User Tools

Site Tools


newdevs:db:schemas:3.10_schma

Index for evergreen



Evergreen Database Schema - 3.10

Index of database - evergreen


Schema acq


Table: acq.acq_lineitem_history

acq.acq_lineitem_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
id bigint NOT NULL
creator integer NOT NULL
editor integer NOT NULL
selector integer NOT NULL
provider integer
purchase_order integer
picklist integer
expected_recv_time timestamp with time zone
create_time timestamp with time zone NOT NULL
edit_time timestamp with time zone NOT NULL
marc text NOT NULL
eg_bib_id bigint
source_label text
state text NOT NULL
cancel_reason integer
estimated_unit_price numeric
claim_policy integer
queued_record bigint
acq_lineitem_hist_id_idx id acq_lineitem_history_queued_record_idx queued_record

Index - Schema acq


View: acq.acq_lineitem_lifecycle

acq.acq_lineitem_lifecycle Structure
F-Key Name Type Description
?column? bigint
audit_time timestamp with time zone
audit_action text
id bigint
creator integer
editor integer
selector integer
provider integer
purchase_order integer
picklist integer
expected_recv_time timestamp with time zone
create_time timestamp with time zone
edit_time timestamp with time zone
marc text
eg_bib_id bigint
source_label text
state text
cancel_reason integer
estimated_unit_price numeric
claim_policy integer
queued_record bigint
SELECT'-1'::integer AS "?column?"
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    lineitem.id
,
    lineitem.creator
,
    lineitem.editor
,
    lineitem.selector
,
    lineitem.provider
,
    lineitem.purchase_order
,
    lineitem.picklist
,
    lineitem.expected_recv_time
,
    lineitem.create_time
,
    lineitem.edit_time
,
    lineitem.marc
,
    lineitem.eg_bib_id
,
    lineitem.source_label
,
    lineitem.state
,
    lineitem.cancel_reason
,
    lineitem.estimated_unit_price
,
    lineitem.claim_policy
,
    lineitem.queued_record
   
FROM acq.lineitem

UNION ALL
 
SELECT acq_lineitem_history.audit_id AS "?column?"
,
    acq_lineitem_history.audit_time
,
    acq_lineitem_history.audit_action
,
    acq_lineitem_history.id
,
    acq_lineitem_history.creator
,
    acq_lineitem_history.editor
,
    acq_lineitem_history.selector
,
    acq_lineitem_history.provider
,
    acq_lineitem_history.purchase_order
,
    acq_lineitem_history.picklist
,
    acq_lineitem_history.expected_recv_time
,
    acq_lineitem_history.create_time
,
    acq_lineitem_history.edit_time
,
    acq_lineitem_history.marc
,
    acq_lineitem_history.eg_bib_id
,
    acq_lineitem_history.source_label
,
    acq_lineitem_history.state
,
    acq_lineitem_history.cancel_reason
,
    acq_lineitem_history.estimated_unit_price
,
    acq_lineitem_history.claim_policy
,
    acq_lineitem_history.queued_record
   
FROM acq.acq_lineitem_history;

Index - Schema acq


Table: acq.acq_purchase_order_history

acq.acq_purchase_order_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
id integer NOT NULL
owner integer NOT NULL
creator integer NOT NULL
editor integer NOT NULL
ordering_agency integer NOT NULL
create_time timestamp with time zone NOT NULL
edit_time timestamp with time zone NOT NULL
provider integer NOT NULL
state text NOT NULL
order_date timestamp with time zone
name text NOT NULL
cancel_reason integer
prepayment_required boolean NOT NULL
acq_po_hist_id_idx id

Index - Schema acq


View: acq.acq_purchase_order_lifecycle

acq.acq_purchase_order_lifecycle Structure
F-Key Name Type Description
?column? bigint
audit_time timestamp with time zone
audit_action text
id integer
owner integer
creator integer
editor integer
ordering_agency integer
create_time timestamp with time zone
edit_time timestamp with time zone
provider integer
state text
order_date timestamp with time zone
name text
cancel_reason integer
prepayment_required boolean
SELECT'-1'::integer AS "?column?"
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    purchase_order.id
,
    purchase_order.owner
,
    purchase_order.creator
,
    purchase_order.editor
,
    purchase_order.ordering_agency
,
    purchase_order.create_time
,
    purchase_order.edit_time
,
    purchase_order.provider
,
    purchase_order.state
,
    purchase_order.order_date
,
    purchase_order.name
,
    purchase_order.cancel_reason
,
    purchase_order.prepayment_required
   
FROM acq.purchase_order

UNION ALL
 
SELECT acq_purchase_order_history.audit_id AS "?column?"
,
    acq_purchase_order_history.audit_time
,
    acq_purchase_order_history.audit_action
,
    acq_purchase_order_history.id
,
    acq_purchase_order_history.owner
,
    acq_purchase_order_history.creator
,
    acq_purchase_order_history.editor
,
    acq_purchase_order_history.ordering_agency
,
    acq_purchase_order_history.create_time
,
    acq_purchase_order_history.edit_time
,
    acq_purchase_order_history.provider
,
    acq_purchase_order_history.state
,
    acq_purchase_order_history.order_date
,
    acq_purchase_order_history.name
,
    acq_purchase_order_history.cancel_reason
,
    acq_purchase_order_history.prepayment_required
   
FROM acq.acq_purchase_order_history;

Index - Schema acq


View: acq.all_fund_allocation_total

acq.all_fund_allocation_total Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT f.id AS fund
,
    COALESCE
(
     (sum
           (
                 (a.amount * acq.exchange_ratio
                       (s.currency_type
                             , f.currency_type
                       )
                 )
           )
     )::numeric
     (100
           ,2
     )
     , (0)::numeric
) AS amount
   
FROM (
     (acq.fund f
     
   LEFT JOIN acq.fund_allocation a 
          ON (
                 (a.fund = f.id)
           )
     )
     
LEFT JOIN acq.funding_source s 
    ON (
           (a.funding_source = s.id)
     )
)
  
GROUP BY f.id;

Index - Schema acq


View: acq.all_fund_combined_balance

acq.all_fund_combined_balance Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT a.fund
,
    
(a.amount - COALESCE
     (c.amount
           , (0)::numeric
     )
) AS amount
   
FROM (acq.all_fund_allocation_total a
     
LEFT JOIN (
      SELECT fund_debit.fund
           ,
            sum
           (fund_debit.amount) AS amount
           
        FROM acq.fund_debit
          
    GROUP BY fund_debit.fund
     ) c 
 USING (fund)
);

Index - Schema acq


View: acq.all_fund_encumbrance_total

acq.all_fund_encumbrance_total Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT f.id AS fund
,
    COALESCE
(encumb.amount
     , (0)::numeric
) AS amount
   
FROM (acq.fund f
     
LEFT JOIN (
      SELECT fund_debit.fund
           ,
            sum
           (fund_debit.amount) AS amount
           
        FROM acq.fund_debit
          
       WHERE fund_debit.encumbrance
          
    GROUP BY fund_debit.fund
     ) encumb 
    ON (
           (f.id = encumb.fund)
     )
);

Index - Schema acq


View: acq.all_fund_spent_balance

acq.all_fund_spent_balance Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT c.fund
,
    
(c.amount - d.amount) AS amount
   
FROM (acq.all_fund_allocation_total c
     
LEFT JOIN acq.all_fund_spent_total d 
 USING (fund)
);

Index - Schema acq


View: acq.all_fund_spent_total

acq.all_fund_spent_total Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT f.id AS fund
,
    COALESCE
(spent.amount
     , (0)::numeric
) AS amount
   
FROM (acq.fund f
     
LEFT JOIN (
      SELECT fund_debit.fund
           ,
            sum
           (fund_debit.amount) AS amount
           
        FROM acq.fund_debit
          
       WHERE (NOT fund_debit.encumbrance)
          
    GROUP BY fund_debit.fund
     ) spent 
    ON (
           (f.id = spent.fund)
     )
);

Index - Schema acq


Table: acq.cancel_reason

acq.cancel_reason Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id org_unit integer UNIQUE#1 NOT NULL
label text UNIQUE#1 NOT NULL
description text NOT NULL
keep_debits boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.claim

acq.claim Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.claim_type.id type integer NOT NULL
acq.lineitem_detail.id lineitem_detail bigint NOT NULL

Tables referencing this one via Foreign Key Constraints:

claim_lid_idx lineitem_detail

Index - Schema acq


Table: acq.claim_event

acq.claim_event Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
acq.claim_event_type.id type integer NOT NULL
acq.claim.id claim serial NOT NULL
event_date timestamp with time zone NOT NULL DEFAULT now()
actor.usr.id creator integer NOT NULL
note text
claim_event_claim_date_idx claim, event_date

Index - Schema acq


Table: acq.claim_event_type

acq.claim_event_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id org_unit integer UNIQUE#1 NOT NULL
code text UNIQUE#1 NOT NULL
description text NOT NULL
library_initiated boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.claim_policy

acq.claim_policy Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id org_unit integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
description text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.claim_policy_action

acq.claim_policy_action Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.claim_policy.id claim_policy integer UNIQUE#1 NOT NULL
action_interval interval UNIQUE#1 NOT NULL
acq.claim_event_type.id action integer NOT NULL

Index - Schema acq


Table: acq.claim_type

acq.claim_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id org_unit integer UNIQUE#1 NOT NULL
code text UNIQUE#1 NOT NULL
description text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.currency_type

acq.currency_type Structure
F-Key Name Type Description
code text PRIMARY KEY
label text

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.debit_attribution

acq.debit_attribution Structure
F-Key Name Type Description
id integer PRIMARY KEY
acq.fund_debit.id fund_debit integer NOT NULL
debit_amount numeric NOT NULL
acq.funding_source_credit.id funding_source_credit integer
credit_amount numeric
acq_attribution_credit_idx funding_source_credit acq_attribution_debit_idx fund_debit

Index - Schema acq


Table: acq.distribution_formula

acq.distribution_formula Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
skip_count integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.distribution_formula_application

acq.distribution_formula_application Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id creator integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
acq.distribution_formula.id formula integer NOT NULL
acq.lineitem.id lineitem integer NOT NULL
acqdfa_creator_idx creator acqdfa_df_idx formula acqdfa_li_idx lineitem

Index - Schema acq


Table: acq.distribution_formula_entry

acq.distribution_formula_entry Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.distribution_formula.id formula integer UNIQUE#1 NOT NULL
position integer UNIQUE#1 NOT NULL
item_count integer NOT NULL
actor.org_unit.id owning_lib integer
asset.copy_location.id location integer
acq.fund.id fund integer
config.circ_modifier.code circ_modifier text
collection_code text

 

acq.distribution_formula_entry Constraints
Name Constraint
acqdfe_must_be_somewhere CHECK (((owning_lib IS NOT NULL) OR (location IS NOT NULL)))

Index - Schema acq


Table: acq.edi_account

acq.edi_account Structure
F-Key Name Type Description
id integer PRIMARY KEY DEFAULT nextval('config.remote_account_id_seq'::regclass)
label text NOT NULL
host text NOT NULL
username text
password text
account text
path text
owner integer NOT NULL
last_activity timestamp with time zone
acq.provider.id provider integer NOT NULL
in_dir text
vendcode text
vendacct text
acq.edi_attr_set.id attr_set integer
use_attrs boolean NOT NULL DEFAULT false

Table acq.edi_account Inherits remote_account,

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.edi_attr

acq.edi_attr Structure
F-Key Name Type Description
key text PRIMARY KEY
label text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.edi_attr_set

acq.edi_attr_set Structure
F-Key Name Type Description
id serial PRIMARY KEY
label text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.edi_attr_set_map

acq.edi_attr_set_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.edi_attr_set.id attr_set integer UNIQUE#1 NOT NULL
acq.edi_attr.key attr text UNIQUE#1 NOT NULL

Index - Schema acq


Table: acq.edi_message

acq.edi_message Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.edi_account.id account integer
remote_file text
create_time timestamp with time zone NOT NULL DEFAULT now()
translate_time timestamp with time zone
process_time timestamp with time zone
error_time timestamp with time zone
status text NOT NULL DEFAULT 'new'::text
edi text
jedi text
error text
acq.purchase_order.id purchase_order integer
message_type text NOT NULL

 

acq.edi_message Constraints
Name Constraint
status_value CHECK ((status = ANY (ARRAY['new'::text, 'translated'::text, 'trans_error'::text, 'processed'::text, 'proc_error'::text, 'delete_error'::text, 'retry'::text, 'complete'::text])))
valid_message_type CHECK ((message_type = ANY (ARRAY['ORDERS'::text, 'ORDRSP'::text, 'INVOIC'::text, 'OSTENQ'::text, 'OSTRPT'::text, 'DESADV'::text])))
edi_message_account_status_idx account, status edi_message_po_idx purchase_order edi_message_remote_file_idx lowercase(remote_file)

Index - Schema acq


Table: acq.exchange_rate

acq.exchange_rate Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.currency_type.code from_currency text UNIQUE#1 NOT NULL
acq.currency_type.code to_currency text UNIQUE#1 NOT NULL
ratio numeric NOT NULL

Index - Schema acq


Table: acq.fiscal_calendar

acq.fiscal_calendar Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.fiscal_year

acq.fiscal_year Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.fiscal_calendar.id calendar integer UNIQUE#1 UNIQUE#2 NOT NULL
year integer UNIQUE#1 NOT NULL
year_begin timestamp with time zone UNIQUE#2 NOT NULL
year_end timestamp with time zone NOT NULL

Index - Schema acq


Table: acq.fund

acq.fund Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id org integer UNIQUE#2 UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
year integer UNIQUE#2 UNIQUE#1 NOT NULL DEFAULT date_part('year'::text, now())
acq.currency_type.code currency_type text NOT NULL
code text UNIQUE#2 NOT NULL
rollover boolean NOT NULL DEFAULT false
propagate boolean NOT NULL DEFAULT true
active boolean NOT NULL DEFAULT true
balance_warning_percent integer
balance_stop_percent integer

 

acq.fund Constraints
Name Constraint
acq_fund_rollover_implies_propagate CHECK ((propagate OR (NOT rollover)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.fund_allocation

acq.fund_allocation Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.funding_source.id funding_source integer NOT NULL
acq.fund.id fund integer NOT NULL
amount numeric NOT NULL
actor.usr.id allocator integer NOT NULL
note text
create_time timestamp with time zone NOT NULL DEFAULT now()
fund_alloc_allocator_idx allocator

Index - Schema acq


Table: acq.fund_allocation_percent

acq.fund_allocation_percent Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.funding_source.id funding_source integer UNIQUE#1 NOT NULL
actor.org_unit.id org integer UNIQUE#1 NOT NULL
fund_code text UNIQUE#1
percent numeric NOT NULL
actor.usr.id allocator integer NOT NULL
note text
create_time timestamp with time zone NOT NULL DEFAULT now()

 

acq.fund_allocation_percent Constraints
Name Constraint
percentage_range CHECK (((percent >= (0)::numeric) AND (percent <= (100)::numeric)))

Index - Schema acq


View: acq.fund_allocation_total

acq.fund_allocation_total Structure
F-Key Name Type Description
fund integer
amount numeric(100,2)
SELECT a.fund
,
    
(sum
     (
           (a.amount * acq.exchange_ratio
                 (s.currency_type
                       , f.currency_type
                 )
           )
     )
)::numeric
(100
     ,2
) AS amount
   
FROM (
     (acq.fund_allocation a
     
        JOIN acq.fund f 
          ON (
                 (a.fund = f.id)
           )
     )
     
  JOIN acq.funding_source s 
    ON (
           (a.funding_source = s.id)
     )
)
  
GROUP BY a.fund;

Index - Schema acq


View: acq.fund_combined_balance

acq.fund_combined_balance Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT c.fund
,
    
(c.amount - COALESCE
     (d.amount
           , 0.0
     )
) AS amount
   
FROM (acq.fund_allocation_total c
     
LEFT JOIN acq.fund_debit_total d 
 USING (fund)
);

Index - Schema acq


Table: acq.fund_debit

acq.fund_debit Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.fund.id fund integer NOT NULL
origin_amount numeric NOT NULL
acq.currency_type.code origin_currency_type text NOT NULL
amount numeric NOT NULL
encumbrance boolean NOT NULL DEFAULT true
debit_type text NOT NULL
acq.fund.id xfer_destination integer
create_time timestamp with time zone NOT NULL DEFAULT now()
acq.invoice_entry.id invoice_entry integer

Tables referencing this one via Foreign Key Constraints:

fund_debit_invoice_entry_idx invoice_entry

Index - Schema acq


View: acq.fund_debit_total

acq.fund_debit_total Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT fund.id AS fund
,
    sum
(COALESCE
     (fund_debit.amount
           , (0)::numeric
     )
) AS amount
   
FROM (acq.fund fund
     
LEFT JOIN acq.fund_debit fund_debit 
    ON (
           (fund.id = fund_debit.fund)
     )
)
  
GROUP BY fund.id;

Index - Schema acq


View: acq.fund_encumbrance_total

acq.fund_encumbrance_total Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT fund.id AS fund
,
    sum
(COALESCE
     (fund_debit.amount
           , (0)::numeric
     )
) AS amount
   
FROM (acq.fund fund
     
LEFT JOIN acq.fund_debit fund_debit 
    ON (
           (fund.id = fund_debit.fund)
     )
)
  
WHERE fund_debit.encumbrance
  
GROUP BY fund.id;

Index - Schema acq


View: acq.fund_spent_balance

acq.fund_spent_balance Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT c.fund
,
    
(c.amount - COALESCE
     (d.amount
           , 0.0
     )
) AS amount
   
FROM (acq.fund_allocation_total c
     
LEFT JOIN acq.fund_spent_total d 
 USING (fund)
);

Index - Schema acq


View: acq.fund_spent_total

acq.fund_spent_total Structure
F-Key Name Type Description
fund integer
amount numeric
SELECT fund.id AS fund
,
    sum
(COALESCE
     (fund_debit.amount
           , (0)::numeric
     )
) AS amount
   
FROM (acq.fund fund
     
LEFT JOIN acq.fund_debit fund_debit 
    ON (
           (fund.id = fund_debit.fund)
     )
)
  
WHERE (NOT fund_debit.encumbrance)
  
GROUP BY fund.id;

Index - Schema acq


Table: acq.fund_tag

acq.fund_tag Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.fund_tag_map

acq.fund_tag_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.fund.id fund integer UNIQUE#1 NOT NULL
acq.fund_tag.id tag integer UNIQUE#1

Index - Schema acq


Table: acq.fund_transfer

Fund Transfer Each row represents the transfer of money from a source fund to a destination fund. There should be corresponding entries in acq.fund_allocation. The purpose of acq.fund_transfer is to record how much money moved from which fund to which other fund. The presence of two amount fields, rather than one, reflects the possibility that the two funds are denominated in different currencies. If they use the same currency type, the two amounts should be the same.

acq.fund_transfer Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.fund.id src_fund integer NOT NULL
src_amount numeric NOT NULL
acq.fund.id dest_fund integer
dest_amount numeric
transfer_time timestamp with time zone NOT NULL DEFAULT now()
actor.usr.id transfer_user integer NOT NULL
note text
acq.funding_source_credit.id funding_source_credit integer NOT NULL
acqftr_usr_idx transfer_user

Index - Schema acq


Table: acq.funding_source

acq.funding_source Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#2 NOT NULL
actor.org_unit.id owner integer UNIQUE#1 UNIQUE#2 NOT NULL
acq.currency_type.code currency_type text NOT NULL
code text UNIQUE#1 NOT NULL
active boolean NOT NULL DEFAULT true

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


View: acq.funding_source_allocation_total

acq.funding_source_allocation_total Structure
F-Key Name Type Description
funding_source integer
amount numeric(100,2)
SELECT a.funding_source
,
    
(sum
     (a.amount)
)::numeric
(100
     ,2
) AS amount
   
FROM acq.fund_allocation a
  
GROUP BY a.funding_source;

Index - Schema acq


View: acq.funding_source_balance

acq.funding_source_balance Structure
F-Key Name Type Description
funding_source integer
amount numeric(100,2)
SELECT COALESCE
(c.funding_source
     , a.funding_source
) AS funding_source
,
    
(sum
     (
           (COALESCE
                 (c.amount
                       , 0.0
                 ) - COALESCE
                 (a.amount
                       , 0.0
                 )
           )
     )
)::numeric
(100
     ,2
) AS amount
   
FROM (acq.funding_source_credit_total c
     FULL 
  JOIN acq.funding_source_allocation_total a 
 USING (funding_source)
)
  
GROUP BY COALESCE
(c.funding_source
     , a.funding_source
);

Index - Schema acq


Table: acq.funding_source_credit

acq.funding_source_credit Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.funding_source.id funding_source integer NOT NULL
amount numeric NOT NULL
note text
deadline_date timestamp with time zone
effective_date timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


View: acq.funding_source_credit_total

acq.funding_source_credit_total Structure
F-Key Name Type Description
funding_source integer
amount numeric
SELECT funding_source_credit.funding_source
,
    sum
(funding_source_credit.amount) AS amount
   
FROM acq.funding_source_credit
  
GROUP BY funding_source_credit.funding_source;

Index - Schema acq


Table: acq.invoice

acq.invoice Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id receiver integer NOT NULL
acq.provider.id provider integer UNIQUE#1 NOT NULL
acq.provider.id shipper integer NOT NULL
recv_date timestamp with time zone NOT NULL DEFAULT now()
acq.invoice_method.code recv_method text NOT NULL DEFAULT 'EDI'::text
inv_type text
inv_ident text UNIQUE#1 NOT NULL
payment_auth text
acq.invoice_payment_method.code payment_method text
note text
close_date timestamp with time zone
actor.usr.id closed_by integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.invoice_entry

acq.invoice_entry Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.invoice.id invoice integer NOT NULL
acq.purchase_order.id purchase_order integer
acq.lineitem.id lineitem integer
inv_item_count integer NOT NULL
phys_item_count integer
note text
billed_per_item boolean
cost_billed numeric(8,2)
actual_cost numeric(8,2)
amount_paid numeric(8,2)

Tables referencing this one via Foreign Key Constraints:

ie_inv_idx invoice ie_li_idx lineitem ie_po_idx purchase_order

Index - Schema acq


Table: acq.invoice_item

acq.invoice_item Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.invoice.id invoice integer NOT NULL
acq.purchase_order.id purchase_order integer
acq.fund_debit.id fund_debit integer
acq.invoice_item_type.code inv_item_type text NOT NULL
title text
author text
note text
cost_billed numeric(8,2)
actual_cost numeric(8,2)
acq.fund.id fund integer
amount_paid numeric(8,2)
acq.po_item.id po_item integer
target bigint
ii_fund_debit_idx fund_debit ii_inv_idx invoice ii_po_idx purchase_order ii_poi_idx po_item

Index - Schema acq


Table: acq.invoice_item_type

acq.invoice_item_type Structure
F-Key Name Type Description
code text PRIMARY KEY
name text NOT NULL
prorate boolean NOT NULL DEFAULT false
blanket boolean NOT NULL DEFAULT false

 

acq.invoice_item_type Constraints
Name Constraint
aiit_not_blanket_and_prorate CHECK (((blanket IS FALSE) OR (prorate IS FALSE)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.invoice_method

acq.invoice_method Structure
F-Key Name Type Description
code text PRIMARY KEY
name text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.invoice_payment_method

acq.invoice_payment_method Structure
F-Key Name Type Description
code text PRIMARY KEY
name text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


View: acq.li_state_label

acq.li_state_label Structure
F-Key Name Type Description
id text
label text
SELECT t.id
,
    t.label
   
FROM ( VALUES 
     ('new'::text
           ,oils_i18n_gettext
           ('new'::text
                 ,'New'::text
                 ,'jubstlbl'::text
                 ,'label'::text
           )
     )
     , ('selector-ready'::text
           ,oils_i18n_gettext
           ('selector-ready'::text
                 ,'Selector-Ready'::text
                 ,'jubstlbl'::text
                 ,'label'::text
           )
     )
     , ('order-ready'::text
           ,oils_i18n_gettext
           ('order-ready'::text
                 ,'Order-Ready'::text
                 ,'jubstlbl'::text
                 ,'label'::text
           )
     )
     , ('approved'::text
           ,oils_i18n_gettext
           ('approved'::text
                 ,'Approved'::text
                 ,'jubstlbl'::text
                 ,'label'::text
           )
     )
     , ('pending-order'::text
           ,oils_i18n_gettext
           ('pending-order'::text
                 ,'Pending-Order'::text
                 ,'jubstlbl'::text
                 ,'label'::text
           )
     )
     , ('on-order'::text
           ,oils_i18n_gettext
           ('on-order'::text
                 ,'On-Order'::text
                 ,'jubstlbl'::text
                 ,'label'::text
           )
     )
     , ('received'::text
           ,oils_i18n_gettext
           ('received'::text
                 ,'Received'::text
                 ,'jubstlbl'::text
                 ,'label'::text
           )
     )
     , ('cancelled'::text
           ,oils_i18n_gettext
           ('cancelled'::text
                 ,'Cancelled'::text
                 ,'jubstlbl'::text
                 ,'label'::text
           )
     )
) t
(id
     , label
);

Index - Schema acq


Table: acq.lineitem

acq.lineitem Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id creator integer NOT NULL
actor.usr.id editor integer NOT NULL
actor.usr.id selector integer NOT NULL
acq.provider.id provider integer
acq.purchase_order.id purchase_order integer
acq.picklist.id picklist integer
expected_recv_time timestamp with time zone
create_time timestamp with time zone NOT NULL DEFAULT now()
edit_time timestamp with time zone NOT NULL DEFAULT now()
marc text NOT NULL
biblio.record_entry.id eg_bib_id bigint
source_label text
state text NOT NULL DEFAULT 'new'::text
acq.cancel_reason.id cancel_reason integer
estimated_unit_price numeric
acq.claim_policy.id claim_policy integer
vandelay.queued_bib_record.id queued_record bigint

 

acq.lineitem Constraints
Name Constraint
picklist_or_po CHECK (((picklist IS NOT NULL) OR (purchase_order IS NOT NULL)))

Tables referencing this one via Foreign Key Constraints:

li_creator_idx creator li_editor_idx editor li_pl_idx picklist li_po_idx purchase_order li_queued_record_idx queued_record li_selector_idx selector

Index - Schema acq


Table: acq.lineitem_alert_text

acq.lineitem_alert_text Structure
F-Key Name Type Description
id serial PRIMARY KEY
code text UNIQUE#1 NOT NULL
description text
actor.org_unit.id owning_lib integer UNIQUE#1 NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.lineitem_attr

acq.lineitem_attr Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
definition bigint NOT NULL
acq.lineitem.id lineitem bigint NOT NULL
attr_type text NOT NULL
attr_name text NOT NULL
attr_value text NOT NULL
order_ident boolean NOT NULL DEFAULT false
li_attr_definition_idx definition li_attr_li_idx lineitem li_attr_value_idx attr_value

Index - Schema acq


Table: acq.lineitem_attr_definition

acq.lineitem_attr_definition Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
code text NOT NULL
description text NOT NULL
remove text NOT NULL DEFAULT ''::text
ident boolean NOT NULL DEFAULT false

Index - Schema acq


Table: acq.lineitem_detail

acq.lineitem_detail Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
acq.lineitem.id lineitem integer NOT NULL
acq.fund.id fund integer
acq.fund_debit.id fund_debit integer
eg_copy_id bigint
barcode text
cn_label text
note text
collection_code text
config.circ_modifier.code circ_modifier text
actor.org_unit.id owning_lib integer
asset.copy_location.id location integer
recv_time timestamp with time zone
actor.usr.id receiver integer
acq.cancel_reason.id cancel_reason integer

Tables referencing this one via Foreign Key Constraints:

li_detail_li_idx lineitem lineitem_detail_fund_debit_idx fund_debit

Index - Schema acq


Table: acq.lineitem_generated_attr_definition

acq.lineitem_generated_attr_definition Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('acq.lineitem_attr_definition_id_seq'::regclass)
code text NOT NULL
description text NOT NULL
remove text NOT NULL DEFAULT ''::text
ident boolean NOT NULL DEFAULT false
xpath text NOT NULL

Table acq.lineitem_generated_attr_definition Inherits lineitem_attr_definition,

Index - Schema acq


Table: acq.lineitem_local_attr_definition

acq.lineitem_local_attr_definition Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('acq.lineitem_attr_definition_id_seq'::regclass)
code text NOT NULL
description text NOT NULL
remove text NOT NULL DEFAULT ''::text
ident boolean NOT NULL DEFAULT false

Table acq.lineitem_local_attr_definition Inherits lineitem_attr_definition,

Index - Schema acq


Table: acq.lineitem_marc_attr_definition

acq.lineitem_marc_attr_definition Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('acq.lineitem_attr_definition_id_seq'::regclass)
code text NOT NULL
description text NOT NULL
remove text NOT NULL DEFAULT ''::text
ident boolean NOT NULL DEFAULT false
xpath text NOT NULL

Table acq.lineitem_marc_attr_definition Inherits lineitem_attr_definition,

Index - Schema acq


Table: acq.lineitem_note

acq.lineitem_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.lineitem.id lineitem integer NOT NULL
actor.usr.id creator integer NOT NULL
actor.usr.id editor integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
edit_time timestamp with time zone NOT NULL DEFAULT now()
value text NOT NULL
acq.lineitem_alert_text.id alert_text integer
vendor_public boolean NOT NULL DEFAULT false
li_note_creator_idx creator li_note_editor_idx editor li_note_li_idx lineitem

Index - Schema acq


Table: acq.lineitem_provider_attr_definition

acq.lineitem_provider_attr_definition Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('acq.lineitem_attr_definition_id_seq'::regclass)
code text NOT NULL
description text NOT NULL
remove text NOT NULL DEFAULT ''::text
ident boolean NOT NULL DEFAULT false
xpath text NOT NULL
acq.provider.id provider integer NOT NULL

Table acq.lineitem_provider_attr_definition Inherits lineitem_attr_definition,

Index - Schema acq


View: acq.lineitem_summary

acq.lineitem_summary Structure
F-Key Name Type Description
lineitem bigint
item_count bigint
recv_count bigint
cancel_count bigint
delay_count bigint
invoice_count bigint
claim_count bigint
estimated_amount numeric(8,2)
encumbrance_amount numeric(8,2)
paid_amount numeric(8,2)
SELECT li.id AS lineitem
,
    
(
SELECT count
     (lid.id) AS count
           
  FROM acq.lineitem_detail lid
          
 WHERE (lid.lineitem = li.id)
) AS item_count
,
    
(
SELECT count
     (lid.id) AS count
           
  FROM acq.lineitem_detail lid
          
 WHERE (
           (lid.recv_time IS NOT NULL)
         AND (lid.lineitem = li.id)
     )
) AS recv_count
,
    
(
SELECT count
     (lid.id) AS count
           
  FROM (acq.lineitem_detail lid
             
        JOIN acq.cancel_reason acqcr 
          ON (
                 (acqcr.id = lid.cancel_reason)
           )
     )
          
 WHERE (
           (acqcr.keep_debits IS FALSE)
         AND (lid.lineitem = li.id)
     )
) AS cancel_count
,
    
(
SELECT count
     (lid.id) AS count
           
  FROM (acq.lineitem_detail lid
             
        JOIN acq.cancel_reason acqcr 
          ON (
                 (acqcr.id = lid.cancel_reason)
           )
     )
          
 WHERE (
           (acqcr.keep_debits IS TRUE)
         AND (lid.lineitem = li.id)
     )
) AS delay_count
,
    
(
SELECT count
     (lid.id) AS count
           
  FROM (acq.lineitem_detail lid
             
        JOIN acq.fund_debit debit 
          ON (
                 (lid.fund_debit = debit.id)
           )
     )
          
 WHERE (
           (NOT debit.encumbrance)
         AND (lid.lineitem = li.id)
     )
) AS invoice_count
,
    
(
SELECT count
     (DISTINCT lid.id) AS count
           
  FROM (acq.lineitem_detail lid
             
        JOIN acq.claim claim 
          ON (
                 (claim.lineitem_detail = lid.id)
           )
     )
          
 WHERE (lid.lineitem = li.id)
) AS claim_count
,
    
(
SELECT (
           (
                 (count
                       (lid.id)
                 )::numeric * li.estimated_unit_price
           )
     )::numeric
     (8
           ,2
     ) AS "numeric"
           
  FROM acq.lineitem_detail lid
          
 WHERE (
           (lid.cancel_reason IS NULL)
         AND (lid.lineitem = li.id)
     )
) AS estimated_amount
,
    
(
SELECT (sum
           (debit.amount)
     )::numeric
     (8
           ,2
     ) AS sum
           
  FROM (acq.lineitem_detail lid
             
        JOIN acq.fund_debit debit 
          ON (
                 (lid.fund_debit = debit.id)
           )
     )
          
 WHERE (debit.encumbrance 
         AND (lid.lineitem = li.id)
     )
) AS encumbrance_amount
,
    
(
SELECT (sum
           (debit.amount)
     )::numeric
     (8
           ,2
     ) AS sum
           
  FROM (acq.lineitem_detail lid
             
        JOIN acq.fund_debit debit 
          ON (
                 (lid.fund_debit = debit.id)
           )
     )
          
 WHERE (
           (NOT debit.encumbrance)
         AND (lid.lineitem = li.id)
     )
) AS paid_amount
   
FROM acq.lineitem li;

Index - Schema acq


Table: acq.lineitem_usr_attr_definition

acq.lineitem_usr_attr_definition Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('acq.lineitem_attr_definition_id_seq'::regclass)
code text NOT NULL
description text NOT NULL
remove text NOT NULL DEFAULT ''::text
ident boolean NOT NULL DEFAULT false
actor.usr.id usr integer NOT NULL

Table acq.lineitem_usr_attr_definition Inherits lineitem_attr_definition,

li_usr_attr_def_usr_idx usr

Index - Schema acq


View: acq.ordered_funding_source_credit

The acq.ordered_funding_source_credit view is a prioritized ordering of funding source credits. When ordered by the first three columns, this view defines the order in which the various credits are to be tapped for spending, subject to the allocations in the acq.fund_allocation table. The first column reflects the principle that we should spend money with deadlines before spending money without deadlines. The second column reflects the principle that we should spend the oldest money first. For money with deadlines, that means that we spend first from the credit with the earliest deadline. For money without deadlines, we spend first from the credit with the earliest effective date. The third column is a tie breaker to ensure a consistent ordering.

acq.ordered_funding_source_credit Structure
F-Key Name Type Description
sort_priority integer
sort_date timestamp with time zone
id integer
funding_source integer
amount numeric
note text
SELECT
        CASE
            WHEN 
(funding_source_credit.deadline_date IS NULL) THEN 2
            ELSE 1
        END AS sort_priority
,
        CASE
            WHEN 
(funding_source_credit.deadline_date IS NULL) THEN funding_source_credit.effective_date
            ELSE funding_source_credit.deadline_date
        END AS sort_date
,
    funding_source_credit.id
,
    funding_source_credit.funding_source
,
    funding_source_credit.amount
,
    funding_source_credit.note
   
FROM acq.funding_source_credit;

Index - Schema acq


Table: acq.picklist

acq.picklist Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id owner integer UNIQUE#1 NOT NULL
actor.usr.id creator integer NOT NULL
actor.usr.id editor integer NOT NULL
actor.org_unit.id org_unit integer NOT NULL
name text UNIQUE#1 NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
edit_time timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

acq_picklist_creator_idx creator acq_picklist_editor_idx editor acq_picklist_owner_idx owner

Index - Schema acq


Table: acq.po_item

acq.po_item Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.purchase_order.id purchase_order integer
acq.fund_debit.id fund_debit integer
acq.invoice_item_type.code inv_item_type text NOT NULL
title text
author text
note text
estimated_cost numeric(8,2)
acq.fund.id fund integer
target bigint

Tables referencing this one via Foreign Key Constraints:

poi_fund_debit_idx fund_debit poi_po_idx purchase_order

Index - Schema acq


Table: acq.po_note

acq.po_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.purchase_order.id purchase_order integer NOT NULL
actor.usr.id creator integer NOT NULL
actor.usr.id editor integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
edit_time timestamp with time zone NOT NULL DEFAULT now()
value text NOT NULL
vendor_public boolean NOT NULL DEFAULT false
acq_po_note_creator_idx creator acq_po_note_editor_idx editor po_note_po_idx purchase_order

Index - Schema acq


View: acq.po_state_label

acq.po_state_label Structure
F-Key Name Type Description
id text
label text
SELECT t.id
,
    t.label
   
FROM ( VALUES 
     ('new'::text
           ,oils_i18n_gettext
           ('new'::text
                 ,'New'::text
                 ,'acqpostlbl'::text
                 ,'label'::text
           )
     )
     , ('pending'::text
           ,oils_i18n_gettext
           ('pending'::text
                 ,'Pending'::text
                 ,'acqpostlbl'::text
                 ,'label'::text
           )
     )
     , ('on-order'::text
           ,oils_i18n_gettext
           ('on-order'::text
                 ,'On-Order'::text
                 ,'acqpostlbl'::text
                 ,'label'::text
           )
     )
     , ('received'::text
           ,oils_i18n_gettext
           ('received'::text
                 ,'Received'::text
                 ,'acqpostlbl'::text
                 ,'label'::text
           )
     )
     , ('cancelled'::text
           ,oils_i18n_gettext
           ('cancelled'::text
                 ,'Cancelled'::text
                 ,'acqpostlbl'::text
                 ,'label'::text
           )
     )
) t
(id
     , label
);

Index - Schema acq


Table: acq.provider

acq.provider Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
actor.org_unit.id owner integer UNIQUE#2 UNIQUE#1 NOT NULL
acq.currency_type.code currency_type text NOT NULL
code text UNIQUE#2 NOT NULL
holding_tag text
san text
acq.edi_account.id edi_default integer
active boolean NOT NULL DEFAULT true
prepayment_required boolean NOT NULL DEFAULT false
url text
email text
phone text
fax_phone text
default_copy_count integer NOT NULL
acq.claim_policy.id default_claim_policy integer
acq.provider_contact.id primary_contact integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.provider_address

acq.provider_address Structure
F-Key Name Type Description
id serial PRIMARY KEY
valid boolean NOT NULL DEFAULT true
address_type text
acq.provider.id provider integer NOT NULL
street1 text NOT NULL
street2 text
city text NOT NULL
county text
state text NOT NULL
country text NOT NULL
post_code text NOT NULL
fax_phone text

Index - Schema acq


Table: acq.provider_contact

acq.provider_contact Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.provider.id provider integer NOT NULL
name text NOT NULL
role text
email text
phone text

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.provider_contact_address

acq.provider_contact_address Structure
F-Key Name Type Description
id serial PRIMARY KEY
valid boolean NOT NULL DEFAULT true
address_type text
acq.provider_contact.id contact integer NOT NULL
street1 text NOT NULL
street2 text
city text NOT NULL
county text
state text NOT NULL
country text NOT NULL
post_code text NOT NULL
fax_phone text

Index - Schema acq


Table: acq.provider_holding_subfield_map

acq.provider_holding_subfield_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.provider.id provider integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
subfield text NOT NULL

Index - Schema acq


Table: acq.provider_note

acq.provider_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.provider.id provider integer NOT NULL
actor.usr.id creator integer NOT NULL
actor.usr.id editor integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
edit_time timestamp with time zone NOT NULL DEFAULT now()
value text NOT NULL
acq_pro_note_creator_idx creator acq_pro_note_editor_idx editor acq_pro_note_pro_idx provider

Index - Schema acq


Table: acq.purchase_order

acq.purchase_order Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id owner integer NOT NULL
actor.usr.id creator integer NOT NULL
actor.usr.id editor integer NOT NULL
actor.org_unit.id ordering_agency integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
edit_time timestamp with time zone NOT NULL DEFAULT now()
acq.provider.id provider integer NOT NULL
state text NOT NULL DEFAULT 'new'::text
order_date timestamp with time zone
name text NOT NULL
acq.cancel_reason.id cancel_reason integer
prepayment_required boolean NOT NULL DEFAULT false

 

acq.purchase_order Constraints
Name Constraint
valid_po_state CHECK ((state = ANY (ARRAY['new'::text, 'pending'::text, 'on-order'::text, 'received'::text, 'cancelled'::text])))

Tables referencing this one via Foreign Key Constraints:

acq_po_org_name_order_date_idx ordering_agency, name, order_date po_creator_idx creator po_editor_idx editor po_owner_idx owner po_provider_idx provider po_state_idx state

Index - Schema acq


Table: acq.serial_claim

acq.serial_claim Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.claim_type.id type integer NOT NULL
serial.item.id item bigint NOT NULL

Tables referencing this one via Foreign Key Constraints:

serial_claim_lid_idx item

Index - Schema acq


Table: acq.serial_claim_event

acq.serial_claim_event Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
acq.claim_event_type.id type integer NOT NULL
acq.serial_claim.id claim serial NOT NULL
event_date timestamp with time zone NOT NULL DEFAULT now()
actor.usr.id creator integer NOT NULL
note text
serial_claim_event_claim_date_idx claim, event_date

Index - Schema acq


Table: acq.shipment_notification

acq.shipment_notification Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id receiver integer NOT NULL
acq.provider.id provider integer UNIQUE#1 NOT NULL
acq.provider.id shipper integer NOT NULL
recv_date timestamp with time zone NOT NULL DEFAULT now()
acq.invoice_method.code recv_method text NOT NULL DEFAULT 'EDI'::text
process_date timestamp with time zone
actor.usr.id processed_by integer
container_code text UNIQUE#1 NOT NULL
lading_number text
note text

Tables referencing this one via Foreign Key Constraints:

acq_asn_container_code_idx container_code

Index - Schema acq


Table: acq.shipment_notification_entry

acq.shipment_notification_entry Structure
F-Key Name Type Description
id serial PRIMARY KEY
acq.shipment_notification.id shipment_notification integer NOT NULL
acq.lineitem.id lineitem integer
item_count integer NOT NULL

Index - Schema acq


Table: acq.user_request

acq.user_request Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id usr integer NOT NULL
hold boolean NOT NULL DEFAULT true
actor.org_unit.id pickup_lib integer NOT NULL
holdable_formats text
phone_notify text
email_notify boolean NOT NULL DEFAULT true
acq.lineitem.id lineitem integer
biblio.record_entry.id eg_bib bigint
request_date timestamp with time zone NOT NULL DEFAULT now()
need_before timestamp with time zone
max_fee text
acq.user_request_type.id request_type integer NOT NULL
isxn text
upc text
title text
volume text
author text
article_title text
article_pages text
publisher text
location text
pubdate text
mentioned text
other_info text
acq.cancel_reason.id cancel_reason integer
cancel_time timestamp with time zone

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Table: acq.user_request_status_type

acq.user_request_status_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
label text

Index - Schema acq


Table: acq.user_request_type

acq.user_request_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
label text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema acq


Function: acq.attribute_debits()

Returns: void

Language: PLPGSQL

/*
Function to attribute expenditures and encumbrances to funding source credits,
and thereby to funding sources.

Read the debits in chonological order, attributing each one to one or
more funding source credits.  Constraints:

1. Don't attribute more to a credit than the amount of the credit.

2. For a given fund, don't attribute more to a funding source than the
source has allocated to that fund.

3. Attribute debits to credits with deadlines before attributing them to
credits without deadlines.  Otherwise attribute to the earliest credits
first, based on the deadline date when present, or on the effective date
when there is no deadline.  Use funding_source_credit.id as a tie-breaker.
This ordering is defined by an ORDER BY clause on the view
acq.ordered_funding_source_credit.

Start by truncating the table acq.debit_attribution.  Then insert a row
into that table for each attribution.  If a debit cannot be fully
attributed, insert a row for the unattributable balance, with the 
funding_source_credit and credit_amount columns NULL.
*/
DECLARE
	curr_fund_source_bal RECORD;
	seqno                INT;     -- sequence num for credits applicable to a fund
	fund_credit          RECORD;  -- current row in temp t_fund_credit table
	fc                   RECORD;  -- used for loading t_fund_credit table
	sc                   RECORD;  -- used for loading t_fund_credit table
	--
	-- Used exclusively in the main loop:
	--
	deb                 RECORD;   -- current row from acq.fund_debit table
	curr_credit_bal     RECORD;   -- current row from temp t_credit table
	debit_balance       NUMERIC;  -- amount left to attribute for current debit
	conv_debit_balance  NUMERIC;  -- debit balance in currency of the fund
	attr_amount         NUMERIC;  -- amount being attributed, in currency of debit
	conv_attr_amount    NUMERIC;  -- amount being attributed, in currency of source
	conv_cred_balance   NUMERIC;  -- credit_balance in the currency of the fund
	conv_alloc_balance  NUMERIC;  -- allocated balance in the currency of the fund
	attrib_count        INT;      -- populates id of acq.debit_attribution
BEGIN
	--
	-- Load a temporary table.  For each combination of fund and funding source,
	-- load an entry with the total amount allocated to that fund by that source.
	-- This sum may reflect transfers as well as original allocations.  We will
	-- reduce this balance whenever we attribute debits to it.
	--
	CREATE TEMP TABLE t_fund_source_bal
	ON COMMIT DROP AS
		SELECT
			fund AS fund,
			funding_source AS source,
			sum( amount ) AS balance
		FROM
			acq.fund_allocation
		GROUP BY
			fund,
			funding_source
		HAVING
			sum( amount ) > 0;
	--
	CREATE INDEX t_fund_source_bal_idx
		ON t_fund_source_bal( fund, source );
	-------------------------------------------------------------------------------
	--
	-- Load another temporary table.  For each fund, load zero or more
	-- funding source credits from which that fund can get money.
	--
	CREATE TEMP TABLE t_fund_credit (
		fund        INT,
		seq         INT,
		credit      INT
	) ON COMMIT DROP;
	--
	FOR fc IN
		SELECT DISTINCT fund
		FROM acq.fund_allocation
		ORDER BY fund
	LOOP                  -- Loop over the funds
		seqno := 1;
		FOR sc IN
			SELECT
				ofsc.id
			FROM
				acq.ordered_funding_source_credit AS ofsc
			WHERE
				ofsc.funding_source IN
				(
					SELECT funding_source
					FROM acq.fund_allocation
					WHERE fund = fc.fund
				)
    		ORDER BY
    		    ofsc.sort_priority,
    		    ofsc.sort_date,
    		    ofsc.id
		LOOP                        -- Add each credit to the list
			INSERT INTO t_fund_credit (
				fund,
				seq,
				credit
			) VALUES (
				fc.fund,
				seqno,
				sc.id
			);
			--RAISE NOTICE 'Fund % credit %', fc.fund, sc.id;
			seqno := seqno + 1;
		END LOOP;     -- Loop over credits for a given fund
	END LOOP;         -- Loop over funds
	--
	CREATE INDEX t_fund_credit_idx
		ON t_fund_credit( fund, seq );
	-------------------------------------------------------------------------------
	--
	-- Load yet another temporary table.  This one is a list of funding source
	-- credits, with their balances.  We shall reduce those balances as we
	-- attribute debits to them.
	--
	CREATE TEMP TABLE t_credit
	ON COMMIT DROP AS
        SELECT
            fsc.id AS credit,
            fsc.funding_source AS source,
            fsc.amount AS balance,
            fs.currency_type AS currency_type
        FROM
            acq.funding_source_credit AS fsc,
            acq.funding_source fs
        WHERE
            fsc.funding_source = fs.id
			AND fsc.amount > 0;
	--
	CREATE INDEX t_credit_idx
		ON t_credit( credit );
	--
	-------------------------------------------------------------------------------
	--
	-- Now that we have loaded the lookup tables: loop through the debits,
	-- attributing each one to one or more funding source credits.
	-- 
	truncate table acq.debit_attribution;
	--
	attrib_count := 0;
	FOR deb in
		SELECT
			fd.id,
			fd.fund,
			fd.amount,
			f.currency_type,
			fd.encumbrance
		FROM
			acq.fund_debit fd,
			acq.fund f
		WHERE
			fd.fund = f.id
		ORDER BY
			fd.id
	LOOP
		--RAISE NOTICE 'Debit %, fund %', deb.id, deb.fund;
		--
		debit_balance := deb.amount;
		--
		-- Loop over the funding source credits that are eligible
		-- to pay for this debit
		--
		FOR fund_credit IN
			SELECT
				credit
			FROM
				t_fund_credit
			WHERE
				fund = deb.fund
			ORDER BY
				seq
		LOOP
			--RAISE NOTICE '   Examining credit %', fund_credit.credit;
			--
			-- Look up the balance for this credit.  If it's zero, then
			-- it's not useful, so treat it as if you didn't find it.
			-- (Actually there shouldn't be any zero balances in the table,
			-- but we check just to make sure.)
			--
			SELECT *
			INTO curr_credit_bal
			FROM t_credit
			WHERE
				credit = fund_credit.credit
				AND balance > 0;
			--
			IF curr_credit_bal IS NULL THEN
				--
				-- This credit is exhausted; try the next one.
				--
				CONTINUE;
			END IF;
			--
			--
			-- At this point we have an applicable credit with some money left.
			-- Now see if the relevant funding_source has any money left.
			--
			-- Look up the balance of the allocation for this combination of
			-- fund and source.  If you find such an entry, but it has a zero
			-- balance, then it's not useful, so treat it as unfound.
			-- (Actually there shouldn't be any zero balances in the table,
			-- but we check just to make sure.)
			--
			SELECT *
			INTO curr_fund_source_bal
			FROM t_fund_source_bal
			WHERE
				fund = deb.fund
				AND source = curr_credit_bal.source
				AND balance > 0;
			--
			IF curr_fund_source_bal IS NULL THEN
				--
				-- This fund/source doesn't exist or is already exhausted,
				-- so we can't use this credit.  Go on to the next one.
				--
				CONTINUE;
			END IF;
			--
			-- Convert the available balances to the currency of the fund
			--
			conv_alloc_balance := curr_fund_source_bal.balance * acq.exchange_ratio(
				curr_credit_bal.currency_type, deb.currency_type );
			conv_cred_balance := curr_credit_bal.balance * acq.exchange_ratio(
				curr_credit_bal.currency_type, deb.currency_type );
			--
			-- Determine how much we can attribute to this credit: the minimum
			-- of the debit amount, the fund/source balance, and the
			-- credit balance
			--
			--RAISE NOTICE '   deb bal %', debit_balance;
			--RAISE NOTICE '      source % balance %', curr_credit_bal.source, conv_alloc_balance;
			--RAISE NOTICE '      credit % balance %', curr_credit_bal.credit, conv_cred_balance;
			--
			conv_attr_amount := NULL;
			attr_amount := debit_balance;
			--
			IF attr_amount > conv_alloc_balance THEN
				attr_amount := conv_alloc_balance;
				conv_attr_amount := curr_fund_source_bal.balance;
			END IF;
			IF attr_amount > conv_cred_balance THEN
				attr_amount := conv_cred_balance;
				conv_attr_amount := curr_credit_bal.balance;
			END IF;
			--
			-- If we're attributing all of one of the balances, then that's how
			-- much we will deduct from the balances, and we already captured
			-- that amount above.  Otherwise we must convert the amount of the
			-- attribution from the currency of the fund back to the currency of
			-- the funding source.
			--
			IF conv_attr_amount IS NULL THEN
				conv_attr_amount := attr_amount * acq.exchange_ratio(
					deb.currency_type, curr_credit_bal.currency_type );
			END IF;
			--
			-- Insert a row to record the attribution
			--
			attrib_count := attrib_count + 1;
			INSERT INTO acq.debit_attribution (
				id,
				fund_debit,
				debit_amount,
				funding_source_credit,
				credit_amount
			) VALUES (
				attrib_count,
				deb.id,
				attr_amount,
				curr_credit_bal.credit,
				conv_attr_amount
			);
			--
			-- Subtract the attributed amount from the various balances
			--
			debit_balance := debit_balance - attr_amount;
			curr_fund_source_bal.balance := curr_fund_source_bal.balance - conv_attr_amount;
			--
			IF curr_fund_source_bal.balance <= 0 THEN
				--
				-- This allocation is exhausted.  Delete it so
				-- that we don't waste time looking at it again.
				--
				DELETE FROM t_fund_source_bal
				WHERE
					fund = curr_fund_source_bal.fund
					AND source = curr_fund_source_bal.source;
			ELSE
				UPDATE t_fund_source_bal
				SET balance = balance - conv_attr_amount
				WHERE
					fund = curr_fund_source_bal.fund
					AND source = curr_fund_source_bal.source;
			END IF;
			--
			IF curr_credit_bal.balance <= 0 THEN
				--
				-- This funding source credit is exhausted.  Delete it
				-- so that we don't waste time looking at it again.
				--
				--DELETE FROM t_credit
				--WHERE
				--	credit = curr_credit_bal.credit;
				--
				DELETE FROM t_fund_credit
				WHERE
					credit = curr_credit_bal.credit;
			ELSE
				UPDATE t_credit
				SET balance = curr_credit_bal.balance
				WHERE
					credit = curr_credit_bal.credit;
			END IF;
			--
			-- Are we done with this debit yet?
			--
			IF debit_balance <= 0 THEN
				EXIT;       -- We've fully attributed this debit; stop looking at credits.
			END IF;
		END LOOP;       -- End loop over credits
		--
		IF debit_balance <> 0 THEN
			--
			-- We weren't able to attribute this debit, or at least not
			-- all of it.  Insert a row for the unattributed balance.
			--
			attrib_count := attrib_count + 1;
			INSERT INTO acq.debit_attribution (
				id,
				fund_debit,
				debit_amount,
				funding_source_credit,
				credit_amount
			) VALUES (
				attrib_count,
				deb.id,
				debit_balance,
				NULL,
				NULL
			);
		END IF;
	END LOOP;   -- End of loop over debits
END;

Function: acq.audit_acq_lineitem_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO acq.acq_lineitem_history
                SELECT	nextval('acq.acq_lineitem_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    OLD.*;
            RETURN NULL;
        END;
        

Function: acq.audit_acq_purchase_order_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO acq.acq_purchase_order_history
                SELECT	nextval('acq.acq_purchase_order_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    OLD.*;
            RETURN NULL;
        END;
        

Function: acq.copy_fund_tags(new_fund_id integer, old_fund_id integer)

Returns: void

Language: PLPGSQL

DECLARE
fund_tag_rec	RECORD;
BEGIN
       
	FOR fund_tag_rec IN SELECT * FROM acq.fund_tag_map WHERE fund=old_fund_id LOOP
                BEGIN
		     INSERT INTO acq.fund_tag_map(fund, tag) VALUES(new_fund_id, fund_tag_rec.tag);
                EXCEPTION
			WHEN unique_violation THEN
			--    RAISE NOTICE 'Fund tag already propagated', old_fund.id;
			CONTINUE;
		END;
	END LOOP;
	RETURN;
END;

Function: acq.create_acq_auditor(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    PERFORM acq.create_acq_seq(sch, tbl);
    PERFORM acq.create_acq_history(sch, tbl);
    PERFORM acq.create_acq_func(sch, tbl);
    PERFORM acq.create_acq_update_trigger(sch, tbl);
    PERFORM acq.create_acq_lifecycle(sch, tbl);
    RETURN TRUE;
END;

Function: acq.create_acq_func(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    EXECUTE $$
        CREATE OR REPLACE FUNCTION acq.audit_$$ || sch || $$_$$ || tbl || $$_func ()
        RETURNS TRIGGER AS $func$
        BEGIN
            INSERT INTO acq.$$ || sch || $$_$$ || tbl || $$_history
                SELECT	nextval('acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    OLD.*;
            RETURN NULL;
        END;
        $func$ LANGUAGE 'plpgsql';
    $$;
	RETURN TRUE;
END;

Function: acq.create_acq_history(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    EXECUTE $$
        CREATE TABLE acq.$$ || sch || $$_$$ || tbl || $$_history (
            audit_id	BIGINT				PRIMARY KEY,
            audit_time	TIMESTAMP WITH TIME ZONE	NOT NULL,
            audit_action	TEXT				NOT NULL,
            LIKE $$ || sch || $$.$$ || tbl || $$
        );
    $$;
	RETURN TRUE;
END;

Function: acq.create_acq_lifecycle(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    EXECUTE $$
        CREATE OR REPLACE VIEW acq.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
            SELECT	-1, now() as audit_time, '-' as audit_action, *
              FROM	$$ || sch || $$.$$ || tbl || $$
                UNION ALL
            SELECT	*
              FROM	acq.$$ || sch || $$_$$ || tbl || $$_history;
    $$;
	RETURN TRUE;
END;

Function: acq.create_acq_seq(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    EXECUTE $$
        CREATE SEQUENCE acq.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
    $$;
	RETURN TRUE;
END;

Function: acq.create_acq_update_trigger(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    EXECUTE $$
        CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
            AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
            EXECUTE PROCEDURE acq.audit_$$ || sch || $$_$$ || tbl || $$_func ();
    $$;
	RETURN TRUE;
END;

Function: acq.exchange_ratio(text, text, numeric)

Returns: numeric

Language: SQL

    SELECT $3 * acq.exchange_ratio($1, $2);

Function: acq.exchange_ratio(to_ex text, from_ex text)

Returns: numeric

Language: PLPGSQL

DECLARE
    rat NUMERIC;
BEGIN
    IF from_ex = to_ex THEN
        RETURN 1.0;
    END IF;

    SELECT ratio INTO rat FROM acq.exchange_rate WHERE from_currency = from_ex AND to_currency = to_ex;

    IF FOUND THEN
        RETURN rat;
    ELSE
        SELECT ratio INTO rat FROM acq.exchange_rate WHERE from_currency = to_ex AND to_currency = from_ex;
        IF FOUND THEN
            RETURN 1.0/rat;
        END IF;
    END IF;

    RETURN NULL;

END;

Function: acq.extract_holding_attr_table(tag integer, lineitem text)

Returns: SET OF flat_lineitem_holding_subfield

Language: PLPGSQL

DECLARE
    counter INT;
    lida    acq.flat_lineitem_holding_subfield%ROWTYPE;
BEGIN

    SELECT  COUNT(*) INTO counter
      FROM  oils_xpath_table(
                'id',
                'marc',
                'acq.lineitem',
                '//*[@tag="' || tag || '"]',
                'id=' || lineitem
            ) as t(i int,c text);

    FOR i IN 1 .. counter LOOP
        FOR lida IN
            SELECT  * 
              FROM  (   SELECT  id,i,t,v
                          FROM  oils_xpath_table(
                                    'id',
                                    'marc',
                                    'acq.lineitem',
                                    '//*[@tag="' || tag || '"][position()=' || i || ']/*/@code|' ||
                                        '//*[@tag="' || tag || '"][position()=' || i || ']/*[@code]',
                                    'id=' || lineitem
                                ) as t(id int,t text,v text)
                    )x
        LOOP
            RETURN NEXT lida;
        END LOOP;
    END LOOP;

    RETURN;
END;

Function: acq.extract_provider_holding_data(lineitem_i integer)

Returns: SET OF flat_lineitem_detail

Language: PLPGSQL

DECLARE
    prov_i  INT;
    tag_t   TEXT;
    lida    acq.flat_lineitem_detail%ROWTYPE;
BEGIN
    SELECT provider INTO prov_i FROM acq.lineitem WHERE id = lineitem_i;
    IF NOT FOUND THEN RETURN; END IF;

    SELECT holding_tag INTO tag_t FROM acq.provider WHERE id = prov_i;
    IF NOT FOUND OR tag_t IS NULL THEN RETURN; END IF;

    FOR lida IN
        SELECT  lineitem_i,
                h.holding,
                a.name,
                h.data
          FROM  acq.extract_holding_attr_table( lineitem_i, tag_t ) h
                JOIN acq.provider_holding_subfield_map a USING (subfield)
          WHERE a.provider = prov_i
    LOOP
        RETURN NEXT lida;
    END LOOP;

    RETURN;
END;

Function: acq.fap_limit_100()

Returns: trigger

Language: PLPGSQL

DECLARE
--
total_percent numeric;
--
BEGIN
    SELECT
        sum( percent )
    INTO
        total_percent
    FROM
        acq.fund_allocation_percent AS fap
    WHERE
        fap.funding_source = NEW.funding_source;
    --
    IF total_percent > 100 THEN
        RAISE EXCEPTION 'Total percentages exceed 100 for funding_source %',
            NEW.funding_source;
    ELSE
        RETURN NEW;
    END IF;
END;

Function: acq.find_bad_fy()

Returns: SET OF record

Language: PLPGSQL

DECLARE
	first_row  BOOLEAN;
	curr_year  RECORD;
	prev_year  RECORD;
	return_rec RECORD;
BEGIN
	first_row := true;
	FOR curr_year in
		SELECT
			id,
			calendar,
			year,
			year_begin,
			year_end
		FROM
			acq.fiscal_year
		ORDER BY
			calendar,
			year_begin
	LOOP
		--
		IF first_row THEN
			first_row := FALSE;
		ELSIF curr_year.calendar    = prev_year.calendar THEN
			IF curr_year.year_begin > prev_year.year_end THEN
				-- This ugly kludge works around the fact that older
				-- versions of PostgreSQL don't support RETURN QUERY SELECT
				FOR return_rec IN SELECT
					prev_year.id,
					prev_year.year,
					'Gap between fiscal years'::TEXT
				LOOP
					RETURN NEXT return_rec;
				END LOOP;
			ELSIF curr_year.year_begin < prev_year.year_end THEN
				FOR return_rec IN SELECT
					prev_year.id,
					prev_year.year,
					'Overlapping fiscal years'::TEXT
				LOOP
					RETURN NEXT return_rec;
				END LOOP;
			ELSIF curr_year.year < prev_year.year THEN
				FOR return_rec IN SELECT
					prev_year.id,
					prev_year.year,
					'Fiscal years out of order'::TEXT
				LOOP
					RETURN NEXT return_rec;
				END LOOP;
			END IF;
		END IF;
		--
		prev_year := curr_year;
	END LOOP;
	--
	RETURN;
END;

Function: acq.fund_alloc_percent_val()

Returns: trigger

Language: PLPGSQL

--
DECLARE
--
dummy int := 0;
--
BEGIN
    SELECT
        1
    INTO
        dummy
    FROM
        acq.fund
    WHERE
        org = NEW.org
        AND code = NEW.fund_code
        LIMIT 1;
    --
    IF dummy = 1 then
        RETURN NEW;
    ELSE
        RAISE EXCEPTION 'No fund exists for org % and code %', NEW.org, NEW.fund_code;
    END IF;
END;

Function: acq.po_org_name_date_unique()

Returns: trigger

Language: PLPGSQL

DECLARE
	collision INT;
BEGIN
	--
	-- If order_date is not null, then make sure we don't have a collision
	-- on order_date (truncated to day), org, and name
	--
	IF NEW.order_date IS NULL THEN
		RETURN NEW;
	END IF;
	--
	-- In the WHERE clause, we compare the order_dates without regard to time of day.
	-- We use a pair of inequalities instead of comparing truncated dates so that the
	-- query can do an indexed range scan.
	--
	SELECT 1 INTO collision
	FROM acq.purchase_order
	WHERE
		ordering_agency = NEW.ordering_agency
		AND name = NEW.name
		AND order_date >= date_trunc( 'day', NEW.order_date )
		AND order_date <  date_trunc( 'day', NEW.order_date ) + '1 day'::INTERVAL
		AND id <> NEW.id;
	--
	IF collision IS NULL THEN
		-- okay, no collision
		RETURN NEW;
	ELSE
		-- collision; nip it in the bud
		RAISE EXCEPTION 'Colliding purchase orders: ordering_agency %, date %, name ''%''',
			NEW.ordering_agency, NEW.order_date, NEW.name;
	END IF;
END;

Function: acq.propagate_funds_by_org_tree(include_desc integer, org_unit_id integer, user_id integer, old_year boolean)

Returns: void

Language: PLPGSQL

DECLARE
--
new_id      INT;
old_fund    RECORD;
org_found   BOOLEAN;
--
BEGIN
	--
	-- Sanity checks
	--
	IF old_year IS NULL THEN
		RAISE EXCEPTION 'Input year argument is NULL';
	ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
		RAISE EXCEPTION 'Input year is out of range';
	END IF;
	--
	IF user_id IS NULL THEN
		RAISE EXCEPTION 'Input user id argument is NULL';
	END IF;
	--
	IF org_unit_id IS NULL THEN
		RAISE EXCEPTION 'Org unit id argument is NULL';
	ELSE
		SELECT TRUE INTO org_found
		FROM actor.org_unit
		WHERE id = org_unit_id;
		--
		IF org_found IS NULL THEN
			RAISE EXCEPTION 'Org unit id is invalid';
		END IF;
	END IF;
	--
	-- Loop over the applicable funds
	--
	FOR old_fund in SELECT * FROM acq.fund
	WHERE
		year = old_year
		AND propagate
		AND ( ( include_desc AND org IN ( SELECT id FROM actor.org_unit_descendants( org_unit_id ) ) )
                OR (NOT include_desc AND org = org_unit_id ) )
    
	LOOP
		BEGIN
			INSERT INTO acq.fund (
				org,
				name,
				year,
				currency_type,
				code,
				rollover,
				propagate,
				balance_warning_percent,
				balance_stop_percent
			) VALUES (
				old_fund.org,
				old_fund.name,
				old_year + 1,
				old_fund.currency_type,
				old_fund.code,
				old_fund.rollover,
				true,
				old_fund.balance_warning_percent,
				old_fund.balance_stop_percent
			)
			RETURNING id INTO new_id;
		EXCEPTION
			WHEN unique_violation THEN
				--RAISE NOTICE 'Fund % already propagated', old_fund.id;
				CONTINUE;
		END;

		PERFORM acq.copy_fund_tags(old_fund.id,new_id);

		--RAISE NOTICE 'Propagating fund % to fund %',
		--	old_fund.code, new_id;
	END LOOP;
END;

Function: acq.propagate_funds_by_org_unit(org_unit_id integer, user_id integer, old_year integer)

Returns: void

Language: SQL

    SELECT acq.propagate_funds_by_org_tree( $1, $2, $3, FALSE );

Function: acq.purchase_order_name_default()

Returns: trigger

Language: PLPGSQL

BEGIN
	IF NEW.name IS NULL THEN
		NEW.name := NEW.id::TEXT;
	END IF;

	RETURN NEW;
END;

Function: acq.rollover_funds_by_org_tree(include_desc integer, encumb_only integer, org_unit_id integer, user_id boolean, old_year boolean)

Returns: void

Language: PLPGSQL

DECLARE
--
new_fund    INT;
new_year    INT := old_year + 1;
org_found   BOOL;
perm_ous    BOOL;
xfer_amount NUMERIC := 0;
roll_fund   RECORD;
deb         RECORD;
detail      RECORD;
roll_distrib_forms BOOL;
--
BEGIN
	--
	-- Sanity checks
	--
	IF old_year IS NULL THEN
		RAISE EXCEPTION 'Input year argument is NULL';
    ELSIF old_year NOT BETWEEN 2008 and 2200 THEN
        RAISE EXCEPTION 'Input year is out of range';
	END IF;
	--
	IF user_id IS NULL THEN
		RAISE EXCEPTION 'Input user id argument is NULL';
	END IF;
	--
	IF org_unit_id IS NULL THEN
		RAISE EXCEPTION 'Org unit id argument is NULL';
	ELSE
		--
		-- Validate the org unit
		--
		SELECT TRUE
		INTO org_found
		FROM actor.org_unit
		WHERE id = org_unit_id;
		--
		IF org_found IS NULL THEN
			RAISE EXCEPTION 'Org unit id % is invalid', org_unit_id;
		ELSIF encumb_only THEN
			SELECT INTO perm_ous value::BOOL FROM
			actor.org_unit_ancestor_setting(
				'acq.fund.allow_rollover_without_money', org_unit_id
			);
			IF NOT FOUND OR NOT perm_ous THEN
				RAISE EXCEPTION 'Encumbrance-only rollover not permitted at org %', org_unit_id;
			END IF;
		END IF;
	END IF;
	--
	-- Loop over the propagable funds to identify the details
	-- from the old fund plus the id of the new one, if it exists.
	--
	FOR roll_fund in
	SELECT
	    oldf.id AS old_fund,
	    oldf.org,
	    oldf.name,
	    oldf.currency_type,
	    oldf.code,
		oldf.rollover,
	    newf.id AS new_fund_id
	FROM
    	acq.fund AS oldf
    	LEFT JOIN acq.fund AS newf
        	ON ( oldf.code = newf.code AND oldf.org = newf.org )
	WHERE
 		    oldf.year = old_year
		AND oldf.propagate
        AND newf.year = new_year
		AND ( ( include_desc AND oldf.org IN ( SELECT id FROM actor.org_unit_descendants( org_unit_id ) ) )
                OR (NOT include_desc AND oldf.org = org_unit_id ) )
	LOOP
		--RAISE NOTICE 'Processing fund %', roll_fund.old_fund;
		--
		IF roll_fund.new_fund_id IS NULL THEN
			--
			-- The old fund hasn't been propagated yet.  Propagate it now.
			--
			INSERT INTO acq.fund (
				org,
				name,
				year,
				currency_type,
				code,
				rollover,
				propagate,
				balance_warning_percent,
				balance_stop_percent
			) VALUES (
				roll_fund.org,
				roll_fund.name,
				new_year,
				roll_fund.currency_type,
				roll_fund.code,
				true,
				true,
				roll_fund.balance_warning_percent,
				roll_fund.balance_stop_percent
			)
			RETURNING id INTO new_fund;

		        PERFORM acq.copy_fund_tags(roll_fund.id,new_fund);

		ELSE
			new_fund = roll_fund.new_fund_id;
		END IF;
		--
		-- Determine the amount to transfer
		--
		SELECT amount
		INTO xfer_amount
		FROM acq.fund_spent_balance
		WHERE fund = roll_fund.old_fund;
		--
		IF xfer_amount <> 0 THEN
			IF NOT encumb_only AND roll_fund.rollover THEN
				--
				-- Transfer balance from old fund to new
				--
				--RAISE NOTICE 'Transferring % from fund % to %', xfer_amount, roll_fund.old_fund, new_fund;
				--
				PERFORM acq.transfer_fund(
					roll_fund.old_fund,
					xfer_amount,
					new_fund,
					xfer_amount,
					user_id,
					'Rollover'
				);
			ELSE
				--
				-- Transfer balance from old fund to the void
				--
				-- RAISE NOTICE 'Transferring % from fund % to the void', xfer_amount, roll_fund.old_fund;
				--
				PERFORM acq.transfer_fund(
					roll_fund.old_fund,
					xfer_amount,
					NULL,
					NULL,
					user_id,
					'Rollover into the void'
				);
			END IF;
		END IF;
		--
		IF roll_fund.rollover THEN
			--
			-- Move any lineitems from the old fund to the new one
			-- where the associated debit is an encumbrance.
			--
			-- Any other tables tying expenditure details to funds should
			-- receive similar treatment.  At this writing there are none.
			--
			UPDATE acq.lineitem_detail
			SET fund = new_fund
			WHERE
    			fund = roll_fund.old_fund -- this condition may be redundant
    			AND fund_debit in
    			(
        			SELECT id
        			FROM acq.fund_debit
        			WHERE
            			fund = roll_fund.old_fund
            			AND encumbrance
    			);
			--
			-- Move encumbrance debits from the old fund to the new fund
			--
			UPDATE acq.fund_debit
			SET fund = new_fund
			wHERE
				fund = roll_fund.old_fund
				AND encumbrance;
		END IF;

		-- Rollover distribution formulae funds
		SELECT INTO roll_distrib_forms value::BOOL FROM
			actor.org_unit_ancestor_setting(
				'acq.fund.rollover_distrib_forms', org_unit_id
			);

		IF roll_distrib_forms THEN
			UPDATE acq.distribution_formula_entry 
				SET fund = roll_fund.new_fund_id
				WHERE fund = roll_fund.old_fund;
		END IF;

		--
		-- Mark old fund as inactive, now that we've closed it
		--
		UPDATE acq.fund
		SET active = FALSE
		WHERE id = roll_fund.old_fund;
	END LOOP;
END;

Function: acq.rollover_funds_by_org_unit(encumb_only integer, org_unit_id integer, user_id integer, old_year boolean)

Returns: void

Language: SQL

    SELECT acq.rollover_funds_by_org_tree( $1, $2, $3, $4, FALSE );

Function: acq.transfer_fund(xfer_note integer, user_id numeric, new_amount integer, new_fund numeric, old_amount integer, old_fund text)

Returns: void

Language: PLPGSQL

/* -------------------------------------------------------------------------------

Function to transfer money from one fund to another.

A transfer is represented as a pair of entries in acq.fund_allocation, with a
negative amount for the old (losing) fund and a positive amount for the new
(gaining) fund.  In some cases there may be more than one such pair of entries
in order to pull the money from different funding sources, or more specifically
from different funding source credits.  For each such pair there is also an
entry in acq.fund_transfer.

Since funding_source is a non-nullable column in acq.fund_allocation, we must
choose a funding source for the transferred money to come from.  This choice
must meet two constraints, so far as possible:

1. The amount transferred from a given funding source must not exceed the
amount allocated to the old fund by the funding source.  To that end we
compare the amount being transferred to the amount allocated.

2. We shouldn't transfer money that has already been spent or encumbered, as
defined by the funding attribution process.  We attribute expenses to the
oldest funding source credits first.  In order to avoid transferring that
attributed money, we reverse the priority, transferring from the newest funding
source credits first.  There can be no guarantee that this approach will
avoid overcommitting a fund, but no other approach can do any better.

In this context the age of a funding source credit is defined by the
deadline_date for credits with deadline_dates, and by the effective_date for
credits without deadline_dates, with the proviso that credits with deadline_dates
are all considered "older" than those without.

----------

In the signature for this function, there is one last parameter commented out,
named "funding_source_in".  Correspondingly, the WHERE clause for the query
driving the main loop has an OR clause commented out, which references the
funding_source_in parameter.

If these lines are uncommented, this function will allow the user optionally to
restrict a fund transfer to a specified funding source.  If the source
parameter is left NULL, then there will be no such restriction.

------------------------------------------------------------------------------- */ 
DECLARE
	same_currency      BOOLEAN;
	currency_ratio     NUMERIC;
	old_fund_currency  TEXT;
	old_remaining      NUMERIC;  -- in currency of old fund
	new_fund_currency  TEXT;
	new_fund_active    BOOLEAN;
	new_remaining      NUMERIC;  -- in currency of new fund
	curr_old_amt       NUMERIC;  -- in currency of old fund
	curr_new_amt       NUMERIC;  -- in currency of new fund
	source_addition    NUMERIC;  -- in currency of funding source
	source_deduction   NUMERIC;  -- in currency of funding source
	orig_allocated_amt NUMERIC;  -- in currency of funding source
	allocated_amt      NUMERIC;  -- in currency of fund
	source             RECORD;
    old_fund_row       acq.fund%ROWTYPE;
    new_fund_row       acq.fund%ROWTYPE;
    old_org_row        actor.org_unit%ROWTYPE;
    new_org_row        actor.org_unit%ROWTYPE;
BEGIN
	--
	-- Sanity checks
	--
	IF old_fund IS NULL THEN
		RAISE EXCEPTION 'acq.transfer_fund: old fund id is NULL';
	END IF;
	--
	IF old_amount IS NULL THEN
		RAISE EXCEPTION 'acq.transfer_fund: amount to transfer is NULL';
	END IF;
	--
	-- The new fund and its amount must be both NULL or both not NULL.
	--
	IF new_fund IS NOT NULL AND new_amount IS NULL THEN
		RAISE EXCEPTION 'acq.transfer_fund: amount to transfer to receiving fund is NULL';
	END IF;
	--
	IF new_fund IS NULL AND new_amount IS NOT NULL THEN
		RAISE EXCEPTION 'acq.transfer_fund: receiving fund is NULL, its amount is not NULL';
	END IF;
	--
	IF user_id IS NULL THEN
		RAISE EXCEPTION 'acq.transfer_fund: user id is NULL';
	END IF;
	--
	-- Initialize the amounts to be transferred, each denominated
	-- in the currency of its respective fund.  They will be
	-- reduced on each iteration of the loop.
	--
	old_remaining := old_amount;
	new_remaining := new_amount;
	--
	-- RAISE NOTICE 'Transferring % in fund % to % in fund %',
	--	old_amount, old_fund, new_amount, new_fund;
	--
	-- Get the currency types of the old and new funds.
	--
	SELECT
		currency_type
	INTO
		old_fund_currency
	FROM
		acq.fund
	WHERE
		id = old_fund;
	--
	IF old_fund_currency IS NULL THEN
		RAISE EXCEPTION 'acq.transfer_fund: old fund id % is not defined', old_fund;
	END IF;
	--
	IF new_fund IS NOT NULL THEN
		SELECT
			currency_type,
			active
		INTO
			new_fund_currency,
			new_fund_active
		FROM
			acq.fund
		WHERE
			id = new_fund;
		--
		IF new_fund_currency IS NULL THEN
			RAISE EXCEPTION 'acq.transfer_fund: new fund id % is not defined', new_fund;
		ELSIF NOT new_fund_active THEN
			--
			-- No point in putting money into a fund from whence you can't spend it
			--
			RAISE EXCEPTION 'acq.transfer_fund: new fund id % is inactive', new_fund;
		END IF;
		--
		IF new_amount = old_amount THEN
			same_currency := true;
			currency_ratio := 1;
		ELSE
			--
			-- We'll have to translate currency between funds.  We presume that
			-- the calling code has already applied an appropriate exchange rate,
			-- so we'll apply the same conversion to each sub-transfer.
			--
			same_currency := false;
			currency_ratio := new_amount / old_amount;
		END IF;
	END IF;

    -- Fetch old and new fund's information
    -- in order to construct the allocation notes
    SELECT INTO old_fund_row * FROM acq.fund WHERE id = old_fund;
    SELECT INTO old_org_row * FROM actor.org_unit WHERE id = old_fund_row.org;
    SELECT INTO new_fund_row * FROM acq.fund WHERE id = new_fund;
    SELECT INTO new_org_row * FROM actor.org_unit WHERE id = new_fund_row.org;

	--
	-- Identify the funding source(s) from which we want to transfer the money.
	-- The principle is that we want to transfer the newest money first, because
	-- we spend the oldest money first.  The priority for spending is defined
	-- by a sort of the view acq.ordered_funding_source_credit.
	--
	FOR source in
		SELECT
			ofsc.id,
			ofsc.funding_source,
			ofsc.amount,
			ofsc.amount * acq.exchange_ratio( fs.currency_type, old_fund_currency )
				AS converted_amt,
			fs.currency_type
		FROM
			acq.ordered_funding_source_credit AS ofsc,
			acq.funding_source fs
		WHERE
			ofsc.funding_source = fs.id
			and ofsc.funding_source IN
			(
				SELECT funding_source
				FROM acq.fund_allocation
				WHERE fund = old_fund
			)
			-- and
			-- (
			-- 	ofsc.funding_source = funding_source_in
			-- 	OR funding_source_in IS NULL
			-- )
		ORDER BY
			ofsc.sort_priority desc,
			ofsc.sort_date desc,
			ofsc.id desc
	LOOP
		--
		-- Determine how much money the old fund got from this funding source,
		-- denominated in the currency types of the source and of the fund.
		-- This result may reflect transfers from previous iterations.
		--
		SELECT
			COALESCE( sum( amount ), 0 ),
			COALESCE( sum( amount )
				* acq.exchange_ratio( source.currency_type, old_fund_currency ), 0 )
		INTO
			orig_allocated_amt,     -- in currency of the source
			allocated_amt           -- in currency of the old fund
		FROM
			acq.fund_allocation
		WHERE
			fund = old_fund
			and funding_source = source.funding_source;
		--	
		-- Determine how much to transfer from this credit, in the currency
		-- of the fund.   Begin with the amount remaining to be attributed:
		--
		curr_old_amt := old_remaining;
		--
		-- Can't attribute more than was allocated from the fund:
		--
		IF curr_old_amt > allocated_amt THEN
			curr_old_amt := allocated_amt;
		END IF;
		--
		-- Can't attribute more than the amount of the current credit:
		--
		IF curr_old_amt > source.converted_amt THEN
			curr_old_amt := source.converted_amt;
		END IF;
		--
		curr_old_amt := trunc( curr_old_amt, 2 );
		--
		old_remaining := old_remaining - curr_old_amt;
		--
		-- Determine the amount to be deducted, if any,
		-- from the old allocation.
		--
		IF old_remaining > 0 THEN
			--
			-- In this case we're using the whole allocation, so use that
			-- amount directly instead of applying a currency translation
			-- and thereby inviting round-off errors.
			--
			source_deduction := - curr_old_amt;
		ELSE 
			source_deduction := trunc(
				( - curr_old_amt ) *
					acq.exchange_ratio( old_fund_currency, source.currency_type ),
				2 );
		END IF;
		--
		IF source_deduction <> 0 THEN
			--
			-- Insert negative allocation for old fund in fund_allocation,
			-- converted into the currency of the funding source
			--
			INSERT INTO acq.fund_allocation (
				funding_source,
				fund,
				amount,
				allocator,
				note
			) VALUES (
				source.funding_source,
				old_fund,
				source_deduction,
				user_id,
				'Transfer to fund ' || new_fund_row.code || ' ('
                                    || new_fund_row.year || ') ('
                                    || new_org_row.shortname || ')'
			);
		END IF;
		--
		IF new_fund IS NOT NULL THEN
			--
			-- Determine how much to add to the new fund, in
			-- its currency, and how much remains to be added:
			--
			IF same_currency THEN
				curr_new_amt := curr_old_amt;
			ELSE
				IF old_remaining = 0 THEN
					--
					-- This is the last iteration, so nothing should be left
					--
					curr_new_amt := new_remaining;
					new_remaining := 0;
				ELSE
					curr_new_amt := trunc( curr_old_amt * currency_ratio, 2 );
					new_remaining := new_remaining - curr_new_amt;
				END IF;
			END IF;
			--
			-- Determine how much to add, if any,
			-- to the new fund's allocation.
			--
			IF old_remaining > 0 THEN
				--
				-- In this case we're using the whole allocation, so use that amount
				-- amount directly instead of applying a currency translation and
				-- thereby inviting round-off errors.
				--
				source_addition := curr_new_amt;
			ELSIF source.currency_type = old_fund_currency THEN
				--
				-- In this case we don't need a round trip currency translation,
				-- thereby inviting round-off errors:
				--
				source_addition := curr_old_amt;
			ELSE 
				source_addition := trunc(
					curr_new_amt *
						acq.exchange_ratio( new_fund_currency, source.currency_type ),
					2 );
			END IF;
			--
			IF source_addition <> 0 THEN
				--
				-- Insert positive allocation for new fund in fund_allocation,
				-- converted to the currency of the founding source
				--
				INSERT INTO acq.fund_allocation (
					funding_source,
					fund,
					amount,
					allocator,
					note
				) VALUES (
					source.funding_source,
					new_fund,
					source_addition,
					user_id,
				    'Transfer from fund ' || old_fund_row.code || ' ('
                                          || old_fund_row.year || ') ('
                                          || old_org_row.shortname || ')'
				);
			END IF;
		END IF;
		--
		IF trunc( curr_old_amt, 2 ) <> 0
		OR trunc( curr_new_amt, 2 ) <> 0 THEN
			--
			-- Insert row in fund_transfer, using amounts in the currency of the funds
			--
			INSERT INTO acq.fund_transfer (
				src_fund,
				src_amount,
				dest_fund,
				dest_amount,
				transfer_user,
				note,
				funding_source_credit
			) VALUES (
				old_fund,
				trunc( curr_old_amt, 2 ),
				new_fund,
				trunc( curr_new_amt, 2 ),
				user_id,
				xfer_note,
				source.id
			);
		END IF;
		--
		if old_remaining <= 0 THEN
			EXIT;                   -- Nothing more to be transferred
		END IF;
	END LOOP;
END;

Schema action


Table: action.aged_circulation

action.aged_circulation Structure
F-Key Name Type Description
usr_post_code text
usr_home_ou integer NOT NULL
usr_profile integer NOT NULL
usr_birth_year integer
copy_call_number integer NOT NULL
copy_owning_lib integer NOT NULL
copy_circ_lib integer NOT NULL
copy_bib_record bigint NOT NULL
id bigint PRIMARY KEY
xact_start timestamp with time zone NOT NULL
xact_finish timestamp with time zone
unrecovered boolean
target_copy bigint NOT NULL
circ_lib integer NOT NULL
circ_staff integer NOT NULL
checkin_staff integer
checkin_lib integer
renewal_remaining integer NOT NULL
grace_period interval NOT NULL
due_date timestamp with time zone
stop_fines_time timestamp with time zone
checkin_time timestamp with time zone
create_time timestamp with time zone NOT NULL
duration interval
fine_interval interval NOT NULL
recurring_fine numeric(6,2)
max_fine numeric(6,2)
phone_renewal boolean NOT NULL
desk_renewal boolean NOT NULL
opac_renewal boolean NOT NULL
duration_rule text NOT NULL
recurring_fine_rule text NOT NULL
max_fine_rule text NOT NULL
stop_fines text
workstation integer
checkin_workstation integer
copy_location integer NOT NULL
checkin_scan_time timestamp with time zone
auto_renewal boolean NOT NULL
auto_renewal_remaining integer
parent_circ bigint
action_aged_circulation_parent_circ_idx parent_circ action_aged_circulation_target_copy_idx target_copy aged_circ_circ_lib_idx circ_lib aged_circ_copy_circ_lib_idx copy_circ_lib aged_circ_copy_location_idx copy_location aged_circ_copy_owning_lib_idx copy_owning_lib aged_circ_start_idx xact_start

Index - Schema action


Table: action.aged_hold_request

action.aged_hold_request Structure
F-Key Name Type Description
usr_post_code text
usr_home_ou integer NOT NULL
usr_profile integer NOT NULL
usr_birth_year integer
staff_placed boolean NOT NULL
id integer PRIMARY KEY
request_time timestamp with time zone NOT NULL
capture_time timestamp with time zone
fulfillment_time timestamp with time zone
checkin_time timestamp with time zone
return_time timestamp with time zone
prev_check_time timestamp with time zone
expire_time timestamp with time zone
cancel_time timestamp with time zone
cancel_cause integer
cancel_note text
target bigint NOT NULL
current_copy bigint
fulfillment_staff integer
fulfillment_lib integer
request_lib integer NOT NULL
selection_ou integer NOT NULL
selection_depth integer NOT NULL
pickup_lib integer NOT NULL
hold_type text
holdable_formats text
phone_notify boolean NOT NULL
email_notify boolean NOT NULL
sms_notify boolean NOT NULL
frozen boolean NOT NULL
thaw_date timestamp with time zone
shelf_time timestamp with time zone
cut_in_line boolean
mint_condition boolean NOT NULL
shelf_expire_time timestamp with time zone
current_shelf_lib integer
behind_desk boolean NOT NULL
hopeless_date timestamp with time zone
aged_hold_request_current_copy_idx current_copy aged_hold_request_fulfillment_staff_idx fulfillment_staff aged_hold_request_pickup_lib_idx pickup_lib aged_hold_request_target_idx target

Index - Schema action


View: action.all_circulation

action.all_circulation Structure
F-Key Name Type Description
id bigint
usr_post_code text
usr_home_ou integer
usr_profile integer
usr_birth_year integer
copy_call_number bigint
copy_location integer
copy_owning_lib integer
copy_circ_lib integer
copy_bib_record bigint
xact_start timestamp with time zone
xact_finish timestamp with time zone
target_copy bigint
circ_lib integer
circ_staff integer
checkin_staff integer
checkin_lib integer
renewal_remaining integer
grace_period interval
due_date timestamp with time zone
stop_fines_time timestamp with time zone
checkin_time timestamp with time zone
create_time timestamp with time zone
duration interval
fine_interval interval
recurring_fine numeric(6,2)
max_fine numeric(6,2)
phone_renewal boolean
desk_renewal boolean
opac_renewal boolean
duration_rule text
recurring_fine_rule text
max_fine_rule text
stop_fines text
workstation integer
checkin_workstation integer
checkin_scan_time timestamp with time zone
parent_circ bigint
auto_renewal boolean
auto_renewal_remaining integer
usr integer
SELECT aged_circulation.id
,
    aged_circulation.usr_post_code
,
    aged_circulation.usr_home_ou
,
    aged_circulation.usr_profile
,
    aged_circulation.usr_birth_year
,
    aged_circulation.copy_call_number
,
    aged_circulation.copy_location
,
    aged_circulation.copy_owning_lib
,
    aged_circulation.copy_circ_lib
,
    aged_circulation.copy_bib_record
,
    aged_circulation.xact_start
,
    aged_circulation.xact_finish
,
    aged_circulation.target_copy
,
    aged_circulation.circ_lib
,
    aged_circulation.circ_staff
,
    aged_circulation.checkin_staff
,
    aged_circulation.checkin_lib
,
    aged_circulation.renewal_remaining
,
    aged_circulation.grace_period
,
    aged_circulation.due_date
,
    aged_circulation.stop_fines_time
,
    aged_circulation.checkin_time
,
    aged_circulation.create_time
,
    aged_circulation.duration
,
    aged_circulation.fine_interval
,
    aged_circulation.recurring_fine
,
    aged_circulation.max_fine
,
    aged_circulation.phone_renewal
,
    aged_circulation.desk_renewal
,
    aged_circulation.opac_renewal
,
    aged_circulation.duration_rule
,
    aged_circulation.recurring_fine_rule
,
    aged_circulation.max_fine_rule
,
    aged_circulation.stop_fines
,
    aged_circulation.workstation
,
    aged_circulation.checkin_workstation
,
    aged_circulation.checkin_scan_time
,
    aged_circulation.parent_circ
,
    aged_circulation.auto_renewal
,
    aged_circulation.auto_renewal_remaining
,
    NULL::integer AS usr
   
FROM action.aged_circulation

UNION ALL
 
SELECT DISTINCT circ.id
,
    COALESCE
(a.post_code
     , b.post_code
) AS usr_post_code
,
    p.home_ou AS usr_home_ou
,
    p.profile AS usr_profile
,
    
(date_part
     ('year'::text
           , p.dob
     )
)::integer AS usr_birth_year
,
    cp.call_number AS copy_call_number
,
    circ.copy_location
,
    cn.owning_lib AS copy_owning_lib
,
    cp.circ_lib AS copy_circ_lib
,
    cn.record AS copy_bib_record
,
    circ.xact_start
,
    circ.xact_finish
,
    circ.target_copy
,
    circ.circ_lib
,
    circ.circ_staff
,
    circ.checkin_staff
,
    circ.checkin_lib
,
    circ.renewal_remaining
,
    circ.grace_period
,
    circ.due_date
,
    circ.stop_fines_time
,
    circ.checkin_time
,
    circ.create_time
,
    circ.duration
,
    circ.fine_interval
,
    circ.recurring_fine
,
    circ.max_fine
,
    circ.phone_renewal
,
    circ.desk_renewal
,
    circ.opac_renewal
,
    circ.duration_rule
,
    circ.recurring_fine_rule
,
    circ.max_fine_rule
,
    circ.stop_fines
,
    circ.workstation
,
    circ.checkin_workstation
,
    circ.checkin_scan_time
,
    circ.parent_circ
,
    circ.auto_renewal
,
    circ.auto_renewal_remaining
,
    circ.usr
   
FROM (
     (
           (
                 (
                       (action.circulation circ
     
                          JOIN asset.copy cp 
                            ON (
                                   (circ.target_copy = cp.id)
                             )
                       )
     
                    JOIN asset.call_number cn 
                      ON (
                             (cp.call_number = cn.id)
                       )
                 )
     
              JOIN actor.usr p 
                ON (
                       (circ.usr = p.id)
                 )
           )
     
   LEFT JOIN actor.usr_address a 
          ON (
                 (p.mailing_address = a.id)
           )
     )
     
LEFT JOIN actor.usr_address b 
    ON (
           (p.billing_address = b.id)
     )
);

Index - Schema action


View: action.all_circulation_combined_types

action.all_circulation_combined_types Structure
F-Key Name Type Description
id bigint
xact_start timestamp with time zone
circ_lib integer
circ_staff integer
create_time timestamp with time zone
item_type text
circ_type text
SELECT acirc.id
,
    acirc.xact_start
,
    acirc.circ_lib
,
    acirc.circ_staff
,
    acirc.create_time
,
    ac_acirc.circ_modifier AS item_type
,
    'regular_circ'::text AS circ_type
   
FROM action.circulation acirc
,
    asset.copy ac_acirc
  
WHERE (acirc.target_copy = ac_acirc.id)
UNION ALL
 
SELECT (ancc.id)::bigint AS id
,
    ancc.circ_time AS xact_start
,
    ancc.circ_lib
,
    ancc.staff AS circ_staff
,
    ancc.circ_time AS create_time
,
    cnct_ancc.name AS item_type
,
    'non-cat_circ'::text AS circ_type
   
FROM action.non_cataloged_circulation ancc
,
    config.non_cataloged_type cnct_ancc
  
WHERE (ancc.item_type = cnct_ancc.id)
UNION ALL
 
SELECT (aihu.id)::bigint AS id
,
    aihu.use_time AS xact_start
,
    aihu.org_unit AS circ_lib
,
    aihu.staff AS circ_staff
,
    aihu.use_time AS create_time
,
    ac_aihu.circ_modifier AS item_type
,
    'in-house_use'::text AS circ_type
   
FROM action.in_house_use aihu
,
    asset.copy ac_aihu
  
WHERE (aihu.item = ac_aihu.id)
UNION ALL
 
SELECT (ancihu.id)::bigint AS id
,
    ancihu.use_time AS xact_start
,
    ancihu.org_unit AS circ_lib
,
    ancihu.staff AS circ_staff
,
    ancihu.use_time AS create_time
,
    cnct_ancihu.name AS item_type
,
    'non-cat-in-house_use'::text AS circ_type
   
FROM action.non_cat_in_house_use ancihu
,
    config.non_cataloged_type cnct_ancihu
  
WHERE (ancihu.item_type = cnct_ancihu.id)
UNION ALL
 
SELECT aacirc.id
,
    aacirc.xact_start
,
    aacirc.circ_lib
,
    aacirc.circ_staff
,
    aacirc.create_time
,
    ac_aacirc.circ_modifier AS item_type
,
    'aged_circ'::text AS circ_type
   
FROM action.aged_circulation aacirc
,
    asset.copy ac_aacirc
  
WHERE (aacirc.target_copy = ac_aacirc.id);

Index - Schema action


View: action.all_circulation_slim

action.all_circulation_slim Structure
F-Key Name Type Description
id bigint
usr integer
xact_start timestamp with time zone
xact_finish timestamp with time zone
unrecovered boolean
target_copy bigint
circ_lib integer
circ_staff integer
checkin_staff integer
checkin_lib integer
renewal_remaining integer
grace_period interval
due_date timestamp with time zone
stop_fines_time timestamp with time zone
checkin_time timestamp with time zone
create_time timestamp with time zone
duration interval
fine_interval interval
recurring_fine numeric(6,2)
max_fine numeric(6,2)
phone_renewal boolean
desk_renewal boolean
opac_renewal boolean
duration_rule text
recurring_fine_rule text
max_fine_rule text
stop_fines text
workstation integer
checkin_workstation integer
copy_location integer
checkin_scan_time timestamp with time zone
auto_renewal boolean
auto_renewal_remaining integer
parent_circ bigint
SELECT circulation.id
,
    circulation.usr
,
    circulation.xact_start
,
    circulation.xact_finish
,
    circulation.unrecovered
,
    circulation.target_copy
,
    circulation.circ_lib
,
    circulation.circ_staff
,
    circulation.checkin_staff
,
    circulation.checkin_lib
,
    circulation.renewal_remaining
,
    circulation.grace_period
,
    circulation.due_date
,
    circulation.stop_fines_time
,
    circulation.checkin_time
,
    circulation.create_time
,
    circulation.duration
,
    circulation.fine_interval
,
    circulation.recurring_fine
,
    circulation.max_fine
,
    circulation.phone_renewal
,
    circulation.desk_renewal
,
    circulation.opac_renewal
,
    circulation.duration_rule
,
    circulation.recurring_fine_rule
,
    circulation.max_fine_rule
,
    circulation.stop_fines
,
    circulation.workstation
,
    circulation.checkin_workstation
,
    circulation.copy_location
,
    circulation.checkin_scan_time
,
    circulation.auto_renewal
,
    circulation.auto_renewal_remaining
,
    circulation.parent_circ
   
FROM action.circulation

UNION ALL
 
SELECT aged_circulation.id
,
    NULL::integer AS usr
,
    aged_circulation.xact_start
,
    aged_circulation.xact_finish
,
    aged_circulation.unrecovered
,
    aged_circulation.target_copy
,
    aged_circulation.circ_lib
,
    aged_circulation.circ_staff
,
    aged_circulation.checkin_staff
,
    aged_circulation.checkin_lib
,
    aged_circulation.renewal_remaining
,
    aged_circulation.grace_period
,
    aged_circulation.due_date
,
    aged_circulation.stop_fines_time
,
    aged_circulation.checkin_time
,
    aged_circulation.create_time
,
    aged_circulation.duration
,
    aged_circulation.fine_interval
,
    aged_circulation.recurring_fine
,
    aged_circulation.max_fine
,
    aged_circulation.phone_renewal
,
    aged_circulation.desk_renewal
,
    aged_circulation.opac_renewal
,
    aged_circulation.duration_rule
,
    aged_circulation.recurring_fine_rule
,
    aged_circulation.max_fine_rule
,
    aged_circulation.stop_fines
,
    aged_circulation.workstation
,
    aged_circulation.checkin_workstation
,
    aged_circulation.copy_location
,
    aged_circulation.checkin_scan_time
,
    aged_circulation.auto_renewal
,
    aged_circulation.auto_renewal_remaining
,
    aged_circulation.parent_circ
   
FROM action.aged_circulation;

Index - Schema action


View: action.all_hold_request

action.all_hold_request Structure
F-Key Name Type Description
usr_post_code text
usr_home_ou integer
usr_profile integer
usr_birth_year integer
staff_placed boolean
id integer
request_time timestamp with time zone
capture_time timestamp with time zone
fulfillment_time timestamp with time zone
checkin_time timestamp with time zone
return_time timestamp with time zone
prev_check_time timestamp with time zone
expire_time timestamp with time zone
cancel_time timestamp with time zone
cancel_cause integer
cancel_note text
target bigint
current_copy bigint
fulfillment_staff integer
fulfillment_lib integer
request_lib integer
selection_ou integer
selection_depth integer
pickup_lib integer
hold_type text
holdable_formats text
phone_notify boolean
email_notify boolean
sms_notify boolean
frozen boolean
thaw_date timestamp with time zone
shelf_time timestamp with time zone
cut_in_line boolean
mint_condition boolean
shelf_expire_time timestamp with time zone
current_shelf_lib integer
behind_desk boolean
SELECT DISTINCT COALESCE
(a.post_code
     , b.post_code
) AS usr_post_code
,
    p.home_ou AS usr_home_ou
,
    p.profile AS usr_profile
,
    
(date_part
     ('year'::text
           , p.dob
     )
)::integer AS usr_birth_year
,
    
(ahr.requestor <> ahr.usr) AS staff_placed
,
    ahr.id
,
    ahr.request_time
,
    ahr.capture_time
,
    ahr.fulfillment_time
,
    ahr.checkin_time
,
    ahr.return_time
,
    ahr.prev_check_time
,
    ahr.expire_time
,
    ahr.cancel_time
,
    ahr.cancel_cause
,
    ahr.cancel_note
,
    ahr.target
,
    ahr.current_copy
,
    ahr.fulfillment_staff
,
    ahr.fulfillment_lib
,
    ahr.request_lib
,
    ahr.selection_ou
,
    ahr.selection_depth
,
    ahr.pickup_lib
,
    ahr.hold_type
,
    ahr.holdable_formats
,
        CASE
            WHEN 
(ahr.phone_notify IS NULL) THEN false
            WHEN 
(ahr.phone_notify = ''::text) THEN false
            ELSE true
        END AS phone_notify
,
    ahr.email_notify
,
        CASE
            WHEN 
(ahr.sms_notify IS NULL) THEN false
            WHEN 
(ahr.sms_notify = ''::text) THEN false
            ELSE true
        END AS sms_notify
,
    ahr.frozen
,
    ahr.thaw_date
,
    ahr.shelf_time
,
    ahr.cut_in_line
,
    ahr.mint_condition
,
    ahr.shelf_expire_time
,
    ahr.current_shelf_lib
,
    ahr.behind_desk
   
FROM (
     (
           (action.hold_request ahr
     
              JOIN actor.usr p 
                ON (
                       (ahr.usr = p.id)
                 )
           )
     
   LEFT JOIN actor.usr_address a 
          ON (
                 (p.mailing_address = a.id)
           )
     )
     
LEFT JOIN actor.usr_address b 
    ON (
           (p.billing_address = b.id)
     )
)
UNION ALL
 
SELECT aged_hold_request.usr_post_code
,
    aged_hold_request.usr_home_ou
,
    aged_hold_request.usr_profile
,
    aged_hold_request.usr_birth_year
,
    aged_hold_request.staff_placed
,
    aged_hold_request.id
,
    aged_hold_request.request_time
,
    aged_hold_request.capture_time
,
    aged_hold_request.fulfillment_time
,
    aged_hold_request.checkin_time
,
    aged_hold_request.return_time
,
    aged_hold_request.prev_check_time
,
    aged_hold_request.expire_time
,
    aged_hold_request.cancel_time
,
    aged_hold_request.cancel_cause
,
    aged_hold_request.cancel_note
,
    aged_hold_request.target
,
    aged_hold_request.current_copy
,
    aged_hold_request.fulfillment_staff
,
    aged_hold_request.fulfillment_lib
,
    aged_hold_request.request_lib
,
    aged_hold_request.selection_ou
,
    aged_hold_request.selection_depth
,
    aged_hold_request.pickup_lib
,
    aged_hold_request.hold_type
,
    aged_hold_request.holdable_formats
,
    aged_hold_request.phone_notify
,
    aged_hold_request.email_notify
,
    aged_hold_request.sms_notify
,
    aged_hold_request.frozen
,
    aged_hold_request.thaw_date
,
    aged_hold_request.shelf_time
,
    aged_hold_request.cut_in_line
,
    aged_hold_request.mint_condition
,
    aged_hold_request.shelf_expire_time
,
    aged_hold_request.current_shelf_lib
,
    aged_hold_request.behind_desk
   
FROM action.aged_hold_request;

Index - Schema action


Table: action.archive_actor_stat_cat

action.archive_actor_stat_cat Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
xact bigint NOT NULL
stat_cat integer NOT NULL
value text NOT NULL

Index - Schema action


Table: action.archive_asset_stat_cat

action.archive_asset_stat_cat Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
xact bigint NOT NULL
stat_cat integer NOT NULL
value text NOT NULL

Index - Schema action


Table: action.batch_hold_event

action.batch_hold_event Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id staff integer NOT NULL
container.user_bucket.id bucket integer NOT NULL
target integer NOT NULL
hold_type text NOT NULL DEFAULT 'T'::text
run_date timestamp with time zone NOT NULL DEFAULT now()
cancelled timestamp with time zone

Tables referencing this one via Foreign Key Constraints:

Index - Schema action


Table: action.batch_hold_event_map

action.batch_hold_event_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
action.batch_hold_event.id batch_hold_event integer NOT NULL
action.hold_request.id hold integer NOT NULL

Index - Schema action


View: action.billable_circulations

action.billable_circulations Structure
F-Key Name Type Description
id bigint
usr integer
xact_start timestamp with time zone
xact_finish timestamp with time zone
unrecovered boolean
target_copy bigint
circ_lib integer
circ_staff integer
checkin_staff integer
checkin_lib integer
renewal_remaining integer
grace_period interval
due_date timestamp with time zone
stop_fines_time timestamp with time zone
checkin_time timestamp with time zone
create_time timestamp with time zone
duration interval
fine_interval interval
recurring_fine numeric(6,2)
max_fine numeric(6,2)
phone_renewal boolean
desk_renewal boolean
opac_renewal boolean
duration_rule text
recurring_fine_rule text
max_fine_rule text
stop_fines text
workstation integer
checkin_workstation integer
copy_location integer
checkin_scan_time timestamp with time zone
auto_renewal boolean
auto_renewal_remaining integer
parent_circ bigint
SELECT circulation.id
,
    circulation.usr
,
    circulation.xact_start
,
    circulation.xact_finish
,
    circulation.unrecovered
,
    circulation.target_copy
,
    circulation.circ_lib
,
    circulation.circ_staff
,
    circulation.checkin_staff
,
    circulation.checkin_lib
,
    circulation.renewal_remaining
,
    circulation.grace_period
,
    circulation.due_date
,
    circulation.stop_fines_time
,
    circulation.checkin_time
,
    circulation.create_time
,
    circulation.duration
,
    circulation.fine_interval
,
    circulation.recurring_fine
,
    circulation.max_fine
,
    circulation.phone_renewal
,
    circulation.desk_renewal
,
    circulation.opac_renewal
,
    circulation.duration_rule
,
    circulation.recurring_fine_rule
,
    circulation.max_fine_rule
,
    circulation.stop_fines
,
    circulation.workstation
,
    circulation.checkin_workstation
,
    circulation.copy_location
,
    circulation.checkin_scan_time
,
    circulation.auto_renewal
,
    circulation.auto_renewal_remaining
,
    circulation.parent_circ
   
FROM action.circulation
  
WHERE (circulation.xact_finish IS NULL);

Index - Schema action


Table: action.circulation

action.circulation Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.billable_xact_id_seq'::regclass)
actor.usr.id usr integer NOT NULL
xact_start timestamp with time zone NOT NULL DEFAULT now()
xact_finish timestamp with time zone
unrecovered boolean
target_copy bigint NOT NULL
actor.org_unit.id circ_lib integer NOT NULL
circ_staff integer NOT NULL
checkin_staff integer
checkin_lib integer
renewal_remaining integer NOT NULL
grace_period interval NOT NULL
due_date timestamp with time zone
stop_fines_time timestamp with time zone
checkin_time timestamp with time zone
create_time timestamp with time zone NOT NULL DEFAULT now()
duration interval
fine_interval interval NOT NULL DEFAULT '1 day'::interval
recurring_fine numeric(6,2)
max_fine numeric(6,2)
phone_renewal boolean NOT NULL DEFAULT false
desk_renewal boolean NOT NULL DEFAULT false
opac_renewal boolean NOT NULL DEFAULT false
duration_rule text NOT NULL
recurring_fine_rule text NOT NULL
max_fine_rule text NOT NULL
stop_fines text
actor.workstation.id workstation integer
actor.workstation.id checkin_workstation integer
asset.copy_location.id copy_location integer NOT NULL DEFAULT 1
checkin_scan_time timestamp with time zone
auto_renewal boolean NOT NULL DEFAULT false
auto_renewal_remaining integer
action.circulation.id parent_circ bigint

Table action.circulation Inherits billable_xact,

 

action.circulation Constraints
Name Constraint
circulation_stop_fines_check CHECK ((stop_fines = ANY (ARRAY['CHECKIN'::text, 'CLAIMSRETURNED'::text, 'LOST'::text, 'MAXFINES'::text, 'RENEW'::text, 'LONGOVERDUE'::text, 'CLAIMSNEVERCHECKEDOUT'::text])))

Tables referencing this one via Foreign Key Constraints:

action_circulation_target_copy_idx target_copy circ_all_usr_idx usr circ_checkin_staff_idx checkin_staff circ_checkin_time checkin_time) WHERE (checkin_time IS NOT NULL circ_circ_lib_idx circ_lib circ_circ_staff_idx circ_staff circ_open_date_idx xact_start) WHERE (xact_finish IS NULL circ_open_xacts_idx usr) WHERE (xact_finish IS NULL circ_outstanding_idx usr) WHERE (checkin_time IS NULL

Index - Schema action


Table: action.circulation_limit_group_map

action.circulation_limit_group_map Structure
F-Key Name Type Description
action.circulation.id circ bigint PRIMARY KEY
config.circ_limit_group.id limit_group integer PRIMARY KEY

Index - Schema action


Table: action.curbside

action.curbside Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id patron integer NOT NULL
actor.org_unit.id org integer NOT NULL
slot timestamp with time zone
staged timestamp with time zone
actor.usr.id stage_staff integer
arrival timestamp with time zone
delivered timestamp with time zone
actor.usr.id delivery_staff integer
notes text

Index - Schema action


Table: action.emergency_closing

action.emergency_closing Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id creator integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
process_start_time timestamp with time zone
process_end_time timestamp with time zone
last_update_time timestamp with time zone

Tables referencing this one via Foreign Key Constraints:

Index - Schema action


Table: action.emergency_closing_circulation

action.emergency_closing_circulation Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
action.emergency_closing.id emergency_closing integer NOT NULL
action.circulation.id circulation integer NOT NULL
original_due_date timestamp with time zone
process_time timestamp with time zone
emergency_closing_circulation_circulation_idx circulation emergency_closing_circulation_emergency_closing_idx emergency_closing

Index - Schema action


Table: action.emergency_closing_hold

action.emergency_closing_hold Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
action.emergency_closing.id emergency_closing integer NOT NULL
action.hold_request.id hold integer NOT NULL
original_shelf_expire_time timestamp with time zone
process_time timestamp with time zone
emergency_closing_hold_emergency_closing_idx emergency_closing emergency_closing_hold_hold_idx hold

Index - Schema action


Table: action.emergency_closing_reservation

action.emergency_closing_reservation Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
action.emergency_closing.id emergency_closing integer NOT NULL
booking.reservation.id reservation integer NOT NULL
original_end_time timestamp with time zone
process_time timestamp with time zone
emergency_closing_reservation_emergency_closing_idx emergency_closing emergency_closing_reservation_reservation_idx reservation

Index - Schema action


View: action.emergency_closing_status

action.emergency_closing_status Structure
F-Key Name Type Description
id integer
creator integer
create_time timestamp with time zone
process_start_time timestamp with time zone
process_end_time timestamp with time zone
last_update_time timestamp with time zone
circulations bigint
circulations_complete bigint
reservations bigint
reservations_complete bigint
holds bigint
holds_complete bigint
SELECT e.id
,
    e.creator
,
    e.create_time
,
    e.process_start_time
,
    e.process_end_time
,
    e.last_update_time
,
    COALESCE
(c.count
     , (0)::bigint
) AS circulations
,
    COALESCE
(c.completed
     , (0)::bigint
) AS circulations_complete
,
    COALESCE
(b.count
     , (0)::bigint
) AS reservations
,
    COALESCE
(b.completed
     , (0)::bigint
) AS reservations_complete
,
    COALESCE
(h.count
     , (0)::bigint
) AS holds
,
    COALESCE
(h.completed
     , (0)::bigint
) AS holds_complete
   
FROM (
     (
           (action.emergency_closing e
     
         LEFT JOIN (
                  SELECT emergency_closing_circulation.emergency_closing
                       ,
            count
                       (*) AS count
                       ,
            sum
                       (
                             (
                                   (emergency_closing_circulation.process_time IS NOT NULL)
                             )::integer
                       ) AS completed
           
                    FROM action.emergency_closing_circulation
          
                GROUP BY emergency_closing_circulation.emergency_closing
                 ) c 
                ON (
                       (c.emergency_closing = e.id)
                 )
           )
     
   LEFT JOIN (
            SELECT emergency_closing_reservation.emergency_closing
                 ,
            count
                 (*) AS count
                 ,
            sum
                 (
                       (
                             (emergency_closing_reservation.process_time IS NOT NULL)
                       )::integer
                 ) AS completed
           
              FROM action.emergency_closing_reservation
          
          GROUP BY emergency_closing_reservation.emergency_closing
           ) b 
          ON (
                 (b.emergency_closing = e.id)
           )
     )
     
LEFT JOIN (
      SELECT emergency_closing_hold.emergency_closing
           ,
            count
           (*) AS count
           ,
            sum
           (
                 (
                       (emergency_closing_hold.process_time IS NOT NULL)
                 )::integer
           ) AS completed
           
        FROM action.emergency_closing_hold
          
    GROUP BY emergency_closing_hold.emergency_closing
     ) h 
    ON (
           (h.emergency_closing = e.id)
     )
);

Index - Schema action


Table: action.fieldset

action.fieldset Structure
F-Key Name Type Description
id serial PRIMARY KEY
action.fieldset_group.id fieldset_group integer
actor.usr.id owner integer NOT NULL
actor.org_unit.id owning_lib integer UNIQUE#1 NOT NULL
status text NOT NULL
creation_time timestamp with time zone NOT NULL DEFAULT now()
scheduled_time timestamp with time zone
applied_time timestamp with time zone
classname text NOT NULL
name text UNIQUE#1 NOT NULL
error_msg text
query.stored_query.id stored_query integer
pkey_value text

 

action.fieldset Constraints
Name Constraint
fieldset_one_or_the_other CHECK ((((stored_query IS NOT NULL) AND (pkey_value IS NULL)) OR ((pkey_value IS NOT NULL) AND (stored_query IS NULL))))
valid_status CHECK ((status = ANY (ARRAY['PENDING'::text, 'APPLIED'::text, 'ERROR'::text])))

Tables referencing this one via Foreign Key Constraints:

action_fieldset_sched_time_idx scheduled_time action_owner_idx owner

Index - Schema action


Table: action.fieldset_col_val

action.fieldset_col_val Structure
F-Key Name Type Description
id serial PRIMARY KEY
action.fieldset.id fieldset integer UNIQUE#1 NOT NULL
col text UNIQUE#1 NOT NULL
val text

Index - Schema action


Table: action.fieldset_group

action.fieldset_group Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
complete_time timestamp with time zone
container integer
container_type text
can_rollback boolean DEFAULT true
action.fieldset_group.id rollback_group integer
rollback_time timestamp with time zone
actor.usr.id creator integer NOT NULL
actor.org_unit.id owning_lib integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema action


Table: action.hold_copy_map

action.hold_copy_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
action.hold_request.id hold integer UNIQUE#1 NOT NULL
target_copy bigint UNIQUE#1 NOT NULL
proximity numeric
acm_copy_idx target_copy

Index - Schema action


Table: action.hold_notification

action.hold_notification Structure
F-Key Name Type Description
id serial PRIMARY KEY
action.hold_request.id hold integer NOT NULL
actor.usr.id notify_staff integer
notify_time timestamp with time zone NOT NULL DEFAULT now()
method text NOT NULL
note text
ahn_hold_idx hold ahn_notify_staff_idx notify_staff

Index - Schema action


Table: action.hold_request

action.hold_request Structure
F-Key Name Type Description
id serial PRIMARY KEY
request_time timestamp with time zone NOT NULL DEFAULT now()
capture_time timestamp with time zone
fulfillment_time timestamp with time zone
checkin_time timestamp with time zone
return_time timestamp with time zone
prev_check_time timestamp with time zone
expire_time timestamp with time zone
cancel_time timestamp with time zone
action.hold_request_cancel_cause.id cancel_cause integer
cancel_note text
target bigint NOT NULL
current_copy bigint
actor.usr.id fulfillment_staff integer
actor.org_unit.id fulfillment_lib integer
actor.org_unit.id request_lib integer NOT NULL
actor.usr.id requestor integer NOT NULL
actor.usr.id usr integer NOT NULL
selection_ou integer NOT NULL
selection_depth integer NOT NULL
actor.org_unit.id pickup_lib integer NOT NULL
config.hold_type.hold_type hold_type text
holdable_formats text
phone_notify text
email_notify boolean NOT NULL DEFAULT false
sms_notify text
config.sms_carrier.id sms_carrier integer
frozen boolean NOT NULL DEFAULT false
thaw_date timestamp with time zone
shelf_time timestamp with time zone
cut_in_line boolean
mint_condition boolean NOT NULL DEFAULT true
shelf_expire_time timestamp with time zone
actor.org_unit.id current_shelf_lib integer
behind_desk boolean NOT NULL DEFAULT false
hopeless_date timestamp with time zone
acq.user_request.id acq_request integer

 

action.hold_request Constraints
Name Constraint
sms_check CHECK (((sms_notify IS NULL) OR (sms_carrier IS NOT NULL)))

Tables referencing this one via Foreign Key Constraints:

hold_fulfillment_time_idx fulfillment_time) WHERE (fulfillment_time IS NOT NULL hold_request_copy_capture_time_idx current_copy, capture_time hold_request_current_copy_before_cap_idx current_copy) WHERE ((capture_time IS NULL) AND (cancel_time IS NULL) hold_request_current_copy_idx current_copy hold_request_fulfillment_staff_idx fulfillment_staff hold_request_open_captured_shelf_lib_idx current_shelf_lib) WHERE ((capture_time IS NOT NULL) AND (fulfillment_time IS NULL) AND (pickup_lib <> current_shelf_lib) hold_request_open_idx id) WHERE ((cancel_time IS NULL) AND (fulfillment_time IS NULL) hold_request_pickup_lib_idx pickup_lib hold_request_prev_check_time_idx prev_check_time hold_request_requestor_idx requestor hold_request_target_idx target hold_request_time_idx request_time hold_request_usr_idx usr

Index - Schema action


Table: action.hold_request_cancel_cause

action.hold_request_cancel_cause Structure
F-Key Name Type Description
id serial PRIMARY KEY
label text UNIQUE

Tables referencing this one via Foreign Key Constraints:

Index - Schema action


Table: action.hold_request_note

action.hold_request_note Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
action.hold_request.id hold bigint NOT NULL
title text NOT NULL
body text NOT NULL
slip boolean NOT NULL DEFAULT false
pub boolean NOT NULL DEFAULT false
staff boolean NOT NULL DEFAULT false
ahrn_hold_idx hold

Index - Schema action


Table: action.hold_transit_copy

action.hold_transit_copy Structure
F-Key Name Type Description
id integer PRIMARY KEY DEFAULT nextval('action.transit_copy_id_seq'::regclass)
source_send_time timestamp with time zone
dest_recv_time timestamp with time zone
target_copy bigint NOT NULL
source integer NOT NULL
dest integer NOT NULL
prev_hop integer
copy_status integer NOT NULL
persistant_transfer boolean NOT NULL DEFAULT false
prev_dest integer
cancel_time timestamp with time zone
action.hold_request.id hold integer

Table action.hold_transit_copy Inherits transit_copy,

active_hold_transit_cp_idx target_copy active_hold_transit_dest_idx dest active_hold_transit_source_idx source hold_transit_copy_hold_idx hold

Index - Schema action


Table: action.in_house_use

action.in_house_use Structure
F-Key Name Type Description
id serial PRIMARY KEY
item bigint NOT NULL
actor.usr.id staff integer NOT NULL
actor.workstation.id workstation integer
actor.org_unit.id org_unit integer NOT NULL
use_time timestamp with time zone NOT NULL DEFAULT now()
action_in_house_use_staff_idx staff action_in_house_use_ws_idx workstation

Index - Schema action


Table: action.non_cat_in_house_use

action.non_cat_in_house_use Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.non_cataloged_type.id item_type bigint NOT NULL
actor.usr.id staff integer NOT NULL
actor.workstation.id workstation integer
actor.org_unit.id org_unit integer NOT NULL
use_time timestamp with time zone NOT NULL DEFAULT now()
non_cat_in_house_use_staff_idx staff non_cat_in_house_use_ws_idx workstation

Index - Schema action


Table: action.non_cataloged_circulation

action.non_cataloged_circulation Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id patron integer NOT NULL
actor.usr.id staff integer NOT NULL
actor.org_unit.id circ_lib integer NOT NULL
config.non_cataloged_type.id item_type integer NOT NULL
circ_time timestamp with time zone NOT NULL DEFAULT now()
action_non_cat_circ_patron_idx patron action_non_cat_circ_staff_idx staff

Index - Schema action


View: action.open_circulation

action.open_circulation Structure
F-Key Name Type Description
id bigint
usr integer
xact_start timestamp with time zone
xact_finish timestamp with time zone
unrecovered boolean
target_copy bigint
circ_lib integer
circ_staff integer
checkin_staff integer
checkin_lib integer
renewal_remaining integer
grace_period interval
due_date timestamp with time zone
stop_fines_time timestamp with time zone
checkin_time timestamp with time zone
create_time timestamp with time zone
duration interval
fine_interval interval
recurring_fine numeric(6,2)
max_fine numeric(6,2)
phone_renewal boolean
desk_renewal boolean
opac_renewal boolean
duration_rule text
recurring_fine_rule text
max_fine_rule text
stop_fines text
workstation integer
checkin_workstation integer
copy_location integer
checkin_scan_time timestamp with time zone
auto_renewal boolean
auto_renewal_remaining integer
parent_circ bigint
SELECT circulation.id
,
    circulation.usr
,
    circulation.xact_start
,
    circulation.xact_finish
,
    circulation.unrecovered
,
    circulation.target_copy
,
    circulation.circ_lib
,
    circulation.circ_staff
,
    circulation.checkin_staff
,
    circulation.checkin_lib
,
    circulation.renewal_remaining
,
    circulation.grace_period
,
    circulation.due_date
,
    circulation.stop_fines_time
,
    circulation.checkin_time
,
    circulation.create_time
,
    circulation.duration
,
    circulation.fine_interval
,
    circulation.recurring_fine
,
    circulation.max_fine
,
    circulation.phone_renewal
,
    circulation.desk_renewal
,
    circulation.opac_renewal
,
    circulation.duration_rule
,
    circulation.recurring_fine_rule
,
    circulation.max_fine_rule
,
    circulation.stop_fines
,
    circulation.workstation
,
    circulation.checkin_workstation
,
    circulation.copy_location
,
    circulation.checkin_scan_time
,
    circulation.auto_renewal
,
    circulation.auto_renewal_remaining
,
    circulation.parent_circ
   
FROM action.circulation
  
WHERE (circulation.checkin_time IS NULL)
  
ORDER BY circulation.due_date;

Index - Schema action


Table: action.reservation_transit_copy

action.reservation_transit_copy Structure
F-Key Name Type Description
id integer PRIMARY KEY DEFAULT nextval('action.transit_copy_id_seq'::regclass)
source_send_time timestamp with time zone
dest_recv_time timestamp with time zone
booking.resource.id target_copy bigint NOT NULL
source integer NOT NULL
dest integer NOT NULL
prev_hop integer
copy_status integer NOT NULL
persistant_transfer boolean NOT NULL DEFAULT false
prev_dest integer
cancel_time timestamp with time zone
booking.reservation.id reservation integer

Table action.reservation_transit_copy Inherits transit_copy,

active_reservation_transit_cp_idx target_copy active_reservation_transit_dest_idx dest active_reservation_transit_source_idx source

Index - Schema action


Table: action.survey

action.survey Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer NOT NULL
start_date timestamp with time zone NOT NULL DEFAULT now()
end_date timestamp with time zone NOT NULL DEFAULT (now() + '10 years'::interval)
usr_summary boolean NOT NULL DEFAULT false
opac boolean NOT NULL DEFAULT false
poll boolean NOT NULL DEFAULT false
required boolean NOT NULL DEFAULT false
name text NOT NULL
description text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema action


Table: action.survey_answer

action.survey_answer Structure
F-Key Name Type Description
id serial PRIMARY KEY
action.survey_question.id question integer NOT NULL
answer text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema action


Table: action.survey_question

action.survey_question Structure
F-Key Name Type Description
id serial PRIMARY KEY
action.survey.id survey integer NOT NULL
question text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema action


Table: action.survey_response

action.survey_response Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
response_group_id integer
usr integer
action.survey.id survey integer NOT NULL
action.survey_question.id question integer NOT NULL
action.survey_answer.id answer integer NOT NULL
answer_date timestamp with time zone
effective_date timestamp with time zone NOT NULL DEFAULT now()
action_survey_response_usr_idx usr

Index - Schema action


Table: action.transit_copy

action.transit_copy Structure
F-Key Name Type Description
id serial PRIMARY KEY
source_send_time timestamp with time zone
dest_recv_time timestamp with time zone
target_copy bigint NOT NULL
actor.org_unit.id source integer NOT NULL
actor.org_unit.id dest integer NOT NULL
action.transit_copy.id prev_hop integer
config.copy_status.id copy_status integer NOT NULL
persistant_transfer boolean NOT NULL DEFAULT false
actor.org_unit.id prev_dest integer
cancel_time timestamp with time zone

Tables referencing this one via Foreign Key Constraints:

active_transit_cp_idx target_copy active_transit_dest_idx dest active_transit_for_copy target_copy) WHERE ((dest_recv_time IS NULL) AND (cancel_time IS NULL) active_transit_source_idx source

Index - Schema action


View: action.unfulfilled_hold_innermost_loop

action.unfulfilled_hold_innermost_loop Structure
F-Key Name Type Description
hold integer
circ_lib integer
count bigint
SELECT DISTINCT l.hold
,
    l.circ_lib
,
    l.count
   
FROM (action.unfulfilled_hold_loops l
     
  JOIN action.unfulfilled_hold_min_loop m 
 USING (hold)
)
  
WHERE (l.count = m.min);

Index - Schema action


Table: action.unfulfilled_hold_list

action.unfulfilled_hold_list Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
current_copy bigint NOT NULL
hold integer NOT NULL
circ_lib integer NOT NULL
fail_time timestamp with time zone NOT NULL DEFAULT now()
uhr_hold_idx hold

Index - Schema action


View: action.unfulfilled_hold_loops

action.unfulfilled_hold_loops Structure
F-Key Name Type Description
hold integer
circ_lib integer
count bigint
SELECT u.hold
,
    c.circ_lib
,
    count
(*) AS count
   
FROM (action.unfulfilled_hold_list u
     
  JOIN asset.copy c 
    ON (
           (c.id = u.current_copy)
     )
)
  
GROUP BY u.hold
, c.circ_lib;

Index - Schema action


View: action.unfulfilled_hold_max_loop

action.unfulfilled_hold_max_loop Structure
F-Key Name Type Description
hold integer
max bigint
SELECT unfulfilled_hold_loops.hold
,
    max
(unfulfilled_hold_loops.count) AS max
   
FROM action.unfulfilled_hold_loops
  
GROUP BY unfulfilled_hold_loops.hold;

Index - Schema action


View: action.unfulfilled_hold_min_loop

action.unfulfilled_hold_min_loop Structure
F-Key Name Type Description
hold integer
min bigint
SELECT unfulfilled_hold_loops.hold
,
    min
(unfulfilled_hold_loops.count) AS min
   
FROM action.unfulfilled_hold_loops
  
GROUP BY unfulfilled_hold_loops.hold;

Index - Schema action


Table: action.usr_circ_history

action.usr_circ_history Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id usr integer NOT NULL
xact_start timestamp with time zone NOT NULL DEFAULT now()
target_copy bigint NOT NULL
due_date timestamp with time zone NOT NULL
checkin_time timestamp with time zone
action.circulation.id source_circ bigint
action_usr_circ_history_source_circ_idx source_circ action_usr_circ_history_usr_idx usr

Index - Schema action


Function: action.age_circ_on_delete()

Returns: trigger

Language: PLPGSQL

DECLARE
found char := 'N';
BEGIN

    -- If there are any renewals for this circulation, don't archive or delete
    -- it yet.   We'll do so later, when we archive and delete the renewals.

    SELECT 'Y' INTO found
    FROM action.circulation
    WHERE parent_circ = OLD.id
    LIMIT 1;

    IF found = 'Y' THEN
        RETURN NULL;  -- don't delete
	END IF;

    -- Archive a copy of the old row to action.aged_circulation

    INSERT INTO action.aged_circulation
        (id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
        copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
        circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
        stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
        max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
        max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ,
        auto_renewal, auto_renewal_remaining)
      SELECT
        id,usr_post_code, usr_home_ou, usr_profile, usr_birth_year, copy_call_number, copy_location,
        copy_owning_lib, copy_circ_lib, copy_bib_record, xact_start, xact_finish, target_copy,
        circ_lib, circ_staff, checkin_staff, checkin_lib, renewal_remaining, grace_period, due_date,
        stop_fines_time, checkin_time, create_time, duration, fine_interval, recurring_fine,
        max_fine, phone_renewal, desk_renewal, opac_renewal, duration_rule, recurring_fine_rule,
        max_fine_rule, stop_fines, workstation, checkin_workstation, checkin_scan_time, parent_circ,
        auto_renewal, auto_renewal_remaining
        FROM action.all_circulation WHERE id = OLD.id;

    -- Migrate billings and payments to aged tables

    SELECT 'Y' INTO found FROM config.global_flag 
        WHERE name = 'history.money.age_with_circs' AND enabled;

    IF found = 'Y' THEN
        PERFORM money.age_billings_and_payments_for_xact(OLD.id);
    END IF;

    -- Break the link with the user in action_trigger.event (warning: event_output may essentially have this information)
    UPDATE
        action_trigger.event e
    SET
        context_user = NULL
    FROM
        action.all_circulation c
    WHERE
            c.id = OLD.id
        AND e.context_user = c.usr
        AND e.target = c.id
        AND e.event_def IN (
            SELECT id
            FROM action_trigger.event_definition
            WHERE hook in (SELECT key FROM action_trigger.hook WHERE core_type = 'circ')
        )
    ;

    RETURN OLD;
END;

Function: action.age_hold_on_delete()

Returns: trigger

Language: PLPGSQL

DECLARE
BEGIN
    -- Archive a copy of the old row to action.aged_hold_request

    INSERT INTO action.aged_hold_request
           (usr_post_code,
            usr_home_ou,
            usr_profile,
            usr_birth_year,
            staff_placed,
            id,
            request_time,
            capture_time,
            fulfillment_time,
            checkin_time,
            return_time,
            prev_check_time,
            expire_time,
            cancel_time,
            cancel_cause,
            cancel_note,
            target,
            current_copy,
            fulfillment_staff,
            fulfillment_lib,
            request_lib,
            selection_ou,
            selection_depth,
            pickup_lib,
            hold_type,
            holdable_formats,
            phone_notify,
            email_notify,
            sms_notify,
            frozen,
            thaw_date,
            shelf_time,
            cut_in_line,
            mint_condition,
            shelf_expire_time,
            current_shelf_lib,
            behind_desk)
      SELECT 
           usr_post_code,
           usr_home_ou,
           usr_profile,
           usr_birth_year,
           staff_placed,
           id,
           request_time,
           capture_time,
           fulfillment_time,
           checkin_time,
           return_time,
           prev_check_time,
           expire_time,
           cancel_time,
           cancel_cause,
           cancel_note,
           target,
           current_copy,
           fulfillment_staff,
           fulfillment_lib,
           request_lib,
           selection_ou,
           selection_depth,
           pickup_lib,
           hold_type,
           holdable_formats,
           phone_notify,
           email_notify,
           sms_notify,
           frozen,
           thaw_date,
           shelf_time,
           cut_in_line,
           mint_condition,
           shelf_expire_time,
           current_shelf_lib,
           behind_desk
        FROM action.all_hold_request WHERE id = OLD.id;

    RETURN OLD;
END;

Function: action.age_parent_circ_on_delete()

Returns: trigger

Language: PLPGSQL

BEGIN

    -- Having deleted a renewal, we can delete the original circulation (or a previous
    -- renewal, if that's what parent_circ is pointing to).  That deletion will trigger
    -- deletion of any prior parents, etc. recursively.

    IF OLD.parent_circ IS NOT NULL THEN
        DELETE FROM action.circulation
        WHERE id = OLD.parent_circ;
    END IF;

    RETURN OLD;
END;

Function: action.all_circ_chain(ctx_circ_id integer)

Returns: SET OF all_circulation_slim

Language: PLPGSQL

DECLARE
    tmp_circ action.all_circulation_slim%ROWTYPE;
    circ_0 action.all_circulation_slim%ROWTYPE;
BEGIN

    SELECT INTO tmp_circ * FROM action.all_circulation_slim WHERE id = ctx_circ_id;

    IF tmp_circ IS NULL THEN
        RETURN NEXT tmp_circ;
    END IF;
    circ_0 := tmp_circ;

    -- find the front of the chain
    WHILE TRUE LOOP
        SELECT INTO tmp_circ * FROM action.all_circulation_slim 
            WHERE id = tmp_circ.parent_circ;
        IF tmp_circ IS NULL THEN
            EXIT;
        END IF;
        circ_0 := tmp_circ;
    END LOOP;

    -- now send the circs to the caller, oldest to newest
    tmp_circ := circ_0;
    WHILE TRUE LOOP
        IF tmp_circ IS NULL THEN
            EXIT;
        END IF;
        RETURN NEXT tmp_circ;
        SELECT INTO tmp_circ * FROM action.all_circulation_slim 
            WHERE parent_circ = tmp_circ.id;
    END LOOP;

END;

Function: action.apply_fieldset(query integer, pkey_name text, table_name text, fieldset_id text)

Returns: text

Language: PLPGSQL

Applies a specified fieldset, using a supplied table name and primary key name. The query parameter should be non-null only for query-based fieldsets. Returns NULL if successful, or an error message if not.

DECLARE
    statement TEXT;
    where_clause TEXT;
    fs_status TEXT;
    fs_pkey_value TEXT;
    fs_query TEXT;
    sep CHAR;
    status_code TEXT;
    msg TEXT;
    fs_id INT;
    fsg_id INT;
    update_count INT;
    cv RECORD;
    fs_obj action.fieldset%ROWTYPE;
    fs_group action.fieldset_group%ROWTYPE;
    rb_row RECORD;
BEGIN
    -- Sanity checks
    IF fieldset_id IS NULL THEN
        RETURN 'Fieldset ID parameter is NULL';
    END IF;
    IF table_name IS NULL THEN
        RETURN 'Table name parameter is NULL';
    END IF;
    IF pkey_name IS NULL THEN
        RETURN 'Primary key name parameter is NULL';
    END IF;

    SELECT
        status,
        quote_literal( pkey_value )
    INTO
        fs_status,
        fs_pkey_value
    FROM
        action.fieldset
    WHERE
        id = fieldset_id;

    --
    -- Build the WHERE clause.  This differs according to whether it's a
    -- single-row fieldset or a query-based fieldset.
    --
    IF query IS NULL        AND fs_pkey_value IS NULL THEN
        RETURN 'Incomplete fieldset: neither a primary key nor a query available';
    ELSIF query IS NOT NULL AND fs_pkey_value IS NULL THEN
        fs_query := rtrim( query, ';' );
        where_clause := 'WHERE ' || pkey_name || ' IN ( '
                     || fs_query || ' )';
    ELSIF query IS NULL     AND fs_pkey_value IS NOT NULL THEN
        where_clause := 'WHERE ' || pkey_name || ' = ';
        IF pkey_name = 'id' THEN
            where_clause := where_clause || fs_pkey_value;
        ELSIF pkey_name = 'code' THEN
            where_clause := where_clause || quote_literal(fs_pkey_value);
        ELSE
            RETURN 'Only know how to handle "id" and "code" pkeys currently, received ' || pkey_name;
        END IF;
    ELSE  -- both are not null
        RETURN 'Ambiguous fieldset: both a primary key and a query provided';
    END IF;

    IF fs_status IS NULL THEN
        RETURN 'No fieldset found for id = ' || fieldset_id;
    ELSIF fs_status = 'APPLIED' THEN
        RETURN 'Fieldset ' || fieldset_id || ' has already been applied';
    END IF;

    SELECT * INTO fs_obj FROM action.fieldset WHERE id = fieldset_id;
    SELECT * INTO fs_group FROM action.fieldset_group WHERE id = fs_obj.fieldset_group;

    IF fs_group.can_rollback THEN
        -- This is part of a non-rollback group.  We need to record the current values for future rollback.

        INSERT INTO action.fieldset_group (can_rollback, name, creator, owning_lib, container, container_type)
            VALUES (FALSE, 'ROLLBACK: '|| fs_group.name, fs_group.creator, fs_group.owning_lib, fs_group.container, fs_group.container_type);

        fsg_id := CURRVAL('action.fieldset_group_id_seq');

        FOR rb_row IN EXECUTE 'SELECT * FROM ' || table_name || ' ' || where_clause LOOP
            IF pkey_name = 'id' THEN
                fs_pkey_value := rb_row.id;
            ELSIF pkey_name = 'code' THEN
                fs_pkey_value := rb_row.code;
            ELSE
                RETURN 'Only know how to handle "id" and "code" pkeys currently, received ' || pkey_name;
            END IF;
            INSERT INTO action.fieldset (fieldset_group,owner,owning_lib,status,classname,name,pkey_value)
                VALUES (fsg_id, fs_obj.owner, fs_obj.owning_lib, 'PENDING', fs_obj.classname, fs_obj.name || ' ROLLBACK FOR ' || fs_pkey_value, fs_pkey_value);

            fs_id := CURRVAL('action.fieldset_id_seq');
            sep := '';
            FOR cv IN
                SELECT  DISTINCT col
                FROM    action.fieldset_col_val
                WHERE   fieldset = fieldset_id
            LOOP
                EXECUTE 'INSERT INTO action.fieldset_col_val (fieldset, col, val) ' || 
                    'SELECT '|| fs_id || ', '||quote_literal(cv.col)||', '||cv.col||' FROM '||table_name||' WHERE '||pkey_name||' = '||fs_pkey_value;
            END LOOP;
        END LOOP;
    END IF;

    statement := 'UPDATE ' || table_name || ' SET';

    sep := '';
    FOR cv IN
        SELECT  col,
                val
        FROM    action.fieldset_col_val
        WHERE   fieldset = fieldset_id
    LOOP
        statement := statement || sep || ' ' || cv.col
                     || ' = ' || coalesce( quote_literal( cv.val ), 'NULL' );
        sep := ',';
    END LOOP;

    IF sep = '' THEN
        RETURN 'Fieldset ' || fieldset_id || ' has no column values defined';
    END IF;
    statement := statement || ' ' || where_clause;

    --
    -- Execute the update
    --
    BEGIN
        EXECUTE statement;
        GET DIAGNOSTICS update_count = ROW_COUNT;

        IF update_count = 0 THEN
            RAISE data_exception;
        END IF;

        IF fsg_id IS NOT NULL THEN
            UPDATE action.fieldset_group SET rollback_group = fsg_id WHERE id = fs_group.id;
        END IF;

        IF fs_group.id IS NOT NULL THEN
            UPDATE action.fieldset_group SET complete_time = now() WHERE id = fs_group.id;
        END IF;

        UPDATE action.fieldset SET status = 'APPLIED', applied_time = now() WHERE id = fieldset_id;

    EXCEPTION WHEN data_exception THEN
        msg := 'No eligible rows found for fieldset ' || fieldset_id;
        UPDATE action.fieldset SET status = 'ERROR', applied_time = now() WHERE id = fieldset_id;
        RETURN msg;

    END;

    RETURN msg;

EXCEPTION WHEN OTHERS THEN
    msg := 'Unable to apply fieldset ' || fieldset_id || ': ' || sqlerrm;
    UPDATE action.fieldset SET status = 'ERROR', applied_time = now() WHERE id = fieldset_id;
    RETURN msg;

END;

Function: action.archive_stat_cats()

Returns: trigger

Language: PLPGSQL

BEGIN
    INSERT INTO action.archive_actor_stat_cat(xact, stat_cat, value)
        SELECT NEW.id, asceum.stat_cat, asceum.stat_cat_entry
        FROM actor.stat_cat_entry_usr_map asceum
             JOIN actor.stat_cat sc ON asceum.stat_cat = sc.id
        WHERE NEW.usr = asceum.target_usr AND sc.checkout_archive;
    INSERT INTO action.archive_asset_stat_cat(xact, stat_cat, value)
        SELECT NEW.id, ascecm.stat_cat, asce.value
        FROM asset.stat_cat_entry_copy_map ascecm
             JOIN asset.stat_cat sc ON ascecm.stat_cat = sc.id
             JOIN asset.stat_cat_entry asce ON ascecm.stat_cat_entry = asce.id
        WHERE NEW.target_copy = ascecm.owning_copy AND sc.checkout_archive;
    RETURN NULL;
END;

Function: action.circ_chain(ctx_circ_id bigint)

Returns: SET OF circulation

Language: PLPGSQL

DECLARE
    tmp_circ action.circulation%ROWTYPE;
    circ_0 action.circulation%ROWTYPE;
BEGIN

    SELECT INTO tmp_circ * FROM action.circulation WHERE id = ctx_circ_id;

    IF tmp_circ IS NULL THEN
        RETURN NEXT tmp_circ;
    END IF;
    circ_0 := tmp_circ;

    -- find the front of the chain
    WHILE TRUE LOOP
        SELECT INTO tmp_circ * FROM action.circulation WHERE id = tmp_circ.parent_circ;
        IF tmp_circ IS NULL THEN
            EXIT;
        END IF;
        circ_0 := tmp_circ;
    END LOOP;

    -- now send the circs to the caller, oldest to newest
    tmp_circ := circ_0;
    WHILE TRUE LOOP
        IF tmp_circ IS NULL THEN
            EXIT;
        END IF;
        RETURN NEXT tmp_circ;
        SELECT INTO tmp_circ * FROM action.circulation WHERE parent_circ = tmp_circ.id;
    END LOOP;

END;

Function: action.circulation_claims_returned()

Returns: trigger

Language: PLPGSQL

BEGIN
	IF OLD.stop_fines IS NULL OR OLD.stop_fines <> NEW.stop_fines THEN
		IF NEW.stop_fines = 'CLAIMSRETURNED' THEN
			UPDATE actor.usr SET claims_returned_count = claims_returned_count + 1 WHERE id = NEW.usr;
		END IF;
		IF NEW.stop_fines = 'CLAIMSNEVERCHECKEDOUT' THEN
			UPDATE actor.usr SET claims_never_checked_out_count = claims_never_checked_out_count + 1 WHERE id = NEW.usr;
		END IF;
		IF NEW.stop_fines = 'LOST' THEN
			UPDATE asset.copy SET status = 3 WHERE id = NEW.target_copy;
		END IF;
	END IF;
	RETURN NEW;
END;

Function: action.copy_calculated_proximity(vacl_ol integer, vacn_ol integer, vacp_cm integer, vacp_cl text, request integer, pickup integer)

Returns: numeric

Language: PLPGSQL

DECLARE
    baseline_prox   NUMERIC;
    aoupa           actor.org_unit_proximity_adjustment%ROWTYPE;
BEGIN

    -- First, gather the baseline proximity of "here" to pickup lib
    SELECT prox INTO baseline_prox FROM actor.org_unit_proximity WHERE from_org = vacp_cl AND to_org = pickup;

    -- Find any absolute adjustments, and set the baseline prox to that
    SELECT  adj.* INTO aoupa
      FROM  actor.org_unit_proximity_adjustment adj
            LEFT JOIN actor.org_unit_ancestors_distance(vacp_cl) acp_cl ON (acp_cl.id = adj.item_circ_lib)
            LEFT JOIN actor.org_unit_ancestors_distance(vacn_ol) acn_ol ON (acn_ol.id = adj.item_owning_lib)
            LEFT JOIN actor.org_unit_ancestors_distance(vacl_ol) acl_ol ON (acl_ol.id = adj.copy_location)
            LEFT JOIN actor.org_unit_ancestors_distance(pickup) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib)
            LEFT JOIN actor.org_unit_ancestors_distance(request) ahr_rl ON (ahr_rl.id = adj.hold_request_lib)
      WHERE (adj.circ_mod IS NULL OR adj.circ_mod = vacp_cm) AND
            (adj.item_circ_lib IS NULL OR adj.item_circ_lib = acp_cl.id) AND
            (adj.item_owning_lib IS NULL OR adj.item_owning_lib = acn_ol.id) AND
            (adj.copy_location IS NULL OR adj.copy_location = acl_ol.id) AND
            (adj.hold_pickup_lib IS NULL OR adj.hold_pickup_lib = ahr_pl.id) AND
            (adj.hold_request_lib IS NULL OR adj.hold_request_lib = ahr_rl.id) AND
            absolute_adjustment AND
            COALESCE(acp_cl.id, acn_ol.id, acl_ol.id, ahr_pl.id, ahr_rl.id) IS NOT NULL
      ORDER BY
            COALESCE(acp_cl.distance,999)
                + COALESCE(acn_ol.distance,999)
                + COALESCE(acl_ol.distance,999)
                + COALESCE(ahr_pl.distance,999)
                + COALESCE(ahr_rl.distance,999),
            adj.pos
      LIMIT 1;

    IF FOUND THEN
        baseline_prox := aoupa.prox_adjustment;
    END IF;

    -- Now find any relative adjustments, and change the baseline prox based on them
    FOR aoupa IN
        SELECT  adj.*
          FROM  actor.org_unit_proximity_adjustment adj
                LEFT JOIN actor.org_unit_ancestors_distance(vacp_cl) acp_cl ON (acp_cl.id = adj.item_circ_lib)
                LEFT JOIN actor.org_unit_ancestors_distance(vacn_ol) acn_ol ON (acn_ol.id = adj.item_owning_lib)
                LEFT JOIN actor.org_unit_ancestors_distance(vacl_ol) acl_ol ON (acn_ol.id = adj.copy_location)
                LEFT JOIN actor.org_unit_ancestors_distance(pickup) ahr_pl ON (ahr_pl.id = adj.hold_pickup_lib)
                LEFT JOIN actor.org_unit_ancestors_distance(request) ahr_rl ON (ahr_rl.id = adj.hold_request_lib)
          WHERE (adj.circ_mod IS NULL OR adj.circ_mod = vacp_cm) AND
                (adj.item_circ_lib IS NULL OR adj.item_circ_lib = acp_cl.id) AND
                (adj.item_owning_lib IS NULL OR adj.item_owning_lib = acn_ol.id) AND
                (adj.copy_location IS NULL OR adj.copy_location = acl_ol.id) AND
                (adj.hold_pickup_lib IS NULL OR adj.hold_pickup_lib = ahr_pl.id) AND
                (adj.hold_request_lib IS NULL OR adj.hold_request_lib = ahr_rl.id) AND
                NOT absolute_adjustment AND
                COALESCE(acp_cl.id, acn_ol.id, acl_ol.id, ahr_pl.id, ahr_rl.id) IS NOT NULL
    LOOP
        baseline_prox := baseline_prox + aoupa.prox_adjustment;
    END LOOP;

    RETURN baseline_prox;
END;

Function: action.copy_related_hold_stats(copy_id bigint)

Returns: hold_stats

Language: PLPGSQL

DECLARE
    output          action.hold_stats%ROWTYPE;
    hold_count      INT := 0;
    copy_count      INT := 0;
    available_count INT := 0;
    hold_map_data   RECORD;
BEGIN

    output.hold_count := 0;
    output.copy_count := 0;
    output.available_count := 0;

    SELECT  COUNT( DISTINCT m.hold ) INTO hold_count
      FROM  action.hold_copy_map m
            JOIN action.hold_request h ON (m.hold = h.id)
      WHERE m.target_copy = copy_id
            AND NOT h.frozen;

    output.hold_count := hold_count;

    IF output.hold_count > 0 THEN
        FOR hold_map_data IN
            SELECT  DISTINCT m.target_copy,
                    acp.status
              FROM  action.hold_copy_map m
                    JOIN asset.copy acp ON (m.target_copy = acp.id)
                    JOIN action.hold_request h ON (m.hold = h.id)
              WHERE m.hold IN ( SELECT DISTINCT hold FROM action.hold_copy_map WHERE target_copy = copy_id ) AND NOT h.frozen
        LOOP
            output.copy_count := output.copy_count + 1;
            IF hold_map_data.status IN (0,7,12) THEN
                output.available_count := output.available_count + 1;
            END IF;
        END LOOP;
        output.total_copy_ratio = output.copy_count::FLOAT / output.hold_count::FLOAT;
        output.available_copy_ratio = output.available_count::FLOAT / output.hold_count::FLOAT;

    END IF;

    RETURN output;

END;

Function: action.copy_transit_is_unique()

Returns: trigger

Language: PLPGSQL

BEGIN
    PERFORM * FROM action.transit_copy 
        WHERE target_copy = NEW.target_copy 
              AND dest_recv_time IS NULL 
              AND cancel_time IS NULL;

    IF FOUND THEN
        RAISE EXCEPTION 'Copy id=% is already in transit', NEW.target_copy;
    END IF;
    RETURN NULL;
END;

Function: action.emergency_closing_stage_1(e_closing integer)

Returns: SET OF emergency_closing_stage_1_count

Language: PLPGSQL

DECLARE
    tmp     INT;
    touched action.emergency_closing_stage_1_count%ROWTYPE;
BEGIN
    -- First, gather circs
    INSERT INTO action.emergency_closing_circulation (emergency_closing, circulation)
        SELECT  e_closing,
                circ.id
          FROM  actor.org_unit_closed closing
                JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing)
                JOIN action.circulation circ ON (
                    circ.circ_lib = closing.org_unit
                    AND circ.due_date BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL)
                    AND circ.xact_finish IS NULL
                )
          WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_circulation t WHERE t.emergency_closing = e_closing AND t.circulation = circ.id);

    GET DIAGNOSTICS tmp = ROW_COUNT;
    touched.circulations := tmp;

    INSERT INTO action.emergency_closing_reservation (emergency_closing, reservation)
        SELECT  e_closing,
                res.id
          FROM  actor.org_unit_closed closing
                JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing)
                JOIN booking.reservation res ON (
                    res.pickup_lib = closing.org_unit
                    AND res.end_time BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL)
                )
          WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_reservation t WHERE t.emergency_closing = e_closing AND t.reservation = res.id);

    GET DIAGNOSTICS tmp = ROW_COUNT;
    touched.reservations := tmp;

    INSERT INTO action.emergency_closing_hold (emergency_closing, hold)
        SELECT  e_closing,
                hold.id
          FROM  actor.org_unit_closed closing
                JOIN action.emergency_closing ec ON (closing.emergency_closing = ec.id AND ec.id = e_closing)
                JOIN action.hold_request hold ON (
                    pickup_lib = closing.org_unit
                    AND hold.shelf_expire_time BETWEEN closing.close_start AND (closing.close_end + '1s'::INTERVAL)
                    AND hold.fulfillment_time IS NULL
                    AND hold.cancel_time IS NULL
                )
          WHERE NOT EXISTS (SELECT 1 FROM action.emergency_closing_hold t WHERE t.emergency_closing = e_closing AND t.hold = hold.id);

    GET DIAGNOSTICS tmp = ROW_COUNT;
    touched.holds := tmp;

    UPDATE  action.emergency_closing
      SET   process_start_time = NOW(),
            last_update_time = NOW()
      WHERE id = e_closing;

    RETURN NEXT touched;
END;

Function: action.emergency_closing_stage_2_circ(circ_closing_entry integer)

Returns: boolean

Language: PLPGSQL

DECLARE
    circ            action.circulation%ROWTYPE;
    e_closing       action.emergency_closing%ROWTYPE;
    e_c_circ        action.emergency_closing_circulation%ROWTYPE;
    closing         actor.org_unit_closed%ROWTYPE;
    adjacent        actor.org_unit_closed%ROWTYPE;
    bill            money.billing%ROWTYPE;
    last_bill       money.billing%ROWTYPE;
    day_number      INT;
    hoo_close       TIME WITHOUT TIME ZONE;
    plus_days       INT;
    avoid_negative  BOOL;
    extend_grace    BOOL;
    new_due_date    TEXT;
BEGIN
    -- Gather objects involved
    SELECT  * INTO e_c_circ
      FROM  action.emergency_closing_circulation
      WHERE id = circ_closing_entry;

    IF e_c_circ.process_time IS NOT NULL THEN
        -- Already processed ... moving on
        RETURN FALSE;
    END IF;

    SELECT  * INTO e_closing
      FROM  action.emergency_closing
      WHERE id = e_c_circ.emergency_closing;

    IF e_closing.process_start_time IS NULL THEN
        -- Huh... that's odd. And wrong.
        RETURN FALSE;
    END IF;

    SELECT  * INTO closing
      FROM  actor.org_unit_closed
      WHERE emergency_closing = e_closing.id;

    SELECT  * INTO circ
      FROM  action.circulation
      WHERE id = e_c_circ.circulation;

    -- Record the processing
    UPDATE  action.emergency_closing_circulation
      SET   original_due_date = circ.due_date,
            process_time = NOW()
      WHERE id = circ_closing_entry;

    UPDATE  action.emergency_closing
      SET   last_update_time = NOW()
      WHERE id = e_closing.id;

    SELECT value::BOOL INTO avoid_negative FROM actor.org_unit_ancestor_setting('bill.prohibit_negative_balance_on_overdues', circ.circ_lib);
    SELECT value::BOOL INTO extend_grace FROM actor.org_unit_ancestor_setting('circ.grace.extend', circ.circ_lib);

    new_due_date := evergreen.find_next_open_time( closing.org_unit, circ.due_date, EXTRACT(EPOCH FROM circ.duration)::INT % 86400 > 0 )::TEXT;
    UPDATE action.circulation SET due_date = new_due_date::TIMESTAMPTZ WHERE id = circ.id;

    -- Now, see if we need to get rid of some fines
    SELECT  * INTO last_bill
      FROM  money.billing b
      WHERE b.xact = circ.id
            AND NOT b.voided
            AND b.btype = 1
      ORDER BY billing_ts DESC
      LIMIT 1;

    FOR bill IN
        SELECT  *
          FROM  money.billing b
          WHERE b.xact = circ.id
                AND b.btype = 1
                AND NOT b.voided
                AND (
                    b.billing_ts BETWEEN closing.close_start AND new_due_date::TIMESTAMPTZ
                    OR (extend_grace AND last_bill.billing_ts <= new_due_date::TIMESTAMPTZ + circ.grace_period)
                )
                AND NOT EXISTS (SELECT 1 FROM money.account_adjustment a WHERE a.billing = b.id)
          ORDER BY billing_ts
    LOOP
        IF avoid_negative THEN
            PERFORM FROM money.materialized_billable_xact_summary WHERE id = circ.id AND balance_owed < bill.amount;
            EXIT WHEN FOUND; -- We can't go negative, and voiding this bill would do that...
        END IF;

        UPDATE  money.billing
          SET   voided = TRUE,
                void_time = NOW(),
                note = COALESCE(note,'') || ' :: Voided by emergency closing handler'
          WHERE id = bill.id;
    END LOOP;
    
    RETURN TRUE;
END;

Function: action.emergency_closing_stage_2_hold(hold_closing_entry integer)

Returns: boolean

Language: PLPGSQL

DECLARE
    hold        action.hold_request%ROWTYPE;
    e_closing   action.emergency_closing%ROWTYPE;
    e_c_hold    action.emergency_closing_hold%ROWTYPE;
    closing     actor.org_unit_closed%ROWTYPE;
    day_number  INT;
    hoo_close   TIME WITHOUT TIME ZONE;
    plus_days   INT;
BEGIN
    -- Gather objects involved
    SELECT  * INTO e_c_hold
      FROM  action.emergency_closing_hold
      WHERE id = hold_closing_entry;

    IF e_c_hold.process_time IS NOT NULL THEN
        -- Already processed ... moving on
        RETURN FALSE;
    END IF;

    SELECT  * INTO e_closing
      FROM  action.emergency_closing
      WHERE id = e_c_hold.emergency_closing;

    IF e_closing.process_start_time IS NULL THEN
        -- Huh... that's odd. And wrong.
        RETURN FALSE;
    END IF;

    SELECT  * INTO closing
      FROM  actor.org_unit_closed
      WHERE emergency_closing = e_closing.id;

    SELECT  * INTO hold
      FROM  action.hold_request h
      WHERE id = e_c_hold.hold;

    -- Record the processing
    UPDATE  action.emergency_closing_hold
      SET   original_shelf_expire_time = hold.shelf_expire_time,
            process_time = NOW()
      WHERE id = hold_closing_entry;

    UPDATE  action.emergency_closing
      SET   last_update_time = NOW()
      WHERE id = e_closing.id;

    UPDATE  action.hold_request
      SET   shelf_expire_time = evergreen.find_next_open_time(closing.org_unit, hold.shelf_expire_time, TRUE)
      WHERE id = hold.id;

    RETURN TRUE;
END;

Function: action.emergency_closing_stage_2_reservation(res_closing_entry integer)

Returns: boolean

Language: PLPGSQL

DECLARE
    res             booking.reservation%ROWTYPE;
    e_closing       action.emergency_closing%ROWTYPE;
    e_c_res         action.emergency_closing_reservation%ROWTYPE;
    closing         actor.org_unit_closed%ROWTYPE;
    adjacent        actor.org_unit_closed%ROWTYPE;
    bill            money.billing%ROWTYPE;
    day_number      INT;
    hoo_close       TIME WITHOUT TIME ZONE;
    plus_days       INT;
    avoid_negative  BOOL;
    new_due_date    TEXT;
BEGIN
    -- Gather objects involved
    SELECT  * INTO e_c_res
      FROM  action.emergency_closing_reservation
      WHERE id = res_closing_entry;

    IF e_c_res.process_time IS NOT NULL THEN
        -- Already processed ... moving on
        RETURN FALSE;
    END IF;

    SELECT  * INTO e_closing
      FROM  action.emergency_closing
      WHERE id = e_c_res.emergency_closing;

    IF e_closing.process_start_time IS NULL THEN
        -- Huh... that's odd. And wrong.
        RETURN FALSE;
    END IF;

    SELECT  * INTO closing
      FROM  actor.org_unit_closed
      WHERE emergency_closing = e_closing.id;

    SELECT  * INTO res
      FROM  booking.reservation
      WHERE id = e_c_res.reservation;

    IF res.pickup_lib IS NULL THEN -- Need to be far enough along to have a pickup lib
        RETURN FALSE;
    END IF;

    -- Record the processing
    UPDATE  action.emergency_closing_reservation
      SET   original_end_time = res.end_time,
            process_time = NOW()
      WHERE id = res_closing_entry;

    UPDATE  action.emergency_closing
      SET   last_update_time = NOW()
      WHERE id = e_closing.id;

    SELECT value::BOOL INTO avoid_negative FROM actor.org_unit_ancestor_setting('bill.prohibit_negative_balance_on_overdues', res.pickup_lib);

    new_due_date := evergreen.find_next_open_time( closing.org_unit, res.end_time, EXTRACT(EPOCH FROM res.booking_interval)::INT % 86400 > 0 )::TEXT;
    UPDATE booking.reservation SET end_time = new_due_date::TIMESTAMPTZ WHERE id = res.id;

    -- Now, see if we need to get rid of some fines
    FOR bill IN
        SELECT  *
          FROM  money.billing b
          WHERE b.xact = res.id
                AND b.btype = 1
                AND NOT b.voided
                AND b.billing_ts BETWEEN closing.close_start AND new_due_date::TIMESTAMPTZ
                AND NOT EXISTS (SELECT 1 FROM money.account_adjustment a WHERE a.billing = b.id)
    LOOP
        IF avoid_negative THEN
            PERFORM FROM money.materialized_billable_xact_summary WHERE id = res.id AND balance_owed < bill.amount;
            EXIT WHEN FOUND; -- We can't go negative, and voiding this bill would do that...
        END IF;

        UPDATE  money.billing
          SET   voided = TRUE,
                void_time = NOW(),
                note = COALESCE(note,'') || ' :: Voided by emergency closing handler'
          WHERE id = bill.id;
    END LOOP;
    
    RETURN TRUE;
END;

Function: action.fill_circ_copy_location()

Returns: trigger

Language: PLPGSQL

BEGIN
    SELECT INTO NEW.copy_location location FROM asset.copy WHERE id = NEW.target_copy;
    RETURN NEW;
END;

Function: action.find_circ_matrix_matchpoint(renewal integer, match_user bigint, match_item integer, context_ou boolean)

Returns: SET OF found_circ_matrix_matchpoint

Language: PLPGSQL

DECLARE
    item_object asset.copy%ROWTYPE;
    user_object actor.usr%ROWTYPE;
BEGIN
    SELECT INTO item_object * FROM asset.copy 	WHERE id = match_item;
    SELECT INTO user_object * FROM actor.usr	WHERE id = match_user;

    RETURN QUERY SELECT * FROM action.find_circ_matrix_matchpoint( context_ou, item_object, user_object, renewal );
END;

Function: action.find_circ_matrix_matchpoint(renewal integer, user_object asset.copy, item_object actor.usr, context_ou boolean)

Returns: found_circ_matrix_matchpoint

Language: PLPGSQL

DECLARE
    cn_object       asset.call_number%ROWTYPE;
    rec_descriptor  metabib.rec_descriptor%ROWTYPE;
    cur_matchpoint  config.circ_matrix_matchpoint%ROWTYPE;
    matchpoint      config.circ_matrix_matchpoint%ROWTYPE;
    weights         config.circ_matrix_weights%ROWTYPE;
    user_age        INTERVAL;
    my_item_age     INTERVAL;
    denominator     NUMERIC(6,2);
    row_list        INT[];
    result          action.found_circ_matrix_matchpoint;
BEGIN
    -- Assume failure
    result.success = false;

    -- Fetch useful data
    SELECT INTO cn_object       * FROM asset.call_number        WHERE id = item_object.call_number;
    SELECT INTO rec_descriptor  * FROM metabib.rec_descriptor   WHERE record = cn_object.record;

    -- Pre-generate this so we only calc it once
    IF user_object.dob IS NOT NULL THEN
        SELECT INTO user_age age(user_object.dob);
    END IF;

    -- Ditto
    SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));

    -- Grab the closest set circ weight setting.
    SELECT INTO weights cw.*
      FROM config.weight_assoc wa
           JOIN config.circ_matrix_weights cw ON (cw.id = wa.circ_weights)
           JOIN actor.org_unit_ancestors_distance( context_ou ) d ON (wa.org_unit = d.id)
      WHERE active
      ORDER BY d.distance
      LIMIT 1;

    -- No weights? Bad admin! Defaults to handle that anyway.
    IF weights.id IS NULL THEN
        weights.grp                 := 11.0;
        weights.org_unit            := 10.0;
        weights.circ_modifier       := 5.0;
        weights.copy_location       := 5.0;
        weights.marc_type           := 4.0;
        weights.marc_form           := 3.0;
        weights.marc_bib_level      := 2.0;
        weights.marc_vr_format      := 2.0;
        weights.copy_circ_lib       := 8.0;
        weights.copy_owning_lib     := 8.0;
        weights.user_home_ou        := 8.0;
        weights.ref_flag            := 1.0;
        weights.juvenile_flag       := 6.0;
        weights.is_renewal          := 7.0;
        weights.usr_age_lower_bound := 0.0;
        weights.usr_age_upper_bound := 0.0;
        weights.item_age            := 0.0;
    END IF;

    -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
    -- If you break your org tree with funky parenting this may be wrong
    -- Note: This CTE is duplicated in the find_hold_matrix_matchpoint function, and it may be a good idea to split it off to a function
    -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
    WITH all_distance(distance) AS (
            SELECT depth AS distance FROM actor.org_unit_type
        UNION
       	    SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
	)
    SELECT INTO denominator MAX(distance) + 1 FROM all_distance;

    -- Loop over all the potential matchpoints
    FOR cur_matchpoint IN
        SELECT m.*
          FROM  config.circ_matrix_matchpoint m
                /*LEFT*/ JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.grp = upgad.id
                /*LEFT*/ JOIN actor.org_unit_ancestors_distance( context_ou ) ctoua ON m.org_unit = ctoua.id
                LEFT JOIN actor.org_unit_ancestors_distance( cn_object.owning_lib ) cnoua ON m.copy_owning_lib = cnoua.id
                LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.copy_circ_lib = iooua.id
                LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
          WHERE m.active
                -- Permission Groups
             -- AND (m.grp                      IS NULL OR upgad.id IS NOT NULL) -- Optional Permission Group?
                -- Org Units
             -- AND (m.org_unit                 IS NULL OR ctoua.id IS NOT NULL) -- Optional Org Unit?
                AND (m.copy_owning_lib          IS NULL OR cnoua.id IS NOT NULL)
                AND (m.copy_circ_lib            IS NULL OR iooua.id IS NOT NULL)
                AND (m.user_home_ou             IS NULL OR uhoua.id IS NOT NULL)
                -- Circ Type
                AND (m.is_renewal               IS NULL OR m.is_renewal = renewal)
                -- Static User Checks
                AND (m.juvenile_flag            IS NULL OR m.juvenile_flag = user_object.juvenile)
                AND (m.usr_age_lower_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_lower_bound < user_age))
                AND (m.usr_age_upper_bound      IS NULL OR (user_age IS NOT NULL AND m.usr_age_upper_bound > user_age))
                -- Static Item Checks
                AND (m.circ_modifier            IS NULL OR m.circ_modifier = item_object.circ_modifier)
                AND (m.copy_location            IS NULL OR m.copy_location = item_object.location)
                AND (m.marc_type                IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
                AND (m.marc_form                IS NULL OR m.marc_form = rec_descriptor.item_form)
                AND (m.marc_bib_level           IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
                AND (m.marc_vr_format           IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
                AND (m.ref_flag                 IS NULL OR m.ref_flag = item_object.ref)
                AND (m.item_age                 IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
          ORDER BY
                -- Permission Groups
                CASE WHEN upgad.distance        IS NOT NULL THEN 2^(2*weights.grp - (upgad.distance/denominator)) ELSE 0.0 END +
                -- Org Units
                CASE WHEN ctoua.distance        IS NOT NULL THEN 2^(2*weights.org_unit - (ctoua.distance/denominator)) ELSE 0.0 END +
                CASE WHEN cnoua.distance        IS NOT NULL THEN 2^(2*weights.copy_owning_lib - (cnoua.distance/denominator)) ELSE 0.0 END +
                CASE WHEN iooua.distance        IS NOT NULL THEN 2^(2*weights.copy_circ_lib - (iooua.distance/denominator)) ELSE 0.0 END +
                CASE WHEN uhoua.distance        IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
                -- Circ Type                    -- Note: 4^x is equiv to 2^(2*x)
                CASE WHEN m.is_renewal          IS NOT NULL THEN 4^weights.is_renewal ELSE 0.0 END +
                -- Static User Checks
                CASE WHEN m.juvenile_flag       IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
                CASE WHEN m.usr_age_lower_bound IS NOT NULL THEN 4^weights.usr_age_lower_bound ELSE 0.0 END +
                CASE WHEN m.usr_age_upper_bound IS NOT NULL THEN 4^weights.usr_age_upper_bound ELSE 0.0 END +
                -- Static Item Checks
                CASE WHEN m.circ_modifier       IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
                CASE WHEN m.copy_location       IS NOT NULL THEN 4^weights.copy_location ELSE 0.0 END +
                CASE WHEN m.marc_type           IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
                CASE WHEN m.marc_form           IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
                CASE WHEN m.marc_vr_format      IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
                CASE WHEN m.ref_flag            IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
                -- Item age has a slight adjustment to weight based on value.
                -- This should ensure that a shorter age limit comes first when all else is equal.
                -- NOTE: This assumes that intervals will normally be in days.
                CASE WHEN m.item_age            IS NOT NULL THEN 4^weights.item_age - 1 + 86400/EXTRACT(EPOCH FROM m.item_age) ELSE 0.0 END DESC,
                -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
                -- This prevents "we changed the table order by updating a rule, and we started getting different results"
                m.id LOOP

        -- Record the full matching row list
        row_list := row_list || cur_matchpoint.id;

        -- No matchpoint yet?
        IF matchpoint.id IS NULL THEN
            -- Take the entire matchpoint as a starting point
            matchpoint := cur_matchpoint;
            CONTINUE; -- No need to look at this row any more.
        END IF;

        -- Incomplete matchpoint?
        IF matchpoint.circulate IS NULL THEN
            matchpoint.circulate := cur_matchpoint.circulate;
        END IF;
        IF matchpoint.duration_rule IS NULL THEN
            matchpoint.duration_rule := cur_matchpoint.duration_rule;
        END IF;
        IF matchpoint.recurring_fine_rule IS NULL THEN
            matchpoint.recurring_fine_rule := cur_matchpoint.recurring_fine_rule;
        END IF;
        IF matchpoint.max_fine_rule IS NULL THEN
            matchpoint.max_fine_rule := cur_matchpoint.max_fine_rule;
        END IF;
        IF matchpoint.hard_due_date IS NULL THEN
            matchpoint.hard_due_date := cur_matchpoint.hard_due_date;
        END IF;
        IF matchpoint.total_copy_hold_ratio IS NULL THEN
            matchpoint.total_copy_hold_ratio := cur_matchpoint.total_copy_hold_ratio;
        END IF;
        IF matchpoint.available_copy_hold_ratio IS NULL THEN
            matchpoint.available_copy_hold_ratio := cur_matchpoint.available_copy_hold_ratio;
        END IF;
        IF matchpoint.renewals IS NULL THEN
            matchpoint.renewals := cur_matchpoint.renewals;
        END IF;
        IF matchpoint.grace_period IS NULL THEN
            matchpoint.grace_period := cur_matchpoint.grace_period;
        END IF;
    END LOOP;

    -- Check required fields
    IF matchpoint.circulate             IS NOT NULL AND
       matchpoint.duration_rule         IS NOT NULL AND
       matchpoint.recurring_fine_rule   IS NOT NULL AND
       matchpoint.max_fine_rule         IS NOT NULL THEN
        -- All there? We have a completed match.
        result.success := true;
    END IF;

    -- Include the assembled matchpoint, even if it isn't complete
    result.matchpoint := matchpoint;

    -- Include (for debugging) the full list of matching rows
    result.buildrows := row_list;

    -- Hand the result back to caller
    RETURN result;
END;

Function: action.find_hold_matrix_matchpoint(match_requestor integer, match_user integer, match_item bigint, request_ou integer, pickup_ou integer)

Returns: integer

Language: PLPGSQL

DECLARE
    requestor_object    actor.usr%ROWTYPE;
    user_object         actor.usr%ROWTYPE;
    item_object         asset.copy%ROWTYPE;
    item_cn_object      asset.call_number%ROWTYPE;
    my_item_age         INTERVAL;
    rec_descriptor      metabib.rec_descriptor%ROWTYPE;
    matchpoint          config.hold_matrix_matchpoint%ROWTYPE;
    weights             config.hold_matrix_weights%ROWTYPE;
    denominator         NUMERIC(6,2);
    v_pickup_ou         ALIAS FOR pickup_ou;
    v_request_ou         ALIAS FOR request_ou;
BEGIN
    SELECT INTO user_object         * FROM actor.usr                WHERE id = match_user;
    SELECT INTO requestor_object    * FROM actor.usr                WHERE id = match_requestor;
    SELECT INTO item_object         * FROM asset.copy               WHERE id = match_item;
    SELECT INTO item_cn_object      * FROM asset.call_number        WHERE id = item_object.call_number;
    SELECT INTO rec_descriptor      * FROM metabib.rec_descriptor   WHERE record = item_cn_object.record;

    SELECT INTO my_item_age age(coalesce(item_object.active_date, now()));

    -- The item's owner should probably be the one determining if the item is holdable
    -- How to decide that is debatable. Decided to default to the circ library (where the item lives)
    -- This flag will allow for setting it to the owning library (where the call number "lives")
    PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.weight_owner_not_circ' AND enabled;

    -- Grab the closest set circ weight setting.
    IF NOT FOUND THEN
        -- Default to circ library
        SELECT INTO weights hw.*
          FROM config.weight_assoc wa
               JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
               JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) d ON (wa.org_unit = d.id)
          WHERE active
          ORDER BY d.distance
          LIMIT 1;
    ELSE
        -- Flag is set, use owning library
        SELECT INTO weights hw.*
          FROM config.weight_assoc wa
               JOIN config.hold_matrix_weights hw ON (hw.id = wa.hold_weights)
               JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) d ON (wa.org_unit = d.id)
          WHERE active
          ORDER BY d.distance
          LIMIT 1;
    END IF;

    -- No weights? Bad admin! Defaults to handle that anyway.
    IF weights.id IS NULL THEN
        weights.user_home_ou    := 5.0;
        weights.request_ou      := 5.0;
        weights.pickup_ou       := 5.0;
        weights.item_owning_ou  := 5.0;
        weights.item_circ_ou    := 5.0;
        weights.usr_grp         := 7.0;
        weights.requestor_grp   := 8.0;
        weights.circ_modifier   := 4.0;
        weights.marc_type       := 3.0;
        weights.marc_form       := 2.0;
        weights.marc_bib_level  := 1.0;
        weights.marc_vr_format  := 1.0;
        weights.juvenile_flag   := 4.0;
        weights.ref_flag        := 0.0;
        weights.item_age        := 0.0;
    END IF;

    -- Determine the max (expected) depth (+1) of the org tree and max depth of the permisson tree
    -- If you break your org tree with funky parenting this may be wrong
    -- Note: This CTE is duplicated in the find_circ_matrix_matchpoint function, and it may be a good idea to split it off to a function
    -- We use one denominator for all tree-based checks for when permission groups and org units have the same weighting
    WITH all_distance(distance) AS (
            SELECT depth AS distance FROM actor.org_unit_type
        UNION
            SELECT distance AS distance FROM permission.grp_ancestors_distance((SELECT id FROM permission.grp_tree WHERE parent IS NULL))
	)
    SELECT INTO denominator MAX(distance) + 1 FROM all_distance;

    -- To ATTEMPT to make this work like it used to, make it reverse the user/requestor profile ids.
    -- This may be better implemented as part of the upgrade script?
    -- Set usr_grp = requestor_grp, requestor_grp = 1 or something when this flag is already set
    -- Then remove this flag, of course.
    PERFORM * FROM config.internal_flag WHERE name = 'circ.holds.usr_not_requestor' AND enabled;

    IF FOUND THEN
        -- Note: This, to me, is REALLY hacky. I put it in anyway.
        -- If you can't tell, this is a single call swap on two variables.
        SELECT INTO user_object.profile, requestor_object.profile
                    requestor_object.profile, user_object.profile;
    END IF;

    -- Select the winning matchpoint into the matchpoint variable for returning
    SELECT INTO matchpoint m.*
      FROM  config.hold_matrix_matchpoint m
            /*LEFT*/ JOIN permission.grp_ancestors_distance( requestor_object.profile ) rpgad ON m.requestor_grp = rpgad.id
            LEFT JOIN permission.grp_ancestors_distance( user_object.profile ) upgad ON m.usr_grp = upgad.id
            LEFT JOIN actor.org_unit_ancestors_distance( v_pickup_ou ) puoua ON m.pickup_ou = puoua.id
            LEFT JOIN actor.org_unit_ancestors_distance( v_request_ou ) rqoua ON m.request_ou = rqoua.id
            LEFT JOIN actor.org_unit_ancestors_distance( item_cn_object.owning_lib ) cnoua ON m.item_owning_ou = cnoua.id
            LEFT JOIN actor.org_unit_ancestors_distance( item_object.circ_lib ) iooua ON m.item_circ_ou = iooua.id
            LEFT JOIN actor.org_unit_ancestors_distance( user_object.home_ou  ) uhoua ON m.user_home_ou = uhoua.id
      WHERE m.active
            -- Permission Groups
         -- AND (m.requestor_grp        IS NULL OR upgad.id IS NOT NULL) -- Optional Requestor Group?
            AND (m.usr_grp              IS NULL OR upgad.id IS NOT NULL)
            -- Org Units
            AND (m.pickup_ou            IS NULL OR (puoua.id IS NOT NULL AND (puoua.distance = 0 OR NOT m.strict_ou_match)))
            AND (m.request_ou           IS NULL OR (rqoua.id IS NOT NULL AND (rqoua.distance = 0 OR NOT m.strict_ou_match)))
            AND (m.item_owning_ou       IS NULL OR (cnoua.id IS NOT NULL AND (cnoua.distance = 0 OR NOT m.strict_ou_match)))
            AND (m.item_circ_ou         IS NULL OR (iooua.id IS NOT NULL AND (iooua.distance = 0 OR NOT m.strict_ou_match)))
            AND (m.user_home_ou         IS NULL OR (uhoua.id IS NOT NULL AND (uhoua.distance = 0 OR NOT m.strict_ou_match)))
            -- Static User Checks
            AND (m.juvenile_flag        IS NULL OR m.juvenile_flag = user_object.juvenile)
            -- Static Item Checks
            AND (m.circ_modifier        IS NULL OR m.circ_modifier = item_object.circ_modifier)
            AND (m.marc_type            IS NULL OR m.marc_type = COALESCE(item_object.circ_as_type, rec_descriptor.item_type))
            AND (m.marc_form            IS NULL OR m.marc_form = rec_descriptor.item_form)
            AND (m.marc_bib_level       IS NULL OR m.marc_bib_level = rec_descriptor.bib_level)
            AND (m.marc_vr_format       IS NULL OR m.marc_vr_format = rec_descriptor.vr_format)
            AND (m.ref_flag             IS NULL OR m.ref_flag = item_object.ref)
            AND (m.item_age             IS NULL OR (my_item_age IS NOT NULL AND m.item_age > my_item_age))
      ORDER BY
            -- Permission Groups
            CASE WHEN rpgad.distance    IS NOT NULL THEN 2^(2*weights.requestor_grp - (rpgad.distance/denominator)) ELSE 0.0 END +
            CASE WHEN upgad.distance    IS NOT NULL THEN 2^(2*weights.usr_grp - (upgad.distance/denominator)) ELSE 0.0 END +
            -- Org Units
            CASE WHEN puoua.distance    IS NOT NULL THEN 2^(2*weights.pickup_ou - (puoua.distance/denominator)) ELSE 0.0 END +
            CASE WHEN rqoua.distance    IS NOT NULL THEN 2^(2*weights.request_ou - (rqoua.distance/denominator)) ELSE 0.0 END +
            CASE WHEN cnoua.distance    IS NOT NULL THEN 2^(2*weights.item_owning_ou - (cnoua.distance/denominator)) ELSE 0.0 END +
            CASE WHEN iooua.distance    IS NOT NULL THEN 2^(2*weights.item_circ_ou - (iooua.distance/denominator)) ELSE 0.0 END +
            CASE WHEN uhoua.distance    IS NOT NULL THEN 2^(2*weights.user_home_ou - (uhoua.distance/denominator)) ELSE 0.0 END +
            -- Static User Checks       -- Note: 4^x is equiv to 2^(2*x)
            CASE WHEN m.juvenile_flag   IS NOT NULL THEN 4^weights.juvenile_flag ELSE 0.0 END +
            -- Static Item Checks
            CASE WHEN m.circ_modifier   IS NOT NULL THEN 4^weights.circ_modifier ELSE 0.0 END +
            CASE WHEN m.marc_type       IS NOT NULL THEN 4^weights.marc_type ELSE 0.0 END +
            CASE WHEN m.marc_form       IS NOT NULL THEN 4^weights.marc_form ELSE 0.0 END +
            CASE WHEN m.marc_vr_format  IS NOT NULL THEN 4^weights.marc_vr_format ELSE 0.0 END +
            CASE WHEN m.ref_flag        IS NOT NULL THEN 4^weights.ref_flag ELSE 0.0 END +
            -- Item age has a slight adjustment to weight based on value.
            -- This should ensure that a shorter age limit comes first when all else is equal.
            -- NOTE: This assumes that intervals will normally be in days.
            CASE WHEN m.item_age            IS NOT NULL THEN 4^weights.item_age - 86400/EXTRACT(EPOCH FROM m.item_age) ELSE 0.0 END DESC,
            -- Final sort on id, so that if two rules have the same sorting in the previous sort they have a defined order
            -- This prevents "we changed the table order by updating a rule, and we started getting different results"
            m.id;

    -- Return just the ID for now
    RETURN matchpoint.id;
END;

Function: action.hold_copy_calculated_proximity(copy_context_ou integer, acp_id bigint, ahr_id integer)

Returns: numeric

Language: PLPGSQL

DECLARE
    ahr  action.hold_request%ROWTYPE;
    acp  asset.copy%ROWTYPE;
    acn  asset.call_number%ROWTYPE;
    acl  asset.copy_location%ROWTYPE;

    prox NUMERIC;
BEGIN

    SELECT * INTO ahr FROM action.hold_request WHERE id = ahr_id;
    SELECT * INTO acp FROM asset.copy WHERE id = acp_id;
    SELECT * INTO acn FROM asset.call_number WHERE id = acp.call_number;
    SELECT * INTO acl FROM asset.copy_location WHERE id = acp.location;

    IF copy_context_ou IS NULL THEN
        copy_context_ou := acp.circ_lib;
    END IF;

    SELECT action.copy_calculated_proximity(
        ahr.pickup_lib,
        ahr.request_lib,
        copy_context_ou,
        acp.circ_modifier,
        acn.owning_lib,
        acl.owning_lib
    ) INTO prox;

    RETURN prox;
END;

Function: action.hold_copy_calculated_proximity_update()

Returns: trigger

Language: PLPGSQL

BEGIN
    NEW.proximity := action.hold_copy_calculated_proximity(NEW.hold,NEW.target_copy);
    RETURN NEW;
END;

Function: action.hold_request_clear_map()

Returns: trigger

Language: PLPGSQL

BEGIN
  DELETE FROM action.hold_copy_map WHERE hold = NEW.id;
  RETURN NEW;
END;

Function: action.hold_request_permit_test(match_requestor integer, match_user integer, match_item bigint, request_ou integer, pickup_ou integer)

Returns: SET OF matrix_test_result

Language: SQL

    SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, FALSE );

Function: action.hold_request_permit_test(retargetting integer, match_requestor integer, match_user bigint, match_item integer, request_ou integer, pickup_ou boolean)

Returns: SET OF matrix_test_result

Language: PLPGSQL

DECLARE
    matchpoint_id        INT;
    user_object        actor.usr%ROWTYPE;
    age_protect_object    config.rule_age_hold_protect%ROWTYPE;
    standing_penalty    config.standing_penalty%ROWTYPE;
    transit_range_ou_type    actor.org_unit_type%ROWTYPE;
    transit_source        actor.org_unit%ROWTYPE;
    item_object        asset.copy%ROWTYPE;
    item_cn_object     asset.call_number%ROWTYPE;
    item_status_object  config.copy_status%ROWTYPE;
    item_location_object    asset.copy_location%ROWTYPE;
    ou_skip              actor.org_unit_setting%ROWTYPE;
    calc_age_prox        actor.org_unit_setting%ROWTYPE;
    result            action.matrix_test_result;
    hold_test        config.hold_matrix_matchpoint%ROWTYPE;
    use_active_date   TEXT;
    prox_ou           INT;
    age_protect_date  TIMESTAMP WITH TIME ZONE;
    hold_count        INT;
    hold_transit_prox    NUMERIC;
    frozen_hold_count    INT;
    context_org_list    INT[];
    done            BOOL := FALSE;
    hold_penalty TEXT;
    v_pickup_ou ALIAS FOR pickup_ou;
    v_request_ou ALIAS FOR request_ou;
    item_prox INT;
    pickup_prox INT;
BEGIN
    SELECT INTO user_object * FROM actor.usr WHERE id = match_user;
    SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( v_pickup_ou );

    result.success := TRUE;

    -- The HOLD penalty block only applies to new holds.
    -- The CAPTURE penalty block applies to existing holds.
    hold_penalty := 'HOLD';
    IF retargetting THEN
        hold_penalty := 'CAPTURE';
    END IF;

    -- Fail if we couldn't find a user
    IF user_object.id IS NULL THEN
        result.fail_part := 'no_user';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
        RETURN;
    END IF;

    SELECT INTO item_object * FROM asset.copy WHERE id = match_item;

    -- Fail if we couldn't find a copy
    IF item_object.id IS NULL THEN
        result.fail_part := 'no_item';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
        RETURN;
    END IF;

    SELECT INTO matchpoint_id action.find_hold_matrix_matchpoint(v_pickup_ou, v_request_ou, match_item, match_user, match_requestor);
    result.matchpoint := matchpoint_id;

    SELECT INTO ou_skip * FROM actor.org_unit_setting WHERE name = 'circ.holds.target_skip_me' AND org_unit = item_object.circ_lib;

    -- Fail if the circ_lib for the item has circ.holds.target_skip_me set to true
    IF ou_skip.id IS NOT NULL AND ou_skip.value = 'true' THEN
        result.fail_part := 'circ.holds.target_skip_me';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
        RETURN;
    END IF;

    -- Fail if user is barred
    IF user_object.barred IS TRUE THEN
        result.fail_part := 'actor.usr.barred';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
        RETURN;
    END IF;

    SELECT INTO item_cn_object * FROM asset.call_number WHERE id = item_object.call_number;
    SELECT INTO item_status_object * FROM config.copy_status WHERE id = item_object.status;
    SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;

    -- Fail if we couldn't find any matchpoint (requires a default)
    IF matchpoint_id IS NULL THEN
        result.fail_part := 'no_matchpoint';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
        RETURN;
    END IF;

    SELECT INTO hold_test * FROM config.hold_matrix_matchpoint WHERE id = matchpoint_id;

    IF hold_test.holdable IS FALSE THEN
        result.fail_part := 'config.hold_matrix_test.holdable';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END IF;

    IF item_object.holdable IS FALSE THEN
        result.fail_part := 'item.holdable';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END IF;

    IF item_status_object.holdable IS FALSE THEN
        result.fail_part := 'status.holdable';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END IF;

    IF item_location_object.holdable IS FALSE THEN
        result.fail_part := 'location.holdable';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END IF;

    IF hold_test.transit_range IS NOT NULL THEN
        SELECT INTO transit_range_ou_type * FROM actor.org_unit_type WHERE id = hold_test.transit_range;
        IF hold_test.distance_is_from_owner THEN
            SELECT INTO transit_source ou.* FROM actor.org_unit ou JOIN asset.call_number cn ON (cn.owning_lib = ou.id) WHERE cn.id = item_object.call_number;
        ELSE
            SELECT INTO transit_source * FROM actor.org_unit WHERE id = item_object.circ_lib;
        END IF;

        PERFORM * FROM actor.org_unit_descendants( transit_source.id, transit_range_ou_type.depth ) WHERE id = v_pickup_ou;

        IF NOT FOUND THEN
            result.fail_part := 'transit_range';
            result.success := FALSE;
            done := TRUE;
            RETURN NEXT result;
        END IF;
    END IF;

    -- Proximity of user's home_ou to the pickup_lib to see if penalty should be ignored.
    SELECT INTO pickup_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = v_pickup_ou;
    -- Proximity of user's home_ou to the items' lib to see if penalty should be ignored.
    IF hold_test.distance_is_from_owner THEN
        SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_cn_object.owning_lib;
    ELSE
        SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_object.circ_lib;
    END IF;

    FOR standing_penalty IN
        SELECT  DISTINCT csp.*
          FROM  actor.usr_standing_penalty usp
                JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
          WHERE usr = match_user
                AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
                AND (usp.stop_date IS NULL or usp.stop_date > NOW())
                AND (csp.ignore_proximity IS NULL OR csp.ignore_proximity < item_prox
                     OR csp.ignore_proximity < pickup_prox)
                AND csp.block_list LIKE '%' || hold_penalty || '%' LOOP

        result.fail_part := standing_penalty.name;
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END LOOP;

    IF hold_test.stop_blocked_user IS TRUE THEN
        FOR standing_penalty IN
            SELECT  DISTINCT csp.*
              FROM  actor.usr_standing_penalty usp
                    JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
              WHERE usr = match_user
                    AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
                    AND (usp.stop_date IS NULL or usp.stop_date > NOW())
                    AND csp.block_list LIKE '%CIRC%' LOOP

            result.fail_part := standing_penalty.name;
            result.success := FALSE;
            done := TRUE;
            RETURN NEXT result;
        END LOOP;
    END IF;

    IF hold_test.max_holds IS NOT NULL AND NOT retargetting THEN
        SELECT    INTO hold_count COUNT(*)
          FROM    action.hold_request
          WHERE    usr = match_user
            AND fulfillment_time IS NULL
            AND cancel_time IS NULL
            AND CASE WHEN hold_test.include_frozen_holds THEN TRUE ELSE frozen IS FALSE END;

        IF hold_count >= hold_test.max_holds THEN
            result.fail_part := 'config.hold_matrix_test.max_holds';
            result.success := FALSE;
            done := TRUE;
            RETURN NEXT result;
        END IF;
    END IF;

    IF item_object.age_protect IS NOT NULL THEN
        SELECT INTO age_protect_object * FROM config.rule_age_hold_protect WHERE id = item_object.age_protect;
        IF hold_test.distance_is_from_owner THEN
            SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_cn_object.owning_lib);
        ELSE
            SELECT INTO use_active_date value FROM actor.org_unit_ancestor_setting('circ.holds.age_protect.active_date', item_object.circ_lib);
        END IF;
        IF use_active_date = 'true' THEN
            age_protect_date := COALESCE(item_object.active_date, NOW());
        ELSE
            age_protect_date := item_object.create_date;
        END IF;
        IF age_protect_date + age_protect_object.age > NOW() THEN
            SELECT INTO calc_age_prox * FROM actor.org_unit_setting WHERE name = 'circ.holds.calculated_age_proximity' AND org_unit = item_object.circ_lib;
            IF hold_test.distance_is_from_owner THEN
                prox_ou := item_cn_object.owning_lib;
            ELSE
                prox_ou := item_object.circ_lib;
            END IF;
            IF calc_age_prox.id IS NOT NULL AND calc_age_prox.value = 'true' THEN
                SELECT INTO hold_transit_prox action.copy_calculated_proximity(
                    v_pickup_ou,
                    v_request_ou,
                    prox_ou,
                    item_object.circ_modifier,
                    item_cn_object.owning_lib,
                    item_location_object.owning_lib
                );
            ELSE
                SELECT INTO hold_transit_prox prox::NUMERIC FROM actor.org_unit_proximity WHERE from_org = prox_ou AND to_org = v_pickup_ou;
            END IF;

            IF hold_transit_prox > age_protect_object.prox::NUMERIC THEN
                result.fail_part := 'config.rule_age_hold_protect.prox';
                result.success := FALSE;
                done := TRUE;
                RETURN NEXT result;
            END IF;
        END IF;
    END IF;

    IF NOT done THEN
        RETURN NEXT result;
    END IF;

    RETURN;
END;

Function: action.hold_request_regen_copy_maps(copy_ids integer, hold_id integer[])

Returns: void

Language: SQL

    DELETE FROM action.hold_copy_map WHERE hold = $1;
    INSERT INTO action.hold_copy_map (hold, target_copy) SELECT DISTINCT $1, UNNEST($2);

Function: action.hold_retarget_permit_test(match_requestor integer, match_user integer, match_item bigint, request_ou integer, pickup_ou integer)

Returns: SET OF matrix_test_result

Language: SQL

    SELECT * FROM action.hold_request_permit_test( $1, $2, $3, $4, $5, TRUE );

Function: action.item_user_circ_test(integer, bigint, integer)

Returns: SET OF circ_matrix_test_result

Language: SQL

    SELECT * FROM action.item_user_circ_test( $1, $2, $3, FALSE );

Function: action.item_user_circ_test(renewal integer, match_user bigint, match_item integer, circ_ou boolean)

Returns: SET OF circ_matrix_test_result

Language: PLPGSQL

DECLARE
    user_object             actor.usr%ROWTYPE;
    standing_penalty        config.standing_penalty%ROWTYPE;
    item_object             asset.copy%ROWTYPE;
    item_status_object      config.copy_status%ROWTYPE;
    item_location_object    asset.copy_location%ROWTYPE;
    result                  action.circ_matrix_test_result;
    circ_test               action.found_circ_matrix_matchpoint;
    circ_matchpoint         config.circ_matrix_matchpoint%ROWTYPE;
    circ_limit_set          config.circ_limit_set%ROWTYPE;
    hold_ratio              action.hold_stats%ROWTYPE;
    penalty_type            TEXT;
    items_out               INT;
    context_org_list        INT[];
    permit_renew            TEXT;
    done                    BOOL := FALSE;
    item_prox               INT;
    home_prox               INT;
BEGIN
    -- Assume success unless we hit a failure condition
    result.success := TRUE;

    -- Need user info to look up matchpoints
    SELECT INTO user_object * FROM actor.usr WHERE id = match_user AND NOT deleted;

    -- (Insta)Fail if we couldn't find the user
    IF user_object.id IS NULL THEN
        result.fail_part := 'no_user';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
        RETURN;
    END IF;

    -- Need item info to look up matchpoints
    SELECT INTO item_object * FROM asset.copy WHERE id = match_item AND NOT deleted;

    -- (Insta)Fail if we couldn't find the item 
    IF item_object.id IS NULL THEN
        result.fail_part := 'no_item';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
        RETURN;
    END IF;

    SELECT INTO circ_test * FROM action.find_circ_matrix_matchpoint(circ_ou, item_object, user_object, renewal);

    circ_matchpoint             := circ_test.matchpoint;
    result.matchpoint           := circ_matchpoint.id;
    result.circulate            := circ_matchpoint.circulate;
    result.duration_rule        := circ_matchpoint.duration_rule;
    result.recurring_fine_rule  := circ_matchpoint.recurring_fine_rule;
    result.max_fine_rule        := circ_matchpoint.max_fine_rule;
    result.hard_due_date        := circ_matchpoint.hard_due_date;
    result.renewals             := circ_matchpoint.renewals;
    result.grace_period         := circ_matchpoint.grace_period;
    result.buildrows            := circ_test.buildrows;

    -- (Insta)Fail if we couldn't find a matchpoint
    IF circ_test.success = false THEN
        result.fail_part := 'no_matchpoint';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
        RETURN;
    END IF;

    -- All failures before this point are non-recoverable
    -- Below this point are possibly overridable failures

    -- Fail if the user is barred
    IF user_object.barred IS TRUE THEN
        result.fail_part := 'actor.usr.barred';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END IF;

    -- Fail if the item can't circulate
    IF item_object.circulate IS FALSE THEN
        result.fail_part := 'asset.copy.circulate';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END IF;

    -- Fail if the item isn't in a circulateable status on a non-renewal
    IF NOT renewal AND item_object.status <> 8 AND item_object.status NOT IN (
        (SELECT id FROM config.copy_status WHERE is_available) ) THEN 
        result.fail_part := 'asset.copy.status';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    -- Alternately, fail if the item isn't checked out on a renewal
    ELSIF renewal AND item_object.status <> 1 THEN
        result.fail_part := 'asset.copy.status';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END IF;

    -- Fail if the item can't circulate because of the shelving location
    SELECT INTO item_location_object * FROM asset.copy_location WHERE id = item_object.location;
    IF item_location_object.circulate IS FALSE THEN
        result.fail_part := 'asset.copy_location.circulate';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END IF;

    -- Use Circ OU for penalties and such
    SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( circ_ou );

    -- Proximity of user's home_ou to circ_ou to see if penalties should be ignored.
    SELECT INTO home_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = circ_ou;

    -- Proximity of user's home_ou to item circ_lib to see if penalties should be ignored.
    SELECT INTO item_prox prox FROM actor.org_unit_proximity WHERE from_org = user_object.home_ou AND to_org = item_object.circ_lib;

    IF renewal THEN
        penalty_type = '%RENEW%';
    ELSE
        penalty_type = '%CIRC%';
    END IF;

    FOR standing_penalty IN
        SELECT  DISTINCT csp.*
          FROM  actor.usr_standing_penalty usp
                JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
          WHERE usr = match_user
                AND usp.org_unit IN ( SELECT * FROM unnest(context_org_list) )
                AND (usp.stop_date IS NULL or usp.stop_date > NOW())
                AND (csp.ignore_proximity IS NULL
                     OR csp.ignore_proximity < home_prox
                     OR csp.ignore_proximity < item_prox)
                AND csp.block_list LIKE penalty_type LOOP
        -- override PATRON_EXCEEDS_FINES penalty for renewals based on org setting
        IF renewal AND standing_penalty.name = 'PATRON_EXCEEDS_FINES' THEN
            SELECT INTO permit_renew value FROM actor.org_unit_ancestor_setting('circ.permit_renew_when_exceeds_fines', circ_ou);
            IF permit_renew IS NOT NULL AND permit_renew ILIKE 'true' THEN
                CONTINUE;
            END IF;
        END IF;

        result.fail_part := standing_penalty.name;
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END LOOP;

    -- Fail if the test is set to hard non-circulating
    IF circ_matchpoint.circulate IS FALSE THEN
        result.fail_part := 'config.circ_matrix_test.circulate';
        result.success := FALSE;
        done := TRUE;
        RETURN NEXT result;
    END IF;

    -- Fail if the total copy-hold ratio is too low
    IF circ_matchpoint.total_copy_hold_ratio IS NOT NULL THEN
        SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
        IF hold_ratio.total_copy_ratio IS NOT NULL AND hold_ratio.total_copy_ratio < circ_matchpoint.total_copy_hold_ratio THEN
            result.fail_part := 'config.circ_matrix_test.total_copy_hold_ratio';
            result.success := FALSE;
            done := TRUE;
            RETURN NEXT result;
        END IF;
    END IF;

    -- Fail if the available copy-hold ratio is too low
    IF circ_matchpoint.available_copy_hold_ratio IS NOT NULL THEN
        IF hold_ratio.hold_count IS NULL THEN
            SELECT INTO hold_ratio * FROM action.copy_related_hold_stats(match_item);
        END IF;
        IF hold_ratio.available_copy_ratio IS NOT NULL AND hold_ratio.available_copy_ratio < circ_matchpoint.available_copy_hold_ratio THEN
            result.fail_part := 'config.circ_matrix_test.available_copy_hold_ratio';
            result.success := FALSE;
            done := TRUE;
            RETURN NEXT result;
        END IF;
    END IF;

    -- Fail if the user has too many items out by defined limit sets
    FOR circ_limit_set IN SELECT ccls.* FROM config.circ_limit_set ccls
      JOIN config.circ_matrix_limit_set_map ccmlsm ON ccmlsm.limit_set = ccls.id
      WHERE ccmlsm.active AND ( ccmlsm.matchpoint = circ_matchpoint.id OR
        ( ccmlsm.matchpoint IN (SELECT * FROM unnest(result.buildrows)) AND ccmlsm.fallthrough )
        ) LOOP
            IF circ_limit_set.items_out > 0 AND NOT renewal THEN
                SELECT INTO context_org_list ARRAY_AGG(aou.id)
                  FROM actor.org_unit_full_path( circ_ou ) aou
                    JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
                  WHERE aout.depth >= circ_limit_set.depth;
                IF circ_limit_set.global THEN
                    WITH RECURSIVE descendant_depth AS (
                        SELECT  ou.id,
                            ou.parent_ou
                        FROM  actor.org_unit ou
                        WHERE ou.id IN (SELECT * FROM unnest(context_org_list))
                            UNION
                        SELECT  ou.id,
                            ou.parent_ou
                        FROM  actor.org_unit ou
                            JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
                    ) SELECT INTO context_org_list ARRAY_AGG(ou.id) FROM actor.org_unit ou JOIN descendant_depth USING (id);
                END IF;
                SELECT INTO items_out COUNT(DISTINCT circ.id)
                  FROM action.circulation circ
                    JOIN asset.copy copy ON (copy.id = circ.target_copy)
                    LEFT JOIN action.circulation_limit_group_map aclgm ON (circ.id = aclgm.circ)
                  WHERE circ.usr = match_user
                    AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
                    AND circ.checkin_time IS NULL
                    AND circ.xact_finish IS NULL
                    AND (circ.stop_fines IN ('MAXFINES','LONGOVERDUE') OR circ.stop_fines IS NULL)
                    AND (copy.circ_modifier IN (SELECT circ_mod FROM config.circ_limit_set_circ_mod_map WHERE limit_set = circ_limit_set.id)
                        OR copy.location IN (SELECT copy_loc FROM config.circ_limit_set_copy_loc_map WHERE limit_set = circ_limit_set.id)
                        OR aclgm.limit_group IN (SELECT limit_group FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id)
                    );
                IF items_out >= circ_limit_set.items_out THEN
                    result.fail_part := 'config.circ_matrix_circ_mod_test';
                    result.success := FALSE;
                    done := TRUE;
                    RETURN NEXT result;
                END IF;
            END IF;
            SELECT INTO result.limit_groups result.limit_groups || ARRAY_AGG(limit_group) FROM config.circ_limit_set_group_map WHERE limit_set = circ_limit_set.id AND NOT check_only;
    END LOOP;

    -- If we passed everything, return the successful matchpoint
    IF NOT done THEN
        RETURN NEXT result;
    END IF;

    RETURN;
END;

Function: action.item_user_renew_test(integer, bigint, integer)

Returns: SET OF circ_matrix_test_result

Language: SQL

    SELECT * FROM action.item_user_circ_test( $1, $2, $3, TRUE );

Function: action.link_circ_limit_groups(bigint, integer[])

Returns: void

Language: SQL

    INSERT INTO action.circulation_limit_group_map(circ, limit_group) SELECT $1, id FROM config.circ_limit_group WHERE id IN (SELECT * FROM UNNEST($2));

Function: action.maintain_usr_circ_history()

Returns: trigger

Language: PLPGSQL

DECLARE
    cur_circ  BIGINT;
    first_circ BIGINT;
BEGIN                                                                          

    -- Any retention value signifies history is enabled.
    -- This assumes that clearing these values via external 
    -- process deletes the action.usr_circ_history rows.
    -- TODO: replace these settings w/ a single bool setting?
    PERFORM 1 FROM actor.usr_setting 
        WHERE usr = NEW.usr AND value IS NOT NULL AND name IN (
            'history.circ.retention_age', 
            'history.circ.retention_start'
        );

    IF NOT FOUND THEN
        RETURN NEW;
    END IF;

    IF TG_OP = 'INSERT' AND NEW.parent_circ IS NULL THEN
        -- Starting a new circulation.  Insert the history row.
        INSERT INTO action.usr_circ_history 
            (usr, xact_start, target_copy, due_date, source_circ)
        VALUES (
            NEW.usr, 
            NEW.xact_start, 
            NEW.target_copy, 
            NEW.due_date, 
            NEW.id
        );

        RETURN NEW;
    END IF;

    -- find the first and last circs in the circ chain 
    -- for the currently modified circ.
    FOR cur_circ IN SELECT id FROM action.circ_chain(NEW.id) LOOP
        IF first_circ IS NULL THEN
            first_circ := cur_circ;
            CONTINUE;
        END IF;
        -- Allow the loop to continue so that at as the loop
        -- completes cur_circ points to the final circulation.
    END LOOP;

    IF NEW.id <> cur_circ THEN
        -- Modifying an intermediate circ.  Ignore it.
        RETURN NEW;
    END IF;

    -- Update the due_date/checkin_time on the history row if the current 
    -- circ is the last circ in the chain and an update is warranted.

    UPDATE action.usr_circ_history 
        SET 
            due_date = NEW.due_date,
            checkin_time = NEW.checkin_time
        WHERE 
            source_circ = first_circ 
            AND (
                due_date <> NEW.due_date OR (
                    (checkin_time IS NULL AND NEW.checkin_time IS NOT NULL) OR
                    (checkin_time IS NOT NULL AND NEW.checkin_time IS NULL) OR
                    (checkin_time <> NEW.checkin_time)
                )
            );
    RETURN NEW;
END;                                                                           

Function: action.purge_circulations()

Returns: integer

Language: PLPGSQL

DECLARE
    org_keep_age    INTERVAL;
    org_use_last    BOOL = false;
    org_age_is_min  BOOL = false;
    org_keep_count  INT;

    keep_age        INTERVAL;

    target_acp      RECORD;
    circ_chain_head action.circulation%ROWTYPE;
    circ_chain_tail action.circulation%ROWTYPE;

    count_purged    INT;
    num_incomplete  INT;

    last_finished   TIMESTAMP WITH TIME ZONE;
BEGIN

    count_purged := 0;

    SELECT value::INTERVAL INTO org_keep_age FROM config.global_flag WHERE name = 'history.circ.retention_age' AND enabled;

    SELECT value::INT INTO org_keep_count FROM config.global_flag WHERE name = 'history.circ.retention_count' AND enabled;
    IF org_keep_count IS NULL THEN
        RETURN count_purged; -- Gimme a count to keep, or I keep them all, forever
    END IF;

    SELECT enabled INTO org_use_last FROM config.global_flag WHERE name = 'history.circ.retention_uses_last_finished';
    SELECT enabled INTO org_age_is_min FROM config.global_flag WHERE name = 'history.circ.retention_age_is_min';

    -- First, find copies with more than keep_count non-renewal circs
    FOR target_acp IN
        SELECT  target_copy,
                COUNT(*) AS total_real_circs
          FROM  action.circulation
          WHERE parent_circ IS NULL
                AND xact_finish IS NOT NULL
          GROUP BY target_copy
          HAVING COUNT(*) > org_keep_count
    LOOP
        -- And, for those, select circs that are finished and older than keep_age
        FOR circ_chain_head IN
            -- For reference, the subquery uses a window function to order the circs newest to oldest and number them
            -- The outer query then uses that information to skip the most recent set the library wants to keep
            -- End result is we don't care what order they come out in, as they are all potentials for deletion.
            SELECT ac.* FROM action.circulation ac JOIN (
              SELECT  rank() OVER (ORDER BY xact_start DESC), ac.id
                FROM  action.circulation ac
                WHERE ac.target_copy = target_acp.target_copy
                  AND ac.parent_circ IS NULL
                ORDER BY ac.xact_start ) ranked USING (id)
                WHERE ranked.rank > org_keep_count
        LOOP

            SELECT * INTO circ_chain_tail FROM action.circ_chain(circ_chain_head.id) ORDER BY xact_start DESC LIMIT 1;
            SELECT COUNT(CASE WHEN xact_finish IS NULL THEN 1 ELSE NULL END), MAX(xact_finish) INTO num_incomplete, last_finished FROM action.circ_chain(circ_chain_head.id);
            CONTINUE WHEN circ_chain_tail.xact_finish IS NULL OR num_incomplete > 0;

            IF NOT org_use_last THEN
                last_finished := circ_chain_tail.xact_finish;
            END IF;

            keep_age := COALESCE( org_keep_age, '2000 years'::INTERVAL );

            IF org_age_is_min THEN
                keep_age := GREATEST( keep_age, org_keep_age );
            END IF;

            CONTINUE WHEN AGE(NOW(), last_finished) < keep_age;

            -- We've passed the purging tests, purge the circ chain starting at the end
            -- A trigger should auto-purge the rest of the chain.
            DELETE FROM action.circulation WHERE id = circ_chain_tail.id;

            count_purged := count_purged + 1;

        END LOOP;
    END LOOP;

    return count_purged;
END;

Function: action.purge_holds()

Returns: integer

Language: PLPGSQL

DECLARE
  current_hold RECORD;
  purged_holds INT;
  cgf_d INTERVAL;
  cgf_f INTERVAL;
  cgf_c INTERVAL;
  prev_usr INT;
  user_start TIMESTAMPTZ;
  user_age INTERVAL;
  user_count INT;
BEGIN
  purged_holds := 0;
  SELECT INTO cgf_d value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age' AND enabled;
  SELECT INTO cgf_f value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_fulfilled' AND enabled;
  SELECT INTO cgf_c value::INTERVAL FROM config.global_flag WHERE name = 'history.hold.retention_age_canceled' AND enabled;
  FOR current_hold IN
    SELECT
      rank() OVER (PARTITION BY usr ORDER BY COALESCE(fulfillment_time, cancel_time) DESC),
      cgf_cs.value::INTERVAL as cgf_cs,
      ahr.*
    FROM
      action.hold_request ahr
      LEFT JOIN config.global_flag cgf_cs ON (ahr.cancel_cause IS NOT NULL AND cgf_cs.name = 'history.hold.retention_age_canceled_' || ahr.cancel_cause AND cgf_cs.enabled)
    WHERE
      (fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL)
  LOOP
    IF prev_usr IS NULL OR prev_usr != current_hold.usr THEN
      prev_usr := current_hold.usr;
      SELECT INTO user_start oils_json_to_text(value)::TIMESTAMPTZ FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_start';
      SELECT INTO user_age oils_json_to_text(value)::INTERVAL FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_age';
      SELECT INTO user_count oils_json_to_text(value)::INT FROM actor.usr_setting WHERE usr = prev_usr AND name = 'history.hold.retention_count';
      IF user_start IS NOT NULL THEN
        user_age := LEAST(user_age, AGE(NOW(), user_start));
      END IF;
      IF user_count IS NULL THEN
        user_count := 1000; -- Assumption based on the user visible holds routine
      END IF;
    END IF;
    -- Library keep age trumps user keep anything, for purposes of being able to hold on to things when staff canceled and such.
    IF current_hold.fulfillment_time IS NOT NULL AND current_hold.fulfillment_time > NOW() - COALESCE(cgf_f, cgf_d) THEN
      CONTINUE;
    END IF;
    IF current_hold.cancel_time IS NOT NULL AND current_hold.cancel_time > NOW() - COALESCE(current_hold.cgf_cs, cgf_c, cgf_d) THEN
      CONTINUE;
    END IF;

    -- User keep age needs combining with count. If too old AND within the count, keep!
    IF user_start IS NOT NULL AND COALESCE(current_hold.fulfillment_time, current_hold.cancel_time) > NOW() - user_age AND current_hold.rank <= user_count THEN
      CONTINUE;
    END IF;

    -- All checks should have passed, delete!
    DELETE FROM action.hold_request WHERE id = current_hold.id;
    purged_holds := purged_holds + 1;
  END LOOP;
  RETURN purged_holds;
END;

Function: action.push_circ_due_time()

Returns: trigger

Language: PLPGSQL

DECLARE
    proper_tz TEXT := COALESCE(
        oils_json_to_text((
            SELECT value
              FROM  actor.org_unit_ancestor_setting('lib.timezone',NEW.circ_lib)
              LIMIT 1
        )),
        CURRENT_SETTING('timezone')
    );
BEGIN

    IF (EXTRACT(EPOCH FROM NEW.duration)::INT % EXTRACT(EPOCH FROM '1 day'::INTERVAL)::INT) = 0 -- day-granular duration
        AND SUBSTRING((NEW.due_date AT TIME ZONE proper_tz)::TIME::TEXT FROM 1 FOR 8) <> '23:59:59' THEN -- has not yet been pushed
        NEW.due_date = ((NEW.due_date AT TIME ZONE proper_tz)::DATE + '1 day'::INTERVAL - '1 second'::INTERVAL) || ' ' || proper_tz;
    END IF;

    RETURN NEW;
END;

Function: action.summarize_all_circ_chain(ctx_circ_id integer)

Returns: circ_chain_summary

Language: PLPGSQL


DECLARE

    -- first circ in the chain
    circ_0 action.all_circulation_slim%ROWTYPE;

    -- last circ in the chain
    circ_n action.all_circulation_slim%ROWTYPE;

    -- circ chain under construction
    chain action.circ_chain_summary;
    tmp_circ action.all_circulation_slim%ROWTYPE;

BEGIN
    
    chain.num_circs := 0;
    FOR tmp_circ IN SELECT * FROM action.all_circ_chain(ctx_circ_id) LOOP

        IF chain.num_circs = 0 THEN
            circ_0 := tmp_circ;
        END IF;

        chain.num_circs := chain.num_circs + 1;
        circ_n := tmp_circ;
    END LOOP;

    chain.start_time := circ_0.xact_start;
    chain.last_stop_fines := circ_n.stop_fines;
    chain.last_stop_fines_time := circ_n.stop_fines_time;
    chain.last_checkin_time := circ_n.checkin_time;
    chain.last_checkin_scan_time := circ_n.checkin_scan_time;
    SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
    SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;

    IF chain.num_circs > 1 THEN
        chain.last_renewal_time := circ_n.xact_start;
        SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
    END IF;

    RETURN chain;

END;

Function: action.summarize_circ_chain(ctx_circ_id bigint)

Returns: circ_chain_summary

Language: PLPGSQL


DECLARE

    -- first circ in the chain
    circ_0 action.circulation%ROWTYPE;

    -- last circ in the chain
    circ_n action.circulation%ROWTYPE;

    -- circ chain under construction
    chain action.circ_chain_summary;
    tmp_circ action.circulation%ROWTYPE;

BEGIN
    
    chain.num_circs := 0;
    FOR tmp_circ IN SELECT * FROM action.circ_chain(ctx_circ_id) LOOP

        IF chain.num_circs = 0 THEN
            circ_0 := tmp_circ;
        END IF;

        chain.num_circs := chain.num_circs + 1;
        circ_n := tmp_circ;
    END LOOP;

    chain.start_time := circ_0.xact_start;
    chain.last_stop_fines := circ_n.stop_fines;
    chain.last_stop_fines_time := circ_n.stop_fines_time;
    chain.last_checkin_time := circ_n.checkin_time;
    chain.last_checkin_scan_time := circ_n.checkin_scan_time;
    SELECT INTO chain.checkout_workstation name FROM actor.workstation WHERE id = circ_0.workstation;
    SELECT INTO chain.last_checkin_workstation name FROM actor.workstation WHERE id = circ_n.checkin_workstation;

    IF chain.num_circs > 1 THEN
        chain.last_renewal_time := circ_n.xact_start;
        SELECT INTO chain.last_renewal_workstation name FROM actor.workstation WHERE id = circ_n.workstation;
    END IF;

    RETURN chain;

END;

Function: action.survey_response_answer_date_fixup()

Returns: trigger

Language: PLPGSQL

BEGIN
	NEW.answer_date := NOW();
	RETURN NEW;
END;

Function: action.usr_visible_holds(usr_id integer)

Returns: SET OF hold_request

Language: PLPGSQL

DECLARE
    h               action.hold_request%ROWTYPE;
    view_age        INTERVAL;
    view_count      INT;
    usr_view_count  actor.usr_setting%ROWTYPE;
    usr_view_age    actor.usr_setting%ROWTYPE;
    usr_view_start  actor.usr_setting%ROWTYPE;
BEGIN
    SELECT * INTO usr_view_count FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_count';
    SELECT * INTO usr_view_age FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_age';
    SELECT * INTO usr_view_start FROM actor.usr_setting WHERE usr = usr_id AND name = 'history.hold.retention_start';

    FOR h IN
        SELECT  *
          FROM  action.hold_request
          WHERE usr = usr_id
                AND fulfillment_time IS NULL
                AND cancel_time IS NULL
          ORDER BY request_time DESC
    LOOP
        RETURN NEXT h;
    END LOOP;

    IF usr_view_start.value IS NULL THEN
        RETURN;
    END IF;

    IF usr_view_age.value IS NOT NULL THEN
        -- User opted in and supplied a retention age
        IF oils_json_to_text(usr_view_age.value)::INTERVAL > AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ) THEN
            view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
        ELSE
            view_age := oils_json_to_text(usr_view_age.value)::INTERVAL;
        END IF;
    ELSE
        -- User opted in
        view_age := AGE(NOW(), oils_json_to_text(usr_view_start.value)::TIMESTAMPTZ);
    END IF;

    IF usr_view_count.value IS NOT NULL THEN
        view_count := oils_json_to_text(usr_view_count.value)::INT;
    ELSE
        view_count := 1000;
    END IF;

    -- show some fulfilled/canceled holds
    FOR h IN
        SELECT  *
          FROM  action.hold_request
          WHERE usr = usr_id
                AND ( fulfillment_time IS NOT NULL OR cancel_time IS NOT NULL )
                AND COALESCE(fulfillment_time, cancel_time) > NOW() - view_age
          ORDER BY COALESCE(fulfillment_time, cancel_time) DESC
          LIMIT view_count
    LOOP
        RETURN NEXT h;
    END LOOP;

    RETURN;
END;

Schema action_trigger


Table: action_trigger.alternate_template

action_trigger.alternate_template Structure
F-Key Name Type Description
id serial NOT NULL
action_trigger.event_definition.id event_def integer UNIQUE#1
template text
active boolean DEFAULT true
config.i18n_locale.code locale text UNIQUE#1
message_title text
message_template text

Index - Schema action_trigger


Table: action_trigger.cleanup

action_trigger.cleanup Structure
F-Key Name Type Description
module text PRIMARY KEY
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema action_trigger


Table: action_trigger.collector

action_trigger.collector Structure
F-Key Name Type Description
module text PRIMARY KEY
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema action_trigger


Table: action_trigger.environment

action_trigger.environment Structure
F-Key Name Type Description
id serial PRIMARY KEY
action_trigger.event_definition.id event_def integer UNIQUE#1 NOT NULL
path text
action_trigger.collector.module collector text
label text UNIQUE#1

 

action_trigger.environment Constraints
Name Constraint
environment_label_check CHECK ((label <> ALL (ARRAY['result'::text, 'target'::text, 'event'::text])))

Index - Schema action_trigger


Table: action_trigger.event

action_trigger.event Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
target bigint NOT NULL
action_trigger.event_definition.id event_def integer
add_time timestamp with time zone NOT NULL DEFAULT now()
run_time timestamp with time zone NOT NULL
start_time timestamp with time zone
update_time timestamp with time zone
complete_time timestamp with time zone
update_process integer
state text NOT NULL DEFAULT 'pending'::text
user_data text
action_trigger.event_output.id template_output bigint
action_trigger.event_output.id error_output bigint
action_trigger.event_output.id async_output bigint
actor.usr.id context_user integer
actor.org_unit.id context_library integer
biblio.record_entry.id context_bib bigint
context_item bigint

 

action_trigger.event Constraints
Name Constraint
event_state_check CHECK ((state = ANY (ARRAY['pending'::text, 'invalid'::text, 'found'::text, 'collecting'::text, 'collected'::text, 'validating'::text, 'valid'::text, 'reacting'::text, 'reacted'::text, 'cleaning'::text, 'complete'::text, 'error'::text])))
event_user_data_check CHECK (((user_data IS NULL) OR is_json(user_data)))
atev_async_output async_output atev_context_item context_item atev_context_library context_library atev_context_user context_user atev_def_state event_def, state atev_error_output error_output atev_target_def_idx target, event_def atev_template_output template_output

Index - Schema action_trigger


Table: action_trigger.event_def_group

action_trigger.event_def_group Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer NOT NULL
action_trigger.hook.key hook text NOT NULL
active boolean NOT NULL DEFAULT true
name text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema action_trigger


Table: action_trigger.event_def_group_member

action_trigger.event_def_group_member Structure
F-Key Name Type Description
id serial PRIMARY KEY
action_trigger.event_def_group.id grp integer NOT NULL
action_trigger.event_definition.id event_def integer NOT NULL
sortable boolean NOT NULL DEFAULT true
holdings boolean NOT NULL DEFAULT false
external boolean NOT NULL DEFAULT false
name text NOT NULL

Index - Schema action_trigger


Table: action_trigger.event_definition

action_trigger.event_definition Structure
F-Key Name Type Description
id serial PRIMARY KEY
active boolean NOT NULL DEFAULT true
actor.org_unit.id owner integer UNIQUE#2 UNIQUE#1 NOT NULL
name text UNIQUE#2 NOT NULL
action_trigger.hook.key hook text UNIQUE#1 NOT NULL
action_trigger.validator.module validator text UNIQUE#1 NOT NULL
action_trigger.reactor.module reactor text UNIQUE#1 NOT NULL
action_trigger.cleanup.module cleanup_success text
action_trigger.cleanup.module cleanup_failure text
delay interval UNIQUE#1 NOT NULL DEFAULT '00:05:00'::interval
max_delay interval
repeat_delay interval
usr_field text
config.usr_setting_type.name opt_in_setting text
delay_field text UNIQUE#1
group_field text
template text
granularity text
context_usr_path text
context_library_path text
context_bib_path text
context_item_path text
message_template text
message_usr_path text
message_library_path text
message_title text
retention_interval interval

Tables referencing this one via Foreign Key Constraints:

Index - Schema action_trigger


Table: action_trigger.event_output

action_trigger.event_output Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
create_time timestamp with time zone NOT NULL DEFAULT now()
is_error boolean NOT NULL DEFAULT false
data text NOT NULL
locale text

Tables referencing this one via Foreign Key Constraints:

Index - Schema action_trigger


Table: action_trigger.event_params

action_trigger.event_params Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
action_trigger.event_definition.id event_def integer UNIQUE#1 NOT NULL
param text UNIQUE#1 NOT NULL
value text NOT NULL

Index - Schema action_trigger


Table: action_trigger.hook

action_trigger.hook Structure
F-Key Name Type Description
key text PRIMARY KEY
core_type text NOT NULL
description text
passive boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema action_trigger


Table: action_trigger.reactor

action_trigger.reactor Structure
F-Key Name Type Description
module text PRIMARY KEY
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema action_trigger


Table: action_trigger.validator

action_trigger.validator Structure
F-Key Name Type Description
module text PRIMARY KEY
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema action_trigger


Function: action_trigger.check_valid_retention_interval()

Returns: trigger

Language: PLPGSQL

BEGIN
    /*
     * 1. Retention intervals are always allowed on active hooks.
     * 2. On passive hooks, retention intervals are only allowed
     *    when the event definition has a max_delay value and the
     *    retention_interval value is greater than the difference 
     *    beteween the delay and max_delay values.
     */ 
    PERFORM TRUE FROM action_trigger.hook 
        WHERE key = NEW.hook AND NOT passive;

    IF FOUND THEN
        RETURN NEW;
    END IF;

    IF NEW.max_delay IS NOT NULL THEN
        IF EXTRACT(EPOCH FROM NEW.retention_interval) > 
            ABS(EXTRACT(EPOCH FROM (NEW.max_delay - NEW.delay))) THEN
            RETURN NEW; -- all good
        ELSE
            RAISE EXCEPTION 'retention_interval is too short';
        END IF;
    ELSE
        RAISE EXCEPTION 'retention_interval requires max_delay';
    END IF;
END;

Function: action_trigger.purge_events()

Returns: void

Language: PLPGSQL

/**
  * Deleting expired events without simultaneously deleting their outputs
  * creates orphaned outputs.  Deleting their outputs and all of the events 
  * linking back to them, plus any outputs those events link to is messy and 
  * inefficient.  It's simpler to handle them in 2 sweeping steps.
  *
  * 1. Delete expired events.
  * 2. Delete orphaned event outputs.
  *
  * This has the added benefit of removing outputs that may have been
  * orphaned by some other process.  Such outputs are not usuable by
  * the system.
  *
  * This does not guarantee that all events within an event group are
  * purged at the same time.  In such cases, the remaining events will
  * be purged with the next instance of the purge (or soon thereafter).
  * This is another nod toward efficiency over completeness of old 
  * data that's circling the bit bucket anyway.
  */
BEGIN

    DELETE FROM action_trigger.event WHERE id IN (
        SELECT evt.id
        FROM action_trigger.event evt
        JOIN action_trigger.event_definition def ON (def.id = evt.event_def)
        WHERE def.retention_interval IS NOT NULL 
            AND evt.state <> 'pending'
            AND evt.update_time < (NOW() - def.retention_interval)
    );

    WITH linked_outputs AS (
        SELECT templates.id AS id FROM (
            SELECT DISTINCT(template_output) AS id
                FROM action_trigger.event WHERE template_output IS NOT NULL
            UNION
            SELECT DISTINCT(error_output) AS id
                FROM action_trigger.event WHERE error_output IS NOT NULL
            UNION
            SELECT DISTINCT(async_output) AS id
                FROM action_trigger.event WHERE async_output IS NOT NULL
        ) templates
    ) DELETE FROM action_trigger.event_output
        WHERE id NOT IN (SELECT id FROM linked_outputs);

END;

Schema actor

Holds all tables pertaining to users and libraries (org units).


Table: actor.address_alert

actor.address_alert Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer NOT NULL
active boolean NOT NULL DEFAULT true
match_all boolean NOT NULL DEFAULT true
alert_message text NOT NULL
street1 text
street2 text
city text
county text
state text
country text
post_code text
mailing_address boolean NOT NULL DEFAULT false
billing_address boolean NOT NULL DEFAULT false

Index - Schema actor


Table: actor.card

Library Cards Each User has one or more library cards. The current "main" card is linked to here from the actor.usr table, and it is up to the consortium policy whether more than one card can be active for any one user at a given time.

actor.card Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id usr integer NOT NULL
barcode text UNIQUE NOT NULL
active boolean NOT NULL DEFAULT true
actor_card_barcode_evergreen_lowercase_idx lowercase(barcode) actor_card_usr_idx usr

Index - Schema actor


Table: actor.copy_alert_suppress

actor.copy_alert_suppress Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id org integer NOT NULL
config.copy_alert_type.id alert_type integer NOT NULL

Index - Schema actor


Table: actor.hours_of_operation

When does this org_unit usually open and close? (Variations are expressed in the actor.org_unit_closed table.)

actor.hours_of_operation Structure
F-Key Name Type Description
actor.org_unit.id id integer PRIMARY KEY
dow_0_open time without time zone NOT NULL DEFAULT '09:00:00'::time without time zone

When does this org_unit open on Monday?
dow_0_close time without time zone NOT NULL DEFAULT '17:00:00'::time without time zone

When does this org_unit close on Monday?
dow_0_note text
dow_1_open time without time zone NOT NULL DEFAULT '09:00:00'::time without time zone

When does this org_unit open on Tuesday?
dow_1_close time without time zone NOT NULL DEFAULT '17:00:00'::time without time zone

When does this org_unit close on Tuesday?
dow_1_note text
dow_2_open time without time zone NOT NULL DEFAULT '09:00:00'::time without time zone

When does this org_unit open on Wednesday?
dow_2_close time without time zone NOT NULL DEFAULT '17:00:00'::time without time zone

When does this org_unit close on Wednesday?
dow_2_note text
dow_3_open time without time zone NOT NULL DEFAULT '09:00:00'::time without time zone

When does this org_unit open on Thursday?
dow_3_close time without time zone NOT NULL DEFAULT '17:00:00'::time without time zone

When does this org_unit close on Thursday?
dow_3_note text
dow_4_open time without time zone NOT NULL DEFAULT '09:00:00'::time without time zone

When does this org_unit open on Friday?
dow_4_close time without time zone NOT NULL DEFAULT '17:00:00'::time without time zone

When does this org_unit close on Friday?
dow_4_note text
dow_5_open time without time zone NOT NULL DEFAULT '09:00:00'::time without time zone

When does this org_unit open on Saturday?
dow_5_close time without time zone NOT NULL DEFAULT '17:00:00'::time without time zone

When does this org_unit close on Saturday?
dow_5_note text
dow_6_open time without time zone NOT NULL DEFAULT '09:00:00'::time without time zone

When does this org_unit open on Sunday?
dow_6_close time without time zone NOT NULL DEFAULT '17:00:00'::time without time zone

When does this org_unit close on Sunday?
dow_6_note text

Index - Schema actor


Table: actor.org_address

actor.org_address Structure
F-Key Name Type Description
id serial PRIMARY KEY
valid boolean NOT NULL DEFAULT true
address_type text NOT NULL DEFAULT 'MAILING'::text
actor.org_unit.id org_unit integer NOT NULL
street1 text NOT NULL
street2 text
city text NOT NULL
county text
state text
country text NOT NULL
post_code text NOT NULL
san text
latitude double precision
longitude double precision

Tables referencing this one via Foreign Key Constraints:

actor_org_address_org_unit_idx org_unit

Index - Schema actor


Table: actor.org_lasso

actor.org_lasso Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE
global boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.org_lasso_map

actor.org_lasso_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_lasso.id lasso integer NOT NULL
actor.org_unit.id org_unit integer NOT NULL
ou_lasso_org_unit_idx org_unit

Index - Schema actor


Table: actor.org_unit

actor.org_unit Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id parent_ou integer
actor.org_unit_type.id ou_type integer NOT NULL
actor.org_address.id ill_address integer
actor.org_address.id holds_address integer
actor.org_address.id mailing_address integer
actor.org_address.id billing_address integer
shortname text UNIQUE NOT NULL
name text UNIQUE NOT NULL
email text
phone text
opac_visible boolean NOT NULL DEFAULT true
acq.fiscal_calendar.id fiscal_calendar integer NOT NULL DEFAULT 1

Tables referencing this one via Foreign Key Constraints:

actor_org_unit_billing_address_idx billing_address actor_org_unit_holds_address_idx holds_address actor_org_unit_ill_address_idx ill_address actor_org_unit_mailing_address_idx mailing_address actor_org_unit_ou_type_idx ou_type actor_org_unit_parent_ou_idx parent_ou

Index - Schema actor


Table: actor.org_unit_closed

actor.org_unit_closed Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id org_unit integer NOT NULL
close_start timestamp with time zone NOT NULL
close_end timestamp with time zone NOT NULL
full_day boolean NOT NULL DEFAULT false
multi_day boolean NOT NULL DEFAULT false
reason text
action.emergency_closing.id emergency_closing integer

Index - Schema actor


Table: actor.org_unit_custom_tree

actor.org_unit_custom_tree Structure
F-Key Name Type Description
id serial PRIMARY KEY
active boolean DEFAULT false
purpose actor.org_unit_custom_tree_purpose UNIQUE NOT NULL DEFAULT 'opac'::actor.org_unit_custom_tree_purpose

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.org_unit_custom_tree_node

actor.org_unit_custom_tree_node Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit_custom_tree.id tree integer UNIQUE#1
actor.org_unit.id org_unit integer UNIQUE#1 NOT NULL
actor.org_unit_custom_tree_node.id parent_node integer
sibling_order integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.org_unit_proximity

actor.org_unit_proximity Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
from_org integer
to_org integer
prox integer
from_prox_idx from_org

Index - Schema actor


Table: actor.org_unit_proximity_adjustment

actor.org_unit_proximity_adjustment Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id item_circ_lib integer
actor.org_unit.id item_owning_lib integer
asset.copy_location.id copy_location integer
actor.org_unit.id hold_pickup_lib integer
actor.org_unit.id hold_request_lib integer
pos integer NOT NULL
absolute_adjustment boolean NOT NULL DEFAULT false
prox_adjustment numeric
config.circ_modifier.code circ_mod text

 

actor.org_unit_proximity_adjustment Constraints
Name Constraint
prox_adj_criterium CHECK ((COALESCE((item_circ_lib)::text, (item_owning_lib)::text, (copy_location)::text, (hold_pickup_lib)::text, (hold_request_lib)::text, circ_mod) IS NOT NULL))
prox_adj_circ_lib_idx item_circ_lib prox_adj_circ_mod_idx circ_mod prox_adj_copy_location_idx copy_location prox_adj_owning_lib_idx item_owning_lib prox_adj_pickup_lib_idx hold_pickup_lib prox_adj_request_lib_idx hold_request_lib

Index - Schema actor


Table: actor.org_unit_setting

Org Unit settings This table contains any arbitrary settings that a client program would like to save for an org unit.

actor.org_unit_setting Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.org_unit.id org_unit integer UNIQUE#1 NOT NULL
config.org_unit_setting_type.name name text UNIQUE#1 NOT NULL
value text NOT NULL

 

actor.org_unit_setting Constraints
Name Constraint
aous_must_be_json CHECK (is_json(value))
actor_org_unit_setting_usr_idx org_unit

Index - Schema actor


Table: actor.org_unit_type

actor.org_unit_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL
opac_label text NOT NULL
depth integer NOT NULL
actor.org_unit_type.id parent integer
can_have_vols boolean NOT NULL DEFAULT true
can_have_users boolean NOT NULL DEFAULT true

Tables referencing this one via Foreign Key Constraints:

actor_org_unit_type_parent_idx parent

Index - Schema actor


Table: actor.passwd

actor.passwd Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id usr integer UNIQUE#1 NOT NULL
salt text
passwd text NOT NULL
actor.passwd_type.code passwd_type text UNIQUE#1 NOT NULL
create_date timestamp with time zone NOT NULL DEFAULT now()
edit_date timestamp with time zone NOT NULL DEFAULT now()

Index - Schema actor


Table: actor.passwd_type

actor.passwd_type Structure
F-Key Name Type Description
code text PRIMARY KEY
name text UNIQUE NOT NULL
login boolean NOT NULL DEFAULT false
regex text
crypt_algo text
iter_count integer

 

actor.passwd_type Constraints
Name Constraint
passwd_type_iter_count_check CHECK (((iter_count IS NULL) OR (iter_count > 0)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.search_filter_group

actor.search_filter_group Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#2 UNIQUE#1 NOT NULL
code text UNIQUE#2 NOT NULL
label text UNIQUE#1 NOT NULL
create_date timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.search_filter_group_entry

actor.search_filter_group_entry Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.search_filter_group.id grp integer UNIQUE#1 NOT NULL
pos integer NOT NULL
actor.search_query.id query integer UNIQUE#1 NOT NULL

Index - Schema actor


Table: actor.search_query

actor.search_query Structure
F-Key Name Type Description
id serial PRIMARY KEY
label text NOT NULL
query_text text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.stat_cat

User Statistical Catagories Local data collected about Users is placed into a Statistical Catagory. Here's where those catagories are defined.

actor.stat_cat Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
opac_visible boolean NOT NULL DEFAULT false
usr_summary boolean NOT NULL DEFAULT false
actor.stat_cat_sip_fields.field sip_field character(2)
sip_format text
checkout_archive boolean NOT NULL DEFAULT false
required boolean NOT NULL DEFAULT false
allow_freetext boolean NOT NULL DEFAULT true

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.stat_cat_entry

User Statistical Catagory Entries Local data collected about Users is placed into a Statistical Catagory. Each library can create entries into any of its own stat_cats, its ancestors' stat_cats, or its descendants' stat_cats.

actor.stat_cat_entry Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.stat_cat.id stat_cat integer UNIQUE#1 NOT NULL
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
value text UNIQUE#1 NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.stat_cat_entry_default

User Statistical Category Default Entry A library may choose one of the stat_cat entries to be the default entry.

actor.stat_cat_entry_default Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.stat_cat_entry.id stat_cat_entry integer NOT NULL
actor.stat_cat.id stat_cat integer UNIQUE#1 NOT NULL
actor.org_unit.id owner integer UNIQUE#1 NOT NULL

Index - Schema actor


Table: actor.stat_cat_entry_usr_map

Statistical Catagory Entry to User map Records the stat_cat entries for each user.

actor.stat_cat_entry_usr_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
stat_cat_entry text NOT NULL
actor.stat_cat.id stat_cat integer UNIQUE#1 NOT NULL
actor.usr.id target_usr integer UNIQUE#1 NOT NULL
actor_stat_cat_entry_usr_idx target_usr

Index - Schema actor


Table: actor.stat_cat_sip_fields

Actor Statistical Category SIP Fields Contains the list of valid SIP Field identifiers for Statistical Categories.

actor.stat_cat_sip_fields Structure
F-Key Name Type Description
field character(2) PRIMARY KEY
name text NOT NULL
one_only boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.toolbar

actor.toolbar Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.workstation.id ws integer
actor.org_unit.id org integer
actor.usr.id usr integer
label text NOT NULL
layout text NOT NULL

 

actor.toolbar Constraints
Name Constraint
layout_must_be_json CHECK (is_json(layout))
only_one_type CHECK ((((ws IS NOT NULL) AND (COALESCE(org, usr) IS NULL)) OR ((org IS NOT NULL) AND (COALESCE(ws, usr) IS NULL)) OR ((usr IS NOT NULL) AND (COALESCE(org, ws) IS NULL))))

Index - Schema actor


Table: actor.usr

User objects This table contains the core User objects that describe both staff members and patrons. The difference between the two types of users is based on the user's permissions.

actor.usr Structure
F-Key Name Type Description
id serial PRIMARY KEY
card integer UNIQUE
permission.grp_tree.id profile integer NOT NULL
usrname text UNIQUE NOT NULL
email text
passwd text NOT NULL
config.standing.id standing integer NOT NULL DEFAULT 1
config.identification_type.id ident_type integer NOT NULL
ident_value text
config.identification_type.id ident_type2 integer
ident_value2 text
config.net_access_level.id net_access_level integer NOT NULL DEFAULT 1
photo_url text
prefix text
first_given_name text NOT NULL
second_given_name text
family_name text NOT NULL
suffix text
guardian text
pref_prefix text
pref_first_given_name text
pref_second_given_name text
pref_family_name text
pref_suffix text
name_keywords text
name_kw_tsvector tsvector
alias text
day_phone text
evening_phone text
other_phone text
actor.usr_address.id mailing_address integer
actor.usr_address.id billing_address integer
actor.org_unit.id home_ou integer NOT NULL
dob date
active boolean NOT NULL DEFAULT true
master_account boolean NOT NULL DEFAULT false
super_user boolean NOT NULL DEFAULT false
barred boolean NOT NULL DEFAULT false
deleted boolean NOT NULL DEFAULT false
juvenile boolean NOT NULL DEFAULT false
usrgroup serial NOT NULL
claims_returned_count integer NOT NULL
credit_forward_balance numeric(6,2) NOT NULL DEFAULT 0.00
last_xact_id text NOT NULL DEFAULT 'none'::text
create_date timestamp with time zone NOT NULL DEFAULT now()
expire_date timestamp with time zone NOT NULL DEFAULT (now() + '3 years'::interval)
claims_never_checked_out_count integer NOT NULL
last_update_time timestamp with time zone
config.i18n_locale.code locale text

Tables referencing this one via Foreign Key Constraints:

actor_usr_billing_address_idx billing_address actor_usr_day_phone_idx lowercase(day_phone) actor_usr_day_phone_idx_numeric lowercase(regexp_replace(day_phone, '[^0-9]'::text, ''::text, 'g'::text)) actor_usr_email_idx lowercase(email) actor_usr_evening_phone_idx lowercase(evening_phone) actor_usr_evening_phone_idx_numeric lowercase(regexp_replace(evening_phone, '[^0-9]'::text, ''::text, 'g'::text)) actor_usr_family_name_idx lowercase(family_name) actor_usr_family_name_unaccent_idx unaccent_and_squash(family_name) actor_usr_first_given_name_idx lowercase(first_given_name) actor_usr_first_given_name_unaccent_idx unaccent_and_squash(first_given_name) actor_usr_guardian_idx lowercase(guardian) actor_usr_guardian_unaccent_idx unaccent_and_squash(guardian) actor_usr_home_ou_idx home_ou actor_usr_ident_value2_idx lowercase(ident_value2) actor_usr_ident_value_idx lowercase(ident_value) actor_usr_mailing_address_idx mailing_address actor_usr_other_phone_idx lowercase(other_phone) actor_usr_other_phone_idx_numeric lowercase(regexp_replace(other_phone, '[^0-9]'::text, ''::text, 'g'::text)) actor_usr_pref_family_name_idx lowercase(pref_family_name) actor_usr_pref_family_name_unaccent_idx unaccent_and_squash(pref_family_name) actor_usr_pref_first_given_name_idx lowercase(pref_first_given_name) actor_usr_pref_first_given_name_unaccent_idx unaccent_and_squash(pref_first_given_name) actor_usr_pref_second_given_name_idx lowercase(pref_second_given_name) actor_usr_pref_second_given_name_unaccent_idx unaccent_and_squash(pref_second_given_name) actor_usr_second_given_name_idx lowercase(second_given_name) actor_usr_second_given_name_unaccent_idx unaccent_and_squash(second_given_name) actor_usr_usrgroup_idx usrgroup actor_usr_usrname_idx lowercase(usrname) actor_usr_usrname_unaccent_idx unaccent_and_squash(usrname)

Index - Schema actor


Table: actor.usr_activity

actor.usr_activity Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id usr integer
config.usr_activity_type.id etype integer NOT NULL
event_time timestamp with time zone NOT NULL DEFAULT now()
usr_activity_usr_idx usr

Index - Schema actor


Table: actor.usr_address

actor.usr_address Structure
F-Key Name Type Description
id serial PRIMARY KEY
valid boolean NOT NULL DEFAULT true
within_city_limits boolean NOT NULL DEFAULT true
address_type text NOT NULL DEFAULT 'MAILING'::text
actor.usr.id usr integer NOT NULL
street1 text NOT NULL
street2 text
city text NOT NULL
county text
state text
country text NOT NULL
post_code text NOT NULL
pending boolean NOT NULL DEFAULT false
actor.usr_address.id replaces integer

Tables referencing this one via Foreign Key Constraints:

actor_usr_addr_city_idx lowercase(city) actor_usr_addr_post_code_idx lowercase(post_code) actor_usr_addr_state_idx lowercase(state) actor_usr_addr_street1_idx lowercase(street1) actor_usr_addr_street2_idx lowercase(street2) actor_usr_addr_usr_idx usr

Index - Schema actor


Table: actor.usr_message

actor.usr_message Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id usr integer NOT NULL
title text
message text NOT NULL
create_date timestamp with time zone NOT NULL DEFAULT now()
deleted boolean NOT NULL DEFAULT false
read_date timestamp with time zone
actor.org_unit.id sending_lib integer NOT NULL
pub boolean NOT NULL DEFAULT false
stop_date timestamp with time zone
actor.usr.id editor bigint
edit_date timestamp with time zone

Tables referencing this one via Foreign Key Constraints:

aum_editor editor aum_usr usr

Index - Schema actor


View: actor.usr_message_limited

actor.usr_message_limited Structure
F-Key Name Type Description
id integer
usr integer
title text
message text
create_date timestamp with time zone
deleted boolean
read_date timestamp with time zone
sending_lib integer
pub boolean
stop_date timestamp with time zone
editor bigint
edit_date timestamp with time zone
SELECT usr_message.id
,
    usr_message.usr
,
    usr_message.title
,
    usr_message.message
,
    usr_message.create_date
,
    usr_message.deleted
,
    usr_message.read_date
,
    usr_message.sending_lib
,
    usr_message.pub
,
    usr_message.stop_date
,
    usr_message.editor
,
    usr_message.edit_date
   
FROM actor.usr_message
  
WHERE (usr_message.pub 
   AND (NOT usr_message.deleted)
);

Index - Schema actor


View: actor.usr_message_penalty

actor.usr_message_penalty Structure
F-Key Name Type Description
id integer
ausp_id integer
aum_id integer
org_unit integer
ausp_org_unit integer
aum_sending_lib integer
usr integer
ausp_usr integer
aum_usr integer
standing_penalty integer
staff integer
create_date timestamp with time zone
ausp_set_date timestamp with time zone
aum_create_date timestamp with time zone
stop_date timestamp with time zone
ausp_stop_date timestamp with time zone
aum_stop_date timestamp with time zone
ausp_usr_message bigint
title text
message text
deleted boolean
read_date timestamp with time zone
pub boolean
editor bigint
edit_date timestamp with time zone
SELECT ausp.id
,
    ausp.id AS ausp_id
,
    aum.id AS aum_id
,
    ausp.org_unit
,
    ausp.org_unit AS ausp_org_unit
,
    aum.sending_lib AS aum_sending_lib
,
    ausp.usr
,
    ausp.usr AS ausp_usr
,
    aum.usr AS aum_usr
,
    ausp.standing_penalty
,
    ausp.staff
,
    ausp.set_date AS create_date
,
    ausp.set_date AS ausp_set_date
,
    aum.create_date AS aum_create_date
,
    ausp.stop_date
,
    ausp.stop_date AS ausp_stop_date
,
    aum.stop_date AS aum_stop_date
,
    ausp.usr_message AS ausp_usr_message
,
    aum.title
,
    aum.message
,
    aum.deleted
,
    aum.read_date
,
    aum.pub
,
    aum.editor
,
    aum.edit_date
   
FROM (actor.usr_standing_penalty ausp
     
LEFT JOIN actor.usr_message aum 
    ON (
           (ausp.usr_message = aum.id)
     )
)
UNION ALL
 
SELECT aum.id
,
    NULL::integer AS ausp_id
,
    aum.id AS aum_id
,
    aum.sending_lib AS org_unit
,
    NULL::integer AS ausp_org_unit
,
    aum.sending_lib AS aum_sending_lib
,
    aum.usr
,
    NULL::integer AS ausp_usr
,
    aum.usr AS aum_usr
,
    NULL::integer AS standing_penalty
,
    NULL::integer AS staff
,
    aum.create_date
,
    NULL::timestamp with time zone AS ausp_set_date
,
    aum.create_date AS aum_create_date
,
    aum.stop_date
,
    NULL::timestamp with time zone AS ausp_stop_date
,
    aum.stop_date AS aum_stop_date
,
    NULL::integer AS ausp_usr_message
,
    aum.title
,
    aum.message
,
    aum.deleted
,
    aum.read_date
,
    aum.pub
,
    aum.editor
,
    aum.edit_date
   
FROM (actor.usr_message aum
     
LEFT JOIN actor.usr_standing_penalty ausp 
    ON (
           (ausp.usr_message = aum.id)
     )
)
  
WHERE (
     (NOT aum.deleted)
   AND (ausp.id IS NULL)
);

Index - Schema actor


Table: actor.usr_org_unit_opt_in

actor.usr_org_unit_opt_in Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id org_unit integer UNIQUE#1 NOT NULL
actor.usr.id usr integer UNIQUE#1 NOT NULL
actor.usr.id staff integer NOT NULL
opt_in_ts timestamp with time zone NOT NULL DEFAULT now()
actor.workstation.id opt_in_ws integer NOT NULL
usr_org_unit_opt_in_staff_idx staff

Index - Schema actor


Table: actor.usr_password_reset

Self-serve password reset requests

actor.usr_password_reset Structure
F-Key Name Type Description
id serial PRIMARY KEY
uuid text NOT NULL
actor.usr.id usr bigint NOT NULL
request_time timestamp with time zone NOT NULL DEFAULT now()
has_been_reset boolean NOT NULL DEFAULT false
actor_usr_password_reset_has_been_reset_idx has_been_reset actor_usr_password_reset_request_time_idx request_time actor_usr_password_reset_usr_idx usr

Index - Schema actor


Table: actor.usr_privacy_waiver

actor.usr_privacy_waiver Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id usr bigint NOT NULL
name text NOT NULL
place_holds boolean DEFAULT false
pickup_holds boolean DEFAULT false
view_history boolean DEFAULT false
checkout_items boolean DEFAULT false
actor_usr_privacy_waiver_usr_idx usr

Index - Schema actor


Table: actor.usr_saved_search

actor.usr_saved_search Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
create_date timestamp with time zone NOT NULL DEFAULT now()
query_text text NOT NULL
query_type text NOT NULL DEFAULT 'URL'::text
target text NOT NULL

 

actor.usr_saved_search Constraints
Name Constraint
valid_query_text CHECK ((query_type = 'URL'::text))
valid_target CHECK ((target = ANY (ARRAY['record'::text, 'metarecord'::text, 'callnumber'::text])))

Index - Schema actor


Table: actor.usr_setting

User settings This table contains any arbitrary settings that a client program would like to save for a user.

actor.usr_setting Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id usr integer UNIQUE#1 NOT NULL
config.usr_setting_type.name name text UNIQUE#1 NOT NULL
value text NOT NULL
actor_usr_setting_usr_idx usr

Index - Schema actor


Table: actor.usr_standing_penalty

User standing penalties

actor.usr_standing_penalty Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id org_unit integer NOT NULL
actor.usr.id usr integer NOT NULL
config.standing_penalty.id standing_penalty integer NOT NULL
actor.usr.id staff integer
set_date timestamp with time zone DEFAULT now()
stop_date timestamp with time zone
actor.usr_message.id usr_message bigint
actor_usr_standing_penalty_staff_idx staff actor_usr_standing_penalty_usr_idx usr usr_standing_penalty_usr_message_idx usr_message

Index - Schema actor


Table: actor.workstation

actor.workstation Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
actor.org_unit.id owning_lib integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema actor


Table: actor.workstation_setting

actor.workstation_setting Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.workstation.id workstation integer UNIQUE#1 NOT NULL
config.workstation_setting_type.name name text UNIQUE#1 NOT NULL
value json NOT NULL
actor_workstation_setting_workstation_idx workstation

Index - Schema actor


Function: actor.address_alert_matches(billing_address integer, mailing_address text, post_code text, country text, state text, county text, city text, street2 text, street1 boolean, org_unit boolean)

Returns: SET OF address_alert

Language: SQL


SELECT *
FROM actor.address_alert
WHERE
    active
    AND owner IN (SELECT id FROM actor.org_unit_ancestors($1)) 
    AND (
        (NOT mailing_address AND NOT billing_address)
        OR (mailing_address AND $9)
        OR (billing_address AND $10)
    )
    AND (
            (
                match_all
                AND COALESCE($2, '') ~* COALESCE(street1,   '.*')
                AND COALESCE($3, '') ~* COALESCE(street2,   '.*')
                AND COALESCE($4, '') ~* COALESCE(city,      '.*')
                AND COALESCE($5, '') ~* COALESCE(county,    '.*')
                AND COALESCE($6, '') ~* COALESCE(state,     '.*')
                AND COALESCE($7, '') ~* COALESCE(country,   '.*')
                AND COALESCE($8, '') ~* COALESCE(post_code, '.*')
            ) OR (
                NOT match_all 
                AND (  
                       $2 ~* street1
                    OR $3 ~* street2
                    OR $4 ~* city
                    OR $5 ~* county
                    OR $6 ~* state
                    OR $7 ~* country
                    OR $8 ~* post_code
                )
            )
        )
    ORDER BY actor.org_unit_proximity(owner, $1)

Function: actor.approve_pending_address(pending_id integer)

Returns: bigint

Language: PLPGSQL

Replaces an address with a pending address. This is done by giving the pending address the ID of the old address. The replaced address is retained with -id.

DECLARE
    old_id INT;
BEGIN
    SELECT INTO old_id replaces FROM actor.usr_address where id = pending_id;
    IF old_id IS NULL THEN
        UPDATE actor.usr_address SET pending = 'f' WHERE id = pending_id;
        RETURN pending_id;
    END IF;
    -- address replaces an existing address
    DELETE FROM actor.usr_address WHERE id = -old_id;
    UPDATE actor.usr_address SET id = -id WHERE id = old_id;
    UPDATE actor.usr_address SET replaces = NULL, id = old_id, pending = 'f' WHERE id = pending_id;
    RETURN old_id;
END

Function: actor.au_updated()

Returns: trigger

Language: PLPGSQL

BEGIN
    NEW.last_update_time := now();
	RETURN NEW;
END;

Function: actor.calculate_system_penalties(context_org integer, match_user integer)

Returns: SET OF usr_standing_penalty

Language: PLPGSQL

DECLARE
    user_object         actor.usr%ROWTYPE;
    new_sp_row          actor.usr_standing_penalty%ROWTYPE;
    existing_sp_row     actor.usr_standing_penalty%ROWTYPE;
    collections_fines   permission.grp_penalty_threshold%ROWTYPE;
    max_fines           permission.grp_penalty_threshold%ROWTYPE;
    max_overdue         permission.grp_penalty_threshold%ROWTYPE;
    max_items_out       permission.grp_penalty_threshold%ROWTYPE;
    max_lost            permission.grp_penalty_threshold%ROWTYPE;
    max_longoverdue     permission.grp_penalty_threshold%ROWTYPE;
    tmp_grp             INT;
    items_overdue       INT;
    items_out           INT;
    items_lost          INT;
    items_longoverdue   INT;
    context_org_list    INT[];
    current_fines        NUMERIC(8,2) := 0.0;
    tmp_fines            NUMERIC(8,2);
    tmp_groc            RECORD;
    tmp_circ            RECORD;
    tmp_org             actor.org_unit%ROWTYPE;
    tmp_penalty         config.standing_penalty%ROWTYPE;
    tmp_depth           INTEGER;
BEGIN
    SELECT INTO user_object * FROM actor.usr WHERE id = match_user;

    -- Max fines
    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;

    -- Fail if the user has a high fine balance
    LOOP
        tmp_grp := user_object.profile;
        LOOP
            SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 1 AND org_unit = tmp_org.id;

            IF max_fines.threshold IS NULL THEN
                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
            ELSE
                EXIT;
            END IF;

            IF tmp_grp IS NULL THEN
                EXIT;
            END IF;
        END LOOP;

        IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
            EXIT;
        END IF;

        SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;

    END LOOP;

    IF max_fines.threshold IS NOT NULL THEN

        RETURN QUERY
            SELECT  *
              FROM  actor.usr_standing_penalty
              WHERE usr = match_user
                    AND org_unit = max_fines.org_unit
                    AND (stop_date IS NULL or stop_date > NOW())
                    AND standing_penalty = 1;

        SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( max_fines.org_unit );

        SELECT  SUM(f.balance_owed) INTO current_fines
          FROM  money.materialized_billable_xact_summary f
                JOIN (
                    SELECT  r.id
                      FROM  booking.reservation r
                      WHERE r.usr = match_user
                            AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
                            AND xact_finish IS NULL
                                UNION ALL
                    SELECT  g.id
                      FROM  money.grocery g
                      WHERE g.usr = match_user
                            AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
                            AND xact_finish IS NULL
                                UNION ALL
                    SELECT  circ.id
                      FROM  action.circulation circ
                      WHERE circ.usr = match_user
                            AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
                            AND xact_finish IS NULL ) l USING (id);

        IF current_fines >= max_fines.threshold THEN
            new_sp_row.usr := match_user;
            new_sp_row.org_unit := max_fines.org_unit;
            new_sp_row.standing_penalty := 1;
            RETURN NEXT new_sp_row;
        END IF;
    END IF;

    -- Start over for max overdue
    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;

    -- Fail if the user has too many overdue items
    LOOP
        tmp_grp := user_object.profile;
        LOOP

            SELECT * INTO max_overdue FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 2 AND org_unit = tmp_org.id;

            IF max_overdue.threshold IS NULL THEN
                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
            ELSE
                EXIT;
            END IF;

            IF tmp_grp IS NULL THEN
                EXIT;
            END IF;
        END LOOP;

        IF max_overdue.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
            EXIT;
        END IF;

        SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;

    END LOOP;

    IF max_overdue.threshold IS NOT NULL THEN

        RETURN QUERY
            SELECT  *
              FROM  actor.usr_standing_penalty
              WHERE usr = match_user
                    AND org_unit = max_overdue.org_unit
                    AND (stop_date IS NULL or stop_date > NOW())
                    AND standing_penalty = 2;

        SELECT  INTO items_overdue COUNT(*)
          FROM  action.circulation circ
                JOIN  actor.org_unit_full_path( max_overdue.org_unit ) fp ON (circ.circ_lib = fp.id)
          WHERE circ.usr = match_user
            AND circ.checkin_time IS NULL
            AND circ.due_date < NOW()
            AND (circ.stop_fines = 'MAXFINES' OR circ.stop_fines IS NULL);

        IF items_overdue >= max_overdue.threshold::INT THEN
            new_sp_row.usr := match_user;
            new_sp_row.org_unit := max_overdue.org_unit;
            new_sp_row.standing_penalty := 2;
            RETURN NEXT new_sp_row;
        END IF;
    END IF;

    -- Start over for max out
    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;

    -- Fail if the user has too many checked out items
    LOOP
        tmp_grp := user_object.profile;
        LOOP
            SELECT * INTO max_items_out FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 3 AND org_unit = tmp_org.id;

            IF max_items_out.threshold IS NULL THEN
                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
            ELSE
                EXIT;
            END IF;

            IF tmp_grp IS NULL THEN
                EXIT;
            END IF;
        END LOOP;

        IF max_items_out.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
            EXIT;
        END IF;

        SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;

    END LOOP;


    -- Fail if the user has too many items checked out
    IF max_items_out.threshold IS NOT NULL THEN

        RETURN QUERY
            SELECT  *
              FROM  actor.usr_standing_penalty
              WHERE usr = match_user
                    AND org_unit = max_items_out.org_unit
                    AND (stop_date IS NULL or stop_date > NOW())
                    AND standing_penalty = 3;

        SELECT  INTO items_out COUNT(*)
          FROM  action.circulation circ
                JOIN  actor.org_unit_full_path( max_items_out.org_unit ) fp ON (circ.circ_lib = fp.id)
          WHERE circ.usr = match_user
                AND circ.checkin_time IS NULL
                AND (circ.stop_fines IN (
                    SELECT 'MAXFINES'::TEXT
                    UNION ALL
                    SELECT 'LONGOVERDUE'::TEXT
                    UNION ALL
                    SELECT 'LOST'::TEXT
                    WHERE 'true' ILIKE
                    (
                        SELECT CASE
                            WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.tally_lost', circ.circ_lib)) ILIKE 'true' THEN 'true'
                            ELSE 'false'
                        END
                    )
                    UNION ALL
                    SELECT 'CLAIMSRETURNED'::TEXT
                    WHERE 'false' ILIKE
                    (
                        SELECT CASE
                            WHEN (SELECT value FROM actor.org_unit_ancestor_setting('circ.do_not_tally_claims_returned', circ.circ_lib)) ILIKE 'true' THEN 'true'
                            ELSE 'false'
                        END
                    )
                    ) OR circ.stop_fines IS NULL)
                AND xact_finish IS NULL;

           IF items_out >= max_items_out.threshold::INT THEN
            new_sp_row.usr := match_user;
            new_sp_row.org_unit := max_items_out.org_unit;
            new_sp_row.standing_penalty := 3;
            RETURN NEXT new_sp_row;
           END IF;
    END IF;

    -- Start over for max lost
    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;

    -- Fail if the user has too many lost items
    LOOP
        tmp_grp := user_object.profile;
        LOOP

            SELECT * INTO max_lost FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 5 AND org_unit = tmp_org.id;

            IF max_lost.threshold IS NULL THEN
                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
            ELSE
                EXIT;
            END IF;

            IF tmp_grp IS NULL THEN
                EXIT;
            END IF;
        END LOOP;

        IF max_lost.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
            EXIT;
        END IF;

        SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;

    END LOOP;

    IF max_lost.threshold IS NOT NULL THEN

        RETURN QUERY
            SELECT  *
            FROM  actor.usr_standing_penalty
            WHERE usr = match_user
                AND org_unit = max_lost.org_unit
                AND (stop_date IS NULL or stop_date > NOW())
                AND standing_penalty = 5;

        SELECT  INTO items_lost COUNT(*)
        FROM  action.circulation circ
            JOIN  actor.org_unit_full_path( max_lost.org_unit ) fp ON (circ.circ_lib = fp.id)
        WHERE circ.usr = match_user
            AND circ.checkin_time IS NULL
            AND (circ.stop_fines = 'LOST')
            AND xact_finish IS NULL;

        IF items_lost >= max_lost.threshold::INT AND 0 < max_lost.threshold::INT THEN
            new_sp_row.usr := match_user;
            new_sp_row.org_unit := max_lost.org_unit;
            new_sp_row.standing_penalty := 5;
            RETURN NEXT new_sp_row;
        END IF;
    END IF;

    -- Start over for max longoverdue
    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;

    -- Fail if the user has too many longoverdue items
    LOOP
        tmp_grp := user_object.profile;
        LOOP

            SELECT * INTO max_longoverdue 
                FROM permission.grp_penalty_threshold 
                WHERE grp = tmp_grp AND 
                    penalty = 35 AND 
                    org_unit = tmp_org.id;

            IF max_longoverdue.threshold IS NULL THEN
                SELECT parent INTO tmp_grp 
                    FROM permission.grp_tree WHERE id = tmp_grp;
            ELSE
                EXIT;
            END IF;

            IF tmp_grp IS NULL THEN
                EXIT;
            END IF;
        END LOOP;

        IF max_longoverdue.threshold IS NOT NULL 
                OR tmp_org.parent_ou IS NULL THEN
            EXIT;
        END IF;

        SELECT INTO tmp_org * FROM actor.org_unit WHERE id = tmp_org.parent_ou;

    END LOOP;

    IF max_longoverdue.threshold IS NOT NULL THEN

        RETURN QUERY
            SELECT  *
            FROM  actor.usr_standing_penalty
            WHERE usr = match_user
                AND org_unit = max_longoverdue.org_unit
                AND (stop_date IS NULL or stop_date > NOW())
                AND standing_penalty = 35;

        SELECT INTO items_longoverdue COUNT(*)
        FROM action.circulation circ
            JOIN actor.org_unit_full_path( max_longoverdue.org_unit ) fp 
                ON (circ.circ_lib = fp.id)
        WHERE circ.usr = match_user
            AND circ.checkin_time IS NULL
            AND (circ.stop_fines = 'LONGOVERDUE')
            AND xact_finish IS NULL;

        IF items_longoverdue >= max_longoverdue.threshold::INT 
                AND 0 < max_longoverdue.threshold::INT THEN
            new_sp_row.usr := match_user;
            new_sp_row.org_unit := max_longoverdue.org_unit;
            new_sp_row.standing_penalty := 35;
            RETURN NEXT new_sp_row;
        END IF;
    END IF;


    -- Start over for collections warning
    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;

    -- Fail if the user has a collections-level fine balance
    LOOP
        tmp_grp := user_object.profile;
        LOOP
            SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 4 AND org_unit = tmp_org.id;

            IF max_fines.threshold IS NULL THEN
                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
            ELSE
                EXIT;
            END IF;

            IF tmp_grp IS NULL THEN
                EXIT;
            END IF;
        END LOOP;

        IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
            EXIT;
        END IF;

        SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;

    END LOOP;

    IF max_fines.threshold IS NOT NULL THEN

        RETURN QUERY
            SELECT  *
              FROM  actor.usr_standing_penalty
              WHERE usr = match_user
                    AND org_unit = max_fines.org_unit
                    AND (stop_date IS NULL or stop_date > NOW())
                    AND standing_penalty = 4;

        SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( max_fines.org_unit );

        SELECT  SUM(f.balance_owed) INTO current_fines
          FROM  money.materialized_billable_xact_summary f
                JOIN (
                    SELECT  r.id
                      FROM  booking.reservation r
                      WHERE r.usr = match_user
                            AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
                            AND r.xact_finish IS NULL
                                UNION ALL
                    SELECT  g.id
                      FROM  money.grocery g
                      WHERE g.usr = match_user
                            AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
                            AND g.xact_finish IS NULL
                                UNION ALL
                    SELECT  circ.id
                      FROM  action.circulation circ
                      WHERE circ.usr = match_user
                            AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
                            AND circ.xact_finish IS NULL ) l USING (id);

        IF current_fines >= max_fines.threshold THEN
            new_sp_row.usr := match_user;
            new_sp_row.org_unit := max_fines.org_unit;
            new_sp_row.standing_penalty := 4;
            RETURN NEXT new_sp_row;
        END IF;
    END IF;

    -- Start over for in collections
    SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;

    -- Remove the in-collections penalty if the user has paid down enough
    -- This penalty is different, because this code is not responsible for creating 
    -- new in-collections penalties, only for removing them
    LOOP
        tmp_grp := user_object.profile;
        LOOP
            SELECT * INTO max_fines FROM permission.grp_penalty_threshold WHERE grp = tmp_grp AND penalty = 30 AND org_unit = tmp_org.id;

            IF max_fines.threshold IS NULL THEN
                SELECT parent INTO tmp_grp FROM permission.grp_tree WHERE id = tmp_grp;
            ELSE
                EXIT;
            END IF;

            IF tmp_grp IS NULL THEN
                EXIT;
            END IF;
        END LOOP;

        IF max_fines.threshold IS NOT NULL OR tmp_org.parent_ou IS NULL THEN
            EXIT;
        END IF;

        SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;

    END LOOP;

    IF max_fines.threshold IS NOT NULL THEN

        SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( max_fines.org_unit );

        -- first, see if the user had paid down to the threshold
        SELECT  SUM(f.balance_owed) INTO current_fines
          FROM  money.materialized_billable_xact_summary f
                JOIN (
                    SELECT  r.id
                      FROM  booking.reservation r
                      WHERE r.usr = match_user
                            AND r.pickup_lib IN (SELECT * FROM unnest(context_org_list))
                            AND r.xact_finish IS NULL
                                UNION ALL
                    SELECT  g.id
                      FROM  money.grocery g
                      WHERE g.usr = match_user
                            AND g.billing_location IN (SELECT * FROM unnest(context_org_list))
                            AND g.xact_finish IS NULL
                                UNION ALL
                    SELECT  circ.id
                      FROM  action.circulation circ
                      WHERE circ.usr = match_user
                            AND circ.circ_lib IN (SELECT * FROM unnest(context_org_list))
                            AND circ.xact_finish IS NULL ) l USING (id);

        IF current_fines IS NULL OR current_fines <= max_fines.threshold THEN
            -- patron has paid down enough

            SELECT INTO tmp_penalty * FROM config.standing_penalty WHERE id = 30;

            IF tmp_penalty.org_depth IS NOT NULL THEN

                -- since this code is not responsible for applying the penalty, it can't 
                -- guarantee the current context org will match the org at which the penalty 
                --- was applied.  search up the org tree until we hit the configured penalty depth
                SELECT INTO tmp_org * FROM actor.org_unit WHERE id = context_org;
                SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;

                WHILE tmp_depth >= tmp_penalty.org_depth LOOP

                    RETURN QUERY
                        SELECT  *
                          FROM  actor.usr_standing_penalty
                          WHERE usr = match_user
                                AND org_unit = tmp_org.id
                                AND (stop_date IS NULL or stop_date > NOW())
                                AND standing_penalty = 30;

                    IF tmp_org.parent_ou IS NULL THEN
                        EXIT;
                    END IF;

                    SELECT * INTO tmp_org FROM actor.org_unit WHERE id = tmp_org.parent_ou;
                    SELECT INTO tmp_depth depth FROM actor.org_unit_type WHERE id = tmp_org.ou_type;
                END LOOP;

            ELSE

                -- no penalty depth is defined, look for exact matches

                RETURN QUERY
                    SELECT  *
                      FROM  actor.usr_standing_penalty
                      WHERE usr = match_user
                            AND org_unit = max_fines.org_unit
                            AND (stop_date IS NULL or stop_date > NOW())
                            AND standing_penalty = 30;
            END IF;
    
        END IF;

    END IF;

    RETURN;
END;

Function: actor.change_password(pw_type integer, new_pw text, user_id text)

Returns: void

Language: PLPGSQL

Allows setting a salted password for a user by passing actor.usr id and the text of the password.

DECLARE
    new_salt TEXT;
BEGIN
    SELECT actor.create_salt(pw_type) INTO new_salt;

    IF pw_type = 'main' THEN
        -- Only 'main' passwords are required to have
        -- the extra layer of MD5 hashing.
        PERFORM actor.set_passwd(
            user_id, pw_type, md5(new_salt || md5(new_pw)), new_salt
        );

    ELSE
        PERFORM actor.set_passwd(user_id, pw_type, new_pw, new_salt);
    END IF;
END;

Function: actor.create_salt(pw_type text)

Returns: text

Language: PLPGSQL

DECLARE
    type_row actor.passwd_type%ROWTYPE;
BEGIN
    /* Returns a new salt based on the passwd_type encryption settings.
     * Returns NULL If the password type is not crypt()'ed.
     */

    SELECT INTO type_row * FROM actor.passwd_type WHERE code = pw_type;

    IF NOT FOUND THEN
        RETURN EXCEPTION 'No such password type: %', pw_type;
    END IF;

    IF type_row.iter_count IS NULL THEN
        -- This password type is unsalted.  That's OK.
        RETURN NULL;
    END IF;

    RETURN gen_salt(type_row.crypt_algo, type_row.iter_count);
END;

Function: actor.crypt_pw_insert()

Returns: trigger

Language: PLPGSQL

	BEGIN
		NEW.passwd = MD5( NEW.passwd );
		RETURN NEW;
	END;

Function: actor.crypt_pw_update()

Returns: trigger

Language: PLPGSQL

	BEGIN
		IF NEW.passwd <> OLD.passwd THEN
			NEW.passwd = MD5( NEW.passwd );
		END IF;
		RETURN NEW;
	END;

Function: actor.get_cascade_setting(workstation_id text, user_id integer, org_id integer, setting_name integer)

Returns: cascade_setting_summary

Language: PLPGSQL

DECLARE
    setting_value JSON;
    summary actor.cascade_setting_summary;
    org_setting_type config.org_unit_setting_type%ROWTYPE;
BEGIN

    summary.name := setting_name;

    -- Collect the org setting type status first in case we exit early.
    -- The existance of an org setting type is not considered
    -- privileged information.
    SELECT INTO org_setting_type * 
        FROM config.org_unit_setting_type WHERE name = setting_name;
    IF FOUND THEN
        summary.has_org_setting := TRUE;
    ELSE
        summary.has_org_setting := FALSE;
    END IF;

    -- User and workstation settings have the same priority.
    -- Start with user settings since that's the simplest code path.
    -- The workstation_id is ignored if no user_id is provided.
    IF user_id IS NOT NULL THEN

        SELECT INTO summary.value value FROM actor.usr_setting
            WHERE usr = user_id AND name = setting_name;

        IF FOUND THEN
            -- if we have a value, we have a setting type
            summary.has_user_setting := TRUE;

            IF workstation_id IS NOT NULL THEN
                -- Only inform the caller about the workstation
                -- setting type disposition when a workstation id is
                -- provided.  Otherwise, it's NULL to indicate UNKNOWN.
                summary.has_workstation_setting := FALSE;
            END IF;

            RETURN summary;
        END IF;

        -- no user setting value, but a setting type may exist
        SELECT INTO summary.has_user_setting EXISTS (
            SELECT TRUE FROM config.usr_setting_type 
            WHERE name = setting_name
        );

        IF workstation_id IS NOT NULL THEN 

            IF NOT summary.has_user_setting THEN
                -- A workstation setting type may only exist when a user
                -- setting type does not.

                SELECT INTO summary.value value 
                    FROM actor.workstation_setting         
                    WHERE workstation = workstation_id AND name = setting_name;

                IF FOUND THEN
                    -- if we have a value, we have a setting type
                    summary.has_workstation_setting := TRUE;
                    RETURN summary;
                END IF;

                -- no value, but a setting type may exist
                SELECT INTO summary.has_workstation_setting EXISTS (
                    SELECT TRUE FROM config.workstation_setting_type 
                    WHERE name = setting_name
                );
            END IF;

            -- Finally make use of the workstation to determine the org
            -- unit if none is provided.
            IF org_id IS NULL AND summary.has_org_setting THEN
                SELECT INTO org_id owning_lib 
                    FROM actor.workstation WHERE id = workstation_id;
            END IF;
        END IF;
    END IF;

    -- Some org unit settings are protected by a view permission.
    -- First see if we have any data that needs protecting, then 
    -- check the permission if needed.

    IF NOT summary.has_org_setting THEN
        RETURN summary;
    END IF;

    -- avoid putting the value into the summary until we confirm
    -- the value should be visible to the caller.
    SELECT INTO setting_value value 
        FROM actor.org_unit_ancestor_setting(setting_name, org_id);

    IF NOT FOUND THEN
        -- No value found -- perm check is irrelevant.
        RETURN summary;
    END IF;

    IF org_setting_type.view_perm IS NOT NULL THEN

        IF user_id IS NULL THEN
            RAISE NOTICE 'Perm check required but no user_id provided';
            RETURN summary;
        END IF;

        IF NOT permission.usr_has_perm(
            user_id, (SELECT code FROM permission.perm_list 
                WHERE id = org_setting_type.view_perm), org_id) 
        THEN
            RAISE NOTICE 'Perm check failed for user % on %',
                user_id, org_setting_type.view_perm;
            RETURN summary;
        END IF;
    END IF;

    -- Perm check succeeded or was not necessary.
    summary.value := setting_value;
    RETURN summary;
END;

Function: actor.get_cascade_setting_batch(workstation_id text[], user_id integer, org_id integer, setting_names integer)

Returns: SET OF cascade_setting_summary

Language: PLPGSQL

-- Returns a row per setting matching the setting name order.  If no 
-- value is applied, NULL is returned to retain name-response ordering.
DECLARE
    setting_name TEXT;
    summary actor.cascade_setting_summary;
BEGIN
    FOREACH setting_name IN ARRAY setting_names LOOP
        SELECT INTO summary * FROM actor.get_cascade_setting(
            setting_Name, org_id, user_id, workstation_id);
        RETURN NEXT summary;
    END LOOP;
END;

Function: actor.get_salt(pw_type integer, pw_usr text)

Returns: text

Language: PLPGSQL

DECLARE
    pw_salt TEXT;
    type_row actor.passwd_type%ROWTYPE;
BEGIN
    /* Returns the salt for the requested user + type.  If the password 
     * type of "main" is requested and no password exists in actor.passwd, 
     * the user's existing password is migrated and the new salt is returned.
     * Returns NULL if the password type is not crypt'ed (iter_count is NULL).
     */

    SELECT INTO pw_salt salt FROM actor.passwd 
        WHERE usr = pw_usr AND passwd_type = pw_type;

    IF FOUND THEN
        RETURN pw_salt;
    END IF;

    IF pw_type = 'main' THEN
        -- Main password has not yet been migrated. 
        -- Do it now and return the newly created salt.
        RETURN actor.migrate_passwd(pw_usr);
    END IF;

    -- We have no salt to return.  actor.create_salt() needed.
    RETURN NULL;
END;

Function: actor.insert_usr_activity(ehow integer, ewhat text, ewho text, usr text)

Returns: SET OF usr_activity

Language: PLPGSQL

DECLARE
    new_row actor.usr_activity%ROWTYPE;
BEGIN
    SELECT id INTO new_row.etype FROM actor.usr_activity_get_type(ewho, ewhat, ehow);
    IF FOUND THEN
        new_row.usr := usr;
        INSERT INTO actor.usr_activity (usr, etype) 
            VALUES (usr, new_row.etype)
            RETURNING * INTO new_row;
        RETURN NEXT new_row;
    END IF;
END;

Function: actor.migrate_passwd(pw_usr integer)

Returns: text

Language: PLPGSQL

DECLARE
    pw_salt TEXT;
    usr_row actor.usr%ROWTYPE;
BEGIN
    /* Migrates legacy actor.usr.passwd value to actor.passwd with 
     * a password type 'main' and returns the new salt.  For backwards
     * compatibility with existing CHAP-style API's, we perform a 
     * layer of intermediate MD5(MD5()) hashing.  This is intermediate
     * hashing is not required of other passwords.
     */

    -- Avoid calling get_salt() here, because it may result in a 
    -- migrate_passwd() call, creating a loop.
    SELECT INTO pw_salt salt FROM actor.passwd 
        WHERE usr = pw_usr AND passwd_type = 'main';

    -- Only migrate passwords that have not already been migrated.
    IF FOUND THEN
        RETURN pw_salt;
    END IF;

    SELECT INTO usr_row * FROM actor.usr WHERE id = pw_usr;

    pw_salt := actor.create_salt('main');

    PERFORM actor.set_passwd(
        pw_usr, 'main', MD5(pw_salt || usr_row.passwd), pw_salt);

    -- clear the existing password
    UPDATE actor.usr SET passwd = '' WHERE id = usr_row.id;

    RETURN pw_salt;
END;

Function: actor.org_unit_ancestor_at_depth(integer, integer)

Returns: org_unit

Language: SQL

	SELECT	a.*
	  FROM	actor.org_unit a
	  WHERE	id = ( SELECT FIRST(x.id)
	  		 FROM	actor.org_unit_ancestors($1) x
			   	JOIN actor.org_unit_type y
					ON x.ou_type = y.id AND y.depth = $2);

Function: actor.org_unit_ancestor_setting(org_id text, setting_name integer)

Returns: SET OF org_unit_setting

Language: PLPGSQL

Search "up" the org_unit tree until we find the first occurrence of an org_unit_setting with the given name.

DECLARE
    setting RECORD;
    cur_org INT;
BEGIN
    cur_org := org_id;
    LOOP
        SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
        IF FOUND THEN
            RETURN NEXT setting;
            EXIT;
        END IF;
        SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
        EXIT WHEN cur_org IS NULL;
    END LOOP;
    RETURN;
END;

Function: actor.org_unit_ancestor_setting_batch(setting_names integer, org_id text[])

Returns: SET OF org_unit_setting

Language: PLPGSQL

For each setting name passed, search "up" the org_unit tree until we find the first occurrence of an org_unit_setting with the given name.

DECLARE
    setting RECORD;
    setting_name TEXT;
    cur_org INT;
BEGIN
    FOREACH setting_name IN ARRAY setting_names
    LOOP
        cur_org := org_id;
        LOOP
            SELECT INTO setting * FROM actor.org_unit_setting WHERE org_unit = cur_org AND name = setting_name;
            IF FOUND THEN
                RETURN NEXT setting;
                EXIT;
            END IF;
            SELECT INTO cur_org parent_ou FROM actor.org_unit WHERE id = cur_org;
            EXIT WHEN cur_org IS NULL;
        END LOOP;
    END LOOP;
    RETURN;
END;

Function: actor.org_unit_ancestor_setting_batch_by_org(org_ids text, setting_name integer[])

Returns: SET OF org_unit_setting

Language: PLPGSQL

DECLARE
    setting RECORD;
    org_id INTEGER;
BEGIN
    /*  Returns one actor.org_unit_setting row per org unit ID provided.
        When no setting exists for a given org unit, the setting row
        will contain all empty values. */
    FOREACH org_id IN ARRAY org_ids LOOP
        SELECT INTO setting * FROM 
            actor.org_unit_ancestor_setting(setting_name, org_id);
        RETURN NEXT setting;
    END LOOP;
    RETURN;
END;

Function: actor.org_unit_ancestors(integer)

Returns: SET OF org_unit

Language: SQL

    WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
            SELECT $1, 0
        UNION
            SELECT ou.parent_ou, ouad.distance+1
            FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON (ou.id = ouad.id)
            WHERE ou.parent_ou IS NOT NULL
    )
    SELECT ou.* FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad USING (id) ORDER BY ouad.distance DESC;

Function: actor.org_unit_ancestors_distance(distance integer)

Returns: SET OF record

Language: SQL

    WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
            SELECT $1, 0
        UNION
            SELECT ou.parent_ou, ouad.distance+1
            FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON (ou.id = ouad.id)
            WHERE ou.parent_ou IS NOT NULL
    )
    SELECT * FROM org_unit_ancestors_distance;

Function: actor.org_unit_combined_ancestors(integer, integer)

Returns: SET OF org_unit

Language: SQL

	SELECT	*
	  FROM	actor.org_unit_ancestors($1)
			UNION
	SELECT	*
	  FROM	actor.org_unit_ancestors($2);

Function: actor.org_unit_common_ancestors(integer, integer)

Returns: SET OF org_unit

Language: SQL

	SELECT	*
	  FROM	actor.org_unit_ancestors($1)
			INTERSECT
	SELECT	*
	  FROM	actor.org_unit_ancestors($2);

Function: actor.org_unit_descendants(integer)

Returns: SET OF org_unit

Language: SQL

    WITH RECURSIVE descendant_depth AS (
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
          FROM  actor.org_unit ou
                JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
          WHERE ou.id = $1
            UNION ALL
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
          FROM  actor.org_unit ou
                JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
                JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
    ) SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);

Function: actor.org_unit_descendants(integer, integer)

Returns: SET OF org_unit

Language: SQL

    WITH RECURSIVE descendant_depth AS (
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
          FROM  actor.org_unit ou
                JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
                JOIN anscestor_depth ad ON (ad.id = ou.id)
          WHERE ad.depth = $2
            UNION ALL
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
          FROM  actor.org_unit ou
                JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
                JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
    ), anscestor_depth AS (
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
          FROM  actor.org_unit ou
                JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
          WHERE ou.id = $1
            UNION ALL
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
          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 ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id);

Function: actor.org_unit_descendants_distance(distance integer)

Returns: SET OF record

Language: SQL

    WITH RECURSIVE org_unit_descendants_distance(id, distance) AS (
            SELECT $1, 0
        UNION
            SELECT ou.id, oudd.distance+1
            FROM actor.org_unit ou JOIN org_unit_descendants_distance oudd ON (ou.parent_ou = oudd.id)
    )
    SELECT * FROM org_unit_descendants_distance;

Function: actor.org_unit_full_path(integer)

Returns: SET OF org_unit

Language: SQL

    SELECT  aou.*
      FROM  actor.org_unit AS aou
            JOIN (
                (SELECT au.id, t.depth FROM actor.org_unit_ancestors($1) AS au JOIN actor.org_unit_type t ON (au.ou_type = t.id))
                    UNION
                (SELECT au.id, t.depth FROM actor.org_unit_descendants($1) AS au JOIN actor.org_unit_type t ON (au.ou_type = t.id))
            ) AS ad ON (aou.id=ad.id)
      ORDER BY ad.depth;

Function: actor.org_unit_full_path(integer, integer)

Returns: SET OF org_unit

Language: SQL

	SELECT	* FROM actor.org_unit_full_path((actor.org_unit_ancestor_at_depth($1, $2)).id)

Function: actor.org_unit_parent_protect()

Returns: trigger

Language: PLPGSQL

	DECLARE
		current_aou actor.org_unit%ROWTYPE;
		seen_ous    INT[];
		depth_count INT;
	BEGIN
		current_aou := NEW;
		depth_count := 0;
		seen_ous := ARRAY[NEW.id];

		IF (TG_OP = 'UPDATE') THEN
			IF (NEW.parent_ou IS NOT DISTINCT FROM OLD.parent_ou) THEN
				RETURN NEW; -- Doing an UPDATE with no change, just return it
			END IF;
		END IF;

		LOOP
			IF current_aou.parent_ou IS NULL THEN -- Top of the org tree?
				RETURN NEW; -- No loop. Carry on.
			END IF;
			IF current_aou.parent_ou = ANY(seen_ous) THEN -- Parent is one we have seen?
				RAISE 'OU LOOP: Saw % twice', current_aou.parent_ou; -- LOOP! ABORT!
			END IF;
			-- Get the next one!
			SELECT INTO current_aou * FROM actor.org_unit WHERE id = current_aou.parent_ou;
			seen_ous := seen_ous || current_aou.id;
			depth_count := depth_count + 1;
			IF depth_count = 100 THEN
				RAISE 'OU CHECK TOO DEEP';
			END IF;
		END LOOP;

		RETURN NEW;
	END;

Function: actor.org_unit_prox_update()

Returns: trigger

Language: PLPGSQL

BEGIN


IF TG_OP = 'DELETE' THEN

    DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);

END IF;

IF TG_OP = 'UPDATE' THEN

    IF NEW.parent_ou <> OLD.parent_ou THEN

        DELETE FROM actor.org_unit_proximity WHERE (from_org = OLD.id or to_org= OLD.id);
            INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
            SELECT  l.id, r.id, actor.org_unit_proximity(l.id,r.id)
                FROM  actor.org_unit l, actor.org_unit r
                WHERE (l.id = NEW.id or r.id = NEW.id);

    END IF;

END IF;

IF TG_OP = 'INSERT' THEN

     INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
     SELECT  l.id, r.id, actor.org_unit_proximity(l.id,r.id)
         FROM  actor.org_unit l, actor.org_unit r
         WHERE (l.id = NEW.id or r.id = NEW.id);

END IF;

RETURN null;

END;

Function: actor.org_unit_proximity(integer, integer)

Returns: integer

Language: SQL

	SELECT COUNT(id)::INT FROM (
		SELECT id FROM actor.org_unit_combined_ancestors($1, $2)
			EXCEPT
		SELECT id FROM actor.org_unit_common_ancestors($1, $2)
	) z;

Function: actor.org_unit_simple_path(integer, integer)

Returns: integer[]

Language: SQL

    WITH RECURSIVE descendant_depth(id, path) AS (
        SELECT  aou.id,
                ARRAY[aou.id]
          FROM  actor.org_unit aou
                JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
          WHERE aou.id = $2
            UNION ALL
        SELECT  aou.id,
                dd.path || ARRAY[aou.id]
          FROM  actor.org_unit aou
                JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
                JOIN descendant_depth dd ON (dd.id = aou.parent_ou)
    ) SELECT dd.path
        FROM actor.org_unit aou
        JOIN descendant_depth dd USING (id)
        WHERE aou.id = $1 ORDER BY dd.path;

Function: actor.permit_remoteauth(userid text, profile_name bigint)

Returns: text

Language: PLPGSQL

DECLARE
    usr               actor.usr%ROWTYPE;
    profile           config.remoteauth_profile%ROWTYPE;
    perm              TEXT;
    context_org_list  INT[];
    home_prox         INT;
    block             TEXT;
    penalty_count     INT;
BEGIN

    SELECT INTO usr * FROM actor.usr WHERE id = userid AND NOT deleted;
    IF usr IS NULL THEN
        RETURN 'not_found';
    END IF;

    IF usr.barred IS TRUE THEN
        RETURN 'blocked';
    END IF;

    SELECT INTO profile * FROM config.remoteauth_profile WHERE name = profile_name;
    SELECT INTO context_org_list ARRAY_AGG(id) FROM actor.org_unit_full_path( profile.context_org );

    -- user's home library must be within the context org
    IF profile.restrict_to_org IS TRUE AND usr.home_ou NOT IN (SELECT * FROM UNNEST(context_org_list)) THEN
        RETURN 'not_found';
    END IF;

    SELECT INTO perm code FROM permission.perm_list WHERE id = profile.perm;
    IF permission.usr_has_perm(usr.id, perm, profile.context_org) IS FALSE THEN
        RETURN 'not_found';
    END IF;
    
    IF usr.expire_date < NOW() AND profile.allow_expired IS FALSE THEN
        RETURN 'expired';
    END IF;

    IF usr.active IS FALSE AND profile.allow_inactive IS FALSE THEN
        RETURN 'blocked';
    END IF;

    -- Proximity of user's home_ou to context_org to see if penalties should be ignored.
    SELECT INTO home_prox prox FROM actor.org_unit_proximity WHERE from_org = usr.home_ou AND to_org = profile.context_org;

    -- Loop through the block list to see if the user has any matching penalties.
    IF profile.block_list IS NOT NULL THEN
        FOR block IN SELECT UNNEST(STRING_TO_ARRAY(profile.block_list, '|')) LOOP
            SELECT INTO penalty_count COUNT(DISTINCT csp.*)
                FROM  actor.usr_standing_penalty usp
                        JOIN config.standing_penalty csp ON (csp.id = usp.standing_penalty)
                WHERE usp.usr = usr.id
                        AND usp.org_unit IN ( SELECT * FROM UNNEST(context_org_list) )
                        AND ( usp.stop_date IS NULL or usp.stop_date > NOW() )
                        AND ( csp.ignore_proximity IS NULL OR csp.ignore_proximity < home_prox )
                        AND csp.block_list ~ block;
            IF penalty_count > 0 THEN
                -- User has penalties that match this block, so auth is not permitted.
                -- Don't bother testing the rest of the block list.
                RETURN 'blocked';
            END IF;
        END LOOP;
    END IF;

    -- User has passed all tests.
    RETURN 'success';

END;

Function: actor.purge_usr_activity_by_type(act_type integer)

Returns: void

Language: PLPGSQL

DECLARE
    cur_usr INTEGER;
BEGIN
    FOR cur_usr IN SELECT DISTINCT(usr)
        FROM actor.usr_activity WHERE etype = act_type LOOP
        DELETE FROM actor.usr_activity WHERE id IN (
            SELECT id
            FROM actor.usr_activity
            WHERE usr = cur_usr AND etype = act_type
            ORDER BY event_time DESC OFFSET 1
        );

    END LOOP;
END 

Function: actor.restrict_usr_message_limited()

Returns: trigger

Language: PLPGSQL

BEGIN
    IF TG_OP = 'UPDATE' THEN
        UPDATE actor.usr_message
        SET    read_date = NEW.read_date,
               deleted   = NEW.deleted
        WHERE  id = NEW.id;
        RETURN NEW;
    END IF;
    RETURN NULL;
END;

Function: actor.set_passwd(new_salt integer, new_pass text, pw_type text, pw_usr text)

Returns: boolean

Language: PLPGSQL

DECLARE
    pw_salt TEXT;
    pw_text TEXT;
BEGIN
    /* Sets the password value, creating a new actor.passwd row if needed.
     * If the password type supports it, the new_pass value is crypt()'ed.
     * For crypt'ed passwords, the salt comes from one of 3 places in order:
     * new_salt (if present), existing salt (if present), newly created 
     * salt.
     */

    IF new_salt IS NOT NULL THEN
        pw_salt := new_salt;
    ELSE 
        pw_salt := actor.get_salt(pw_usr, pw_type);

        IF pw_salt IS NULL THEN
            /* We have no salt for this user + type.  Assume they want a 
             * new salt.  If this type is unsalted, create_salt() will 
             * return NULL. */
            pw_salt := actor.create_salt(pw_type);
        END IF;
    END IF;

    IF pw_salt IS NULL THEN 
        pw_text := new_pass; -- unsalted, use as-is.
    ELSE
        pw_text := CRYPT(new_pass, pw_salt);
    END IF;

    UPDATE actor.passwd 
        SET passwd = pw_text, salt = pw_salt, edit_date = NOW()
        WHERE usr = pw_usr AND passwd_type = pw_type;

    IF NOT FOUND THEN
        -- no password row exists for this user + type.  Create one.
        INSERT INTO actor.passwd (usr, passwd_type, salt, passwd) 
            VALUES (pw_usr, pw_type, pw_salt, pw_text);
    END IF;

    RETURN TRUE;
END;

Function: actor.stat_cat_check()

Returns: trigger

Language: PLPGSQL

DECLARE
    sipfield actor.stat_cat_sip_fields%ROWTYPE;
    use_count INT;
BEGIN
    IF NEW.sip_field IS NOT NULL THEN
        SELECT INTO sipfield * FROM actor.stat_cat_sip_fields WHERE field = NEW.sip_field;
        IF sipfield.one_only THEN
            SELECT INTO use_count count(id) FROM actor.stat_cat WHERE sip_field = NEW.sip_field AND id != NEW.id;
            IF use_count > 0 THEN
                RAISE EXCEPTION 'Sip field cannot be used twice';
            END IF;
        END IF;
    END IF;
    RETURN NEW;
END;

Function: actor.user_ingest_name_keywords()

Returns: trigger

Language: PLPGSQL

BEGIN
    NEW.name_kw_tsvector := TO_TSVECTOR(
        COALESCE(NEW.prefix, '')                || ' ' || 
        COALESCE(NEW.first_given_name, '')      || ' ' || 
        COALESCE(evergreen.unaccent_and_squash(NEW.first_given_name), '') || ' ' || 
        COALESCE(NEW.second_given_name, '')     || ' ' || 
        COALESCE(evergreen.unaccent_and_squash(NEW.second_given_name), '') || ' ' || 
        COALESCE(NEW.family_name, '')           || ' ' || 
        COALESCE(evergreen.unaccent_and_squash(NEW.family_name), '') || ' ' || 
        COALESCE(NEW.suffix, '')                || ' ' || 
        COALESCE(NEW.pref_prefix, '')            || ' ' || 
        COALESCE(NEW.pref_first_given_name, '')  || ' ' || 
        COALESCE(evergreen.unaccent_and_squash(NEW.pref_first_given_name), '') || ' ' || 
        COALESCE(NEW.pref_second_given_name, '') || ' ' || 
        COALESCE(evergreen.unaccent_and_squash(NEW.pref_second_given_name), '') || ' ' || 
        COALESCE(NEW.pref_family_name, '')       || ' ' || 
        COALESCE(evergreen.unaccent_and_squash(NEW.pref_family_name), '') || ' ' || 
        COALESCE(NEW.pref_suffix, '')            || ' ' || 
        COALESCE(NEW.name_keywords, '')
    );
    RETURN NEW;
END;

Function: actor.usr_activity_get_type(ehow text, ewhat text, ewho text)

Returns: SET OF usr_activity_type

Language: SQL

SELECT * FROM config.usr_activity_type 
    WHERE 
        enabled AND 
        (ewho  IS NULL OR ewho  = $1) AND
        (ewhat IS NULL OR ewhat = $2) AND
        (ehow  IS NULL OR ehow  = $3) 
    ORDER BY 
        -- BOOL comparisons sort false to true
        COALESCE(ewho, '')  != COALESCE($1, ''),
        COALESCE(ewhat,'')  != COALESCE($2, ''),
        COALESCE(ehow, '')  != COALESCE($3, '') 
    LIMIT 1;

Function: actor.usr_activity_transient_trg()

Returns: trigger

Language: PLPGSQL

BEGIN
    DELETE FROM actor.usr_activity act USING config.usr_activity_type atype
        WHERE atype.transient AND 
            NEW.etype = atype.id AND
            act.etype = atype.id AND
            act.usr = NEW.usr;
    RETURN NEW;
END;

Function: actor.usr_delete(dest_usr integer, src_usr integer)

Returns: void

Language: PLPGSQL

Logically deletes a user. Removes personally identifiable information, and purges associated data in other tables.

DECLARE
	old_profile actor.usr.profile%type;
	old_home_ou actor.usr.home_ou%type;
	new_profile actor.usr.profile%type;
	new_home_ou actor.usr.home_ou%type;
	new_name    text;
	new_dob     actor.usr.dob%type;
BEGIN
	SELECT
		id || '-PURGED-' || now(),
		profile,
		home_ou,
		dob
	INTO
		new_name,
		old_profile,
		old_home_ou,
		new_dob
	FROM
		actor.usr
	WHERE
		id = src_usr;
	--
	-- Quit if no such user
	--
	IF old_profile IS NULL THEN
		RETURN;
	END IF;
	--
	perform actor.usr_purge_data( src_usr, dest_usr );
	--
	-- Find the root grp_tree and the root org_unit.  This would be simpler if we 
	-- could assume that there is only one root.  Theoretically, someday, maybe,
	-- there could be multiple roots, so we take extra trouble to get the right ones.
	--
	SELECT
		id
	INTO
		new_profile
	FROM
		permission.grp_ancestors( old_profile )
	WHERE
		parent is null;
	--
	SELECT
		id
	INTO
		new_home_ou
	FROM
		actor.org_unit_ancestors( old_home_ou )
	WHERE
		parent_ou is null;
	--
	-- Truncate date of birth
	--
	IF new_dob IS NOT NULL THEN
		new_dob := date_trunc( 'year', new_dob );
	END IF;
	--
	UPDATE
		actor.usr
		SET
			card = NULL,
			profile = new_profile,
			usrname = new_name,
			email = NULL,
			passwd = random()::text,
			standing = DEFAULT,
			ident_type = 
			(
				SELECT MIN( id )
				FROM config.identification_type
			),
			ident_value = NULL,
			ident_type2 = NULL,
			ident_value2 = NULL,
			net_access_level = DEFAULT,
			photo_url = NULL,
			prefix = NULL,
			first_given_name = new_name,
			second_given_name = NULL,
			family_name = new_name,
			suffix = NULL,
			alias = NULL,
            guardian = NULL,
			day_phone = NULL,
			evening_phone = NULL,
			other_phone = NULL,
			mailing_address = NULL,
			billing_address = NULL,
			home_ou = new_home_ou,
			dob = new_dob,
			active = FALSE,
			master_account = DEFAULT, 
			super_user = DEFAULT,
			barred = FALSE,
			deleted = TRUE,
			juvenile = DEFAULT,
			usrgroup = 0,
			claims_returned_count = DEFAULT,
			credit_forward_balance = DEFAULT,
			last_xact_id = DEFAULT,
			pref_prefix = NULL,
			pref_first_given_name = NULL,
			pref_second_given_name = NULL,
			pref_family_name = NULL,
			pref_suffix = NULL,
			name_keywords = NULL,
			create_date = now(),
			expire_date = now()
	WHERE
		id = src_usr;
END;

Function: actor.usr_merge(deactivate_cards integer, del_cards integer, del_addrs boolean, dest_usr boolean, src_usr boolean)

Returns: void

Language: PLPGSQL

Merges all user date from src_usr to dest_usr. When collisions occur, keep dest_usr's data and delete src_usr's data.

DECLARE
	suffix TEXT;
	bucket_row RECORD;
	picklist_row RECORD;
	queue_row RECORD;
	folder_row RECORD;
BEGIN

    -- Bail if src_usr equals dest_usr because the result of merging a
    -- user with itself is not what you want.
    IF src_usr = dest_usr THEN
        RETURN;
    END IF;

    -- do some initial cleanup 
    UPDATE actor.usr SET card = NULL WHERE id = src_usr;
    UPDATE actor.usr SET mailing_address = NULL WHERE id = src_usr;
    UPDATE actor.usr SET billing_address = NULL WHERE id = src_usr;

    -- actor.*
    IF del_cards THEN
        DELETE FROM actor.card where usr = src_usr;
    ELSE
        IF deactivate_cards THEN
            UPDATE actor.card SET active = 'f' WHERE usr = src_usr;
        END IF;
        UPDATE actor.card SET usr = dest_usr WHERE usr = src_usr;
    END IF;


    IF del_addrs THEN
        DELETE FROM actor.usr_address WHERE usr = src_usr;
    ELSE
        UPDATE actor.usr_address SET usr = dest_usr WHERE usr = src_usr;
    END IF;

    UPDATE actor.usr_message SET usr = dest_usr WHERE usr = src_usr;
    -- dupes are technically OK in actor.usr_standing_penalty, should manually delete them...
    UPDATE actor.usr_standing_penalty SET usr = dest_usr WHERE usr = src_usr;
    PERFORM actor.usr_merge_rows('actor.usr_org_unit_opt_in', 'usr', src_usr, dest_usr);
    PERFORM actor.usr_merge_rows('actor.usr_setting', 'usr', src_usr, dest_usr);

    -- permission.*
    PERFORM actor.usr_merge_rows('permission.usr_perm_map', 'usr', src_usr, dest_usr);
    PERFORM actor.usr_merge_rows('permission.usr_object_perm_map', 'usr', src_usr, dest_usr);
    PERFORM actor.usr_merge_rows('permission.usr_grp_map', 'usr', src_usr, dest_usr);
    PERFORM actor.usr_merge_rows('permission.usr_work_ou_map', 'usr', src_usr, dest_usr);


    -- container.*
	
	-- For each *_bucket table: transfer every bucket belonging to src_usr
	-- into the custody of dest_usr.
	--
	-- In order to avoid colliding with an existing bucket owned by
	-- the destination user, append the source user's id (in parenthesese)
	-- to the name.  If you still get a collision, add successive
	-- spaces to the name and keep trying until you succeed.
	--
	FOR bucket_row in
		SELECT id, name
		FROM   container.biblio_record_entry_bucket
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  container.biblio_record_entry_bucket
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = bucket_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

	FOR bucket_row in
		SELECT id, name
		FROM   container.call_number_bucket
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  container.call_number_bucket
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = bucket_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

	FOR bucket_row in
		SELECT id, name
		FROM   container.copy_bucket
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  container.copy_bucket
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = bucket_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

	FOR bucket_row in
		SELECT id, name
		FROM   container.user_bucket
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  container.user_bucket
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = bucket_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

	UPDATE container.user_bucket_item SET target_user = dest_usr WHERE target_user = src_usr;

    -- vandelay.*
	-- transfer queues the same way we transfer buckets (see above)
	FOR queue_row in
		SELECT id, name
		FROM   vandelay.queue
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  vandelay.queue
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = queue_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

    UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;

    -- money.*
    PERFORM actor.usr_merge_rows('money.collections_tracker', 'usr', src_usr, dest_usr);
    PERFORM actor.usr_merge_rows('money.collections_tracker', 'collector', src_usr, dest_usr);
    UPDATE money.billable_xact SET usr = dest_usr WHERE usr = src_usr;
    UPDATE money.billing SET voider = dest_usr WHERE voider = src_usr;
    UPDATE money.bnm_payment SET accepting_usr = dest_usr WHERE accepting_usr = src_usr;

    -- action.*
    UPDATE action.circulation SET usr = dest_usr WHERE usr = src_usr;
    UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
    UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
    UPDATE action.usr_circ_history SET usr = dest_usr WHERE usr = src_usr;

    UPDATE action.hold_request SET usr = dest_usr WHERE usr = src_usr;
    UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
    UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
    UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;

    UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
    UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
    UPDATE action.non_cataloged_circulation SET patron = dest_usr WHERE patron = src_usr;
    UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
    UPDATE action.survey_response SET usr = dest_usr WHERE usr = src_usr;

    -- acq.*
    UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
	UPDATE acq.fund_transfer SET transfer_user = dest_usr WHERE transfer_user = src_usr;
    UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;

	-- transfer picklists the same way we transfer buckets (see above)
	FOR picklist_row in
		SELECT id, name
		FROM   acq.picklist
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  acq.picklist
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = picklist_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

    UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
    UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
    UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
    UPDATE acq.provider_note SET creator = dest_usr WHERE creator = src_usr;
    UPDATE acq.provider_note SET editor = dest_usr WHERE editor = src_usr;
    UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
    UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
    UPDATE acq.lineitem_usr_attr_definition SET usr = dest_usr WHERE usr = src_usr;

    -- asset.*
    UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
    UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
    UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;
    UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
    UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
    UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;

    -- serial.*
    UPDATE serial.record_entry SET creator = dest_usr WHERE creator = src_usr;
    UPDATE serial.record_entry SET editor = dest_usr WHERE editor = src_usr;

    -- reporter.*
    -- It's not uncommon to define the reporter schema in a replica 
    -- DB only, so don't assume these tables exist in the write DB.
    BEGIN
    	UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
    EXCEPTION WHEN undefined_table THEN
        -- do nothing
    END;
    BEGIN
    	UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
    EXCEPTION WHEN undefined_table THEN
        -- do nothing
    END;
    BEGIN
    	UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
    EXCEPTION WHEN undefined_table THEN
        -- do nothing
    END;
    BEGIN
		-- transfer folders the same way we transfer buckets (see above)
		FOR folder_row in
			SELECT id, name
			FROM   reporter.template_folder
			WHERE  owner = src_usr
		LOOP
			suffix := ' (' || src_usr || ')';
			LOOP
				BEGIN
					UPDATE  reporter.template_folder
					SET     owner = dest_usr, name = name || suffix
					WHERE   id = folder_row.id;
				EXCEPTION WHEN unique_violation THEN
					suffix := suffix || ' ';
					CONTINUE;
				END;
				EXIT;
			END LOOP;
		END LOOP;
    EXCEPTION WHEN undefined_table THEN
        -- do nothing
    END;
    BEGIN
		-- transfer folders the same way we transfer buckets (see above)
		FOR folder_row in
			SELECT id, name
			FROM   reporter.report_folder
			WHERE  owner = src_usr
		LOOP
			suffix := ' (' || src_usr || ')';
			LOOP
				BEGIN
					UPDATE  reporter.report_folder
					SET     owner = dest_usr, name = name || suffix
					WHERE   id = folder_row.id;
				EXCEPTION WHEN unique_violation THEN
					suffix := suffix || ' ';
					CONTINUE;
				END;
				EXIT;
			END LOOP;
		END LOOP;
    EXCEPTION WHEN undefined_table THEN
        -- do nothing
    END;
    BEGIN
		-- transfer folders the same way we transfer buckets (see above)
		FOR folder_row in
			SELECT id, name
			FROM   reporter.output_folder
			WHERE  owner = src_usr
		LOOP
			suffix := ' (' || src_usr || ')';
			LOOP
				BEGIN
					UPDATE  reporter.output_folder
					SET     owner = dest_usr, name = name || suffix
					WHERE   id = folder_row.id;
				EXCEPTION WHEN unique_violation THEN
					suffix := suffix || ' ';
					CONTINUE;
				END;
				EXIT;
			END LOOP;
		END LOOP;
    EXCEPTION WHEN undefined_table THEN
        -- do nothing
    END;

    -- propagate preferred name values from the source user to the
    -- destination user, but only when values are not being replaced.
    WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr)
    UPDATE actor.usr SET 
        pref_prefix = 
            COALESCE(pref_prefix, (SELECT pref_prefix FROM susr)),
        pref_first_given_name = 
            COALESCE(pref_first_given_name, (SELECT pref_first_given_name FROM susr)),
        pref_second_given_name = 
            COALESCE(pref_second_given_name, (SELECT pref_second_given_name FROM susr)),
        pref_family_name = 
            COALESCE(pref_family_name, (SELECT pref_family_name FROM susr)),
        pref_suffix = 
            COALESCE(pref_suffix, (SELECT pref_suffix FROM susr))
    WHERE id = dest_usr;

    -- Copy and deduplicate name keywords
    -- String -> array -> rows -> DISTINCT -> array -> string
    WITH susr AS (SELECT * FROM actor.usr WHERE id = src_usr),
         dusr AS (SELECT * FROM actor.usr WHERE id = dest_usr)
    UPDATE actor.usr SET name_keywords = (
        WITH keywords AS (
            SELECT DISTINCT UNNEST(
                REGEXP_SPLIT_TO_ARRAY(
                    COALESCE((SELECT name_keywords FROM susr), '') || ' ' ||
                    COALESCE((SELECT name_keywords FROM dusr), ''),  E'\\s+'
                )
            ) AS parts
        ) SELECT STRING_AGG(kw.parts, ' ') FROM keywords kw
    ) WHERE id = dest_usr;

    -- Finally, delete the source user
    PERFORM actor.usr_delete(src_usr,dest_usr);

END;

Function: actor.usr_merge_rows(dest_usr text, src_usr text, col_name integer, table_name integer)

Returns: void

Language: PLPGSQL

Attempts to move each row of the specified table from src_user to dest_user. Where conflicts exist, the conflicting "source" row is deleted.

DECLARE
    sel TEXT;
    upd TEXT;
    del TEXT;
    cur_row RECORD;
BEGIN
    sel := 'SELECT id::BIGINT FROM ' || table_name || ' WHERE ' || quote_ident(col_name) || ' = ' || quote_literal(src_usr);
    upd := 'UPDATE ' || table_name || ' SET ' || quote_ident(col_name) || ' = ' || quote_literal(dest_usr) || ' WHERE id = ';
    del := 'DELETE FROM ' || table_name || ' WHERE id = ';
    FOR cur_row IN EXECUTE sel LOOP
        BEGIN
            --RAISE NOTICE 'Attempting to merge % %', table_name, cur_row.id;
            EXECUTE upd || cur_row.id;
        EXCEPTION WHEN unique_violation THEN
            --RAISE NOTICE 'Deleting conflicting % %', table_name, cur_row.id;
            EXECUTE del || cur_row.id;
        END;
    END LOOP;
END;

Function: actor.usr_purge_data(specified_dest_usr integer, src_usr integer)

Returns: void

Language: PLPGSQL

Finds rows dependent on a given row in actor.usr and either deletes them or reassigns them to a different user.

DECLARE
	suffix TEXT;
	renamable_row RECORD;
	dest_usr INTEGER;
BEGIN

	IF specified_dest_usr IS NULL THEN
		dest_usr := 1; -- Admin user on stock installs
	ELSE
		dest_usr := specified_dest_usr;
	END IF;

    -- action_trigger.event (even doing this, event_output may--and probably does--contain PII and should have a retention/removal policy)
    UPDATE action_trigger.event SET context_user = dest_usr WHERE context_user = src_usr;

	-- acq.*
	UPDATE acq.fund_allocation SET allocator = dest_usr WHERE allocator = src_usr;
	UPDATE acq.lineitem SET creator = dest_usr WHERE creator = src_usr;
	UPDATE acq.lineitem SET editor = dest_usr WHERE editor = src_usr;
	UPDATE acq.lineitem SET selector = dest_usr WHERE selector = src_usr;
	UPDATE acq.lineitem_note SET creator = dest_usr WHERE creator = src_usr;
	UPDATE acq.lineitem_note SET editor = dest_usr WHERE editor = src_usr;
	UPDATE acq.invoice SET closed_by = dest_usr WHERE closed_by = src_usr;
	DELETE FROM acq.lineitem_usr_attr_definition WHERE usr = src_usr;

	-- Update with a rename to avoid collisions
	FOR renamable_row in
		SELECT id, name
		FROM   acq.picklist
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  acq.picklist
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = renamable_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

	UPDATE acq.picklist SET creator = dest_usr WHERE creator = src_usr;
	UPDATE acq.picklist SET editor = dest_usr WHERE editor = src_usr;
	UPDATE acq.po_note SET creator = dest_usr WHERE creator = src_usr;
	UPDATE acq.po_note SET editor = dest_usr WHERE editor = src_usr;
	UPDATE acq.purchase_order SET owner = dest_usr WHERE owner = src_usr;
	UPDATE acq.purchase_order SET creator = dest_usr WHERE creator = src_usr;
	UPDATE acq.purchase_order SET editor = dest_usr WHERE editor = src_usr;
	UPDATE acq.claim_event SET creator = dest_usr WHERE creator = src_usr;

	-- action.*
	DELETE FROM action.circulation WHERE usr = src_usr;
	UPDATE action.circulation SET circ_staff = dest_usr WHERE circ_staff = src_usr;
	UPDATE action.circulation SET checkin_staff = dest_usr WHERE checkin_staff = src_usr;
	UPDATE action.hold_notification SET notify_staff = dest_usr WHERE notify_staff = src_usr;
	UPDATE action.hold_request SET fulfillment_staff = dest_usr WHERE fulfillment_staff = src_usr;
	UPDATE action.hold_request SET requestor = dest_usr WHERE requestor = src_usr;
	DELETE FROM action.hold_request WHERE usr = src_usr;
	UPDATE action.in_house_use SET staff = dest_usr WHERE staff = src_usr;
	UPDATE action.non_cat_in_house_use SET staff = dest_usr WHERE staff = src_usr;
	DELETE FROM action.non_cataloged_circulation WHERE patron = src_usr;
	UPDATE action.non_cataloged_circulation SET staff = dest_usr WHERE staff = src_usr;
	DELETE FROM action.survey_response WHERE usr = src_usr;
	UPDATE action.fieldset SET owner = dest_usr WHERE owner = src_usr;
	DELETE FROM action.usr_circ_history WHERE usr = src_usr;
	UPDATE action.curbside SET notes = NULL WHERE patron = src_usr;

	-- actor.*
	DELETE FROM actor.card WHERE usr = src_usr;
	DELETE FROM actor.stat_cat_entry_usr_map WHERE target_usr = src_usr;
	DELETE FROM actor.usr_privacy_waiver WHERE usr = src_usr;
	DELETE FROM actor.usr_message WHERE usr = src_usr;

	-- The following update is intended to avoid transient violations of a foreign
	-- key constraint, whereby actor.usr_address references itself.  It may not be
	-- necessary, but it does no harm.
	UPDATE actor.usr_address SET replaces = NULL
		WHERE usr = src_usr AND replaces IS NOT NULL;
	DELETE FROM actor.usr_address WHERE usr = src_usr;
	DELETE FROM actor.usr_org_unit_opt_in WHERE usr = src_usr;
	UPDATE actor.usr_org_unit_opt_in SET staff = dest_usr WHERE staff = src_usr;
	DELETE FROM actor.usr_setting WHERE usr = src_usr;
	DELETE FROM actor.usr_standing_penalty WHERE usr = src_usr;
	UPDATE actor.usr_message SET title = 'purged', message = 'purged', read_date = NOW() WHERE usr = src_usr;
	DELETE FROM actor.usr_message WHERE usr = src_usr;
	UPDATE actor.usr_standing_penalty SET staff = dest_usr WHERE staff = src_usr;
	UPDATE actor.usr_message SET editor = dest_usr WHERE editor = src_usr;

	-- asset.*
	UPDATE asset.call_number SET creator = dest_usr WHERE creator = src_usr;
	UPDATE asset.call_number SET editor = dest_usr WHERE editor = src_usr;
	UPDATE asset.call_number_note SET creator = dest_usr WHERE creator = src_usr;
	UPDATE asset.copy SET creator = dest_usr WHERE creator = src_usr;
	UPDATE asset.copy SET editor = dest_usr WHERE editor = src_usr;
	UPDATE asset.copy_note SET creator = dest_usr WHERE creator = src_usr;

	-- auditor.*
	DELETE FROM auditor.actor_usr_address_history WHERE id = src_usr;
	DELETE FROM auditor.actor_usr_history WHERE id = src_usr;
	UPDATE auditor.asset_call_number_history SET creator = dest_usr WHERE creator = src_usr;
	UPDATE auditor.asset_call_number_history SET editor  = dest_usr WHERE editor  = src_usr;
	UPDATE auditor.asset_copy_history SET creator = dest_usr WHERE creator = src_usr;
	UPDATE auditor.asset_copy_history SET editor  = dest_usr WHERE editor  = src_usr;
	UPDATE auditor.biblio_record_entry_history SET creator = dest_usr WHERE creator = src_usr;
	UPDATE auditor.biblio_record_entry_history SET editor  = dest_usr WHERE editor  = src_usr;

	-- biblio.*
	UPDATE biblio.record_entry SET creator = dest_usr WHERE creator = src_usr;
	UPDATE biblio.record_entry SET editor = dest_usr WHERE editor = src_usr;
	UPDATE biblio.record_note SET creator = dest_usr WHERE creator = src_usr;
	UPDATE biblio.record_note SET editor = dest_usr WHERE editor = src_usr;

	-- container.*
	-- Update buckets with a rename to avoid collisions
	FOR renamable_row in
		SELECT id, name
		FROM   container.biblio_record_entry_bucket
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  container.biblio_record_entry_bucket
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = renamable_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

	FOR renamable_row in
		SELECT id, name
		FROM   container.call_number_bucket
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  container.call_number_bucket
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = renamable_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

	FOR renamable_row in
		SELECT id, name
		FROM   container.copy_bucket
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  container.copy_bucket
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = renamable_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

	FOR renamable_row in
		SELECT id, name
		FROM   container.user_bucket
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  container.user_bucket
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = renamable_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

	DELETE FROM container.user_bucket_item WHERE target_user = src_usr;

	-- money.*
	DELETE FROM money.billable_xact WHERE usr = src_usr;
	DELETE FROM money.collections_tracker WHERE usr = src_usr;
	UPDATE money.collections_tracker SET collector = dest_usr WHERE collector = src_usr;

	-- permission.*
	DELETE FROM permission.usr_grp_map WHERE usr = src_usr;
	DELETE FROM permission.usr_object_perm_map WHERE usr = src_usr;
	DELETE FROM permission.usr_perm_map WHERE usr = src_usr;
	DELETE FROM permission.usr_work_ou_map WHERE usr = src_usr;

	-- reporter.*
	-- Update with a rename to avoid collisions
	BEGIN
		FOR renamable_row in
			SELECT id, name
			FROM   reporter.output_folder
			WHERE  owner = src_usr
		LOOP
			suffix := ' (' || src_usr || ')';
			LOOP
				BEGIN
					UPDATE  reporter.output_folder
					SET     owner = dest_usr, name = name || suffix
					WHERE   id = renamable_row.id;
				EXCEPTION WHEN unique_violation THEN
					suffix := suffix || ' ';
					CONTINUE;
				END;
				EXIT;
			END LOOP;
		END LOOP;
	EXCEPTION WHEN undefined_table THEN
		-- do nothing
	END;

	BEGIN
		UPDATE reporter.report SET owner = dest_usr WHERE owner = src_usr;
	EXCEPTION WHEN undefined_table THEN
		-- do nothing
	END;

	-- Update with a rename to avoid collisions
	BEGIN
		FOR renamable_row in
			SELECT id, name
			FROM   reporter.report_folder
			WHERE  owner = src_usr
		LOOP
			suffix := ' (' || src_usr || ')';
			LOOP
				BEGIN
					UPDATE  reporter.report_folder
					SET     owner = dest_usr, name = name || suffix
					WHERE   id = renamable_row.id;
				EXCEPTION WHEN unique_violation THEN
					suffix := suffix || ' ';
					CONTINUE;
				END;
				EXIT;
			END LOOP;
		END LOOP;
	EXCEPTION WHEN undefined_table THEN
		-- do nothing
	END;

	BEGIN
		UPDATE reporter.schedule SET runner = dest_usr WHERE runner = src_usr;
	EXCEPTION WHEN undefined_table THEN
		-- do nothing
	END;

	BEGIN
		UPDATE reporter.template SET owner = dest_usr WHERE owner = src_usr;
	EXCEPTION WHEN undefined_table THEN
		-- do nothing
	END;

	-- Update with a rename to avoid collisions
	BEGIN
		FOR renamable_row in
			SELECT id, name
			FROM   reporter.template_folder
			WHERE  owner = src_usr
		LOOP
			suffix := ' (' || src_usr || ')';
			LOOP
				BEGIN
					UPDATE  reporter.template_folder
					SET     owner = dest_usr, name = name || suffix
					WHERE   id = renamable_row.id;
				EXCEPTION WHEN unique_violation THEN
					suffix := suffix || ' ';
					CONTINUE;
				END;
				EXIT;
			END LOOP;
		END LOOP;
	EXCEPTION WHEN undefined_table THEN
	-- do nothing
	END;

	-- vandelay.*
	-- Update with a rename to avoid collisions
	FOR renamable_row in
		SELECT id, name
		FROM   vandelay.queue
		WHERE  owner = src_usr
	LOOP
		suffix := ' (' || src_usr || ')';
		LOOP
			BEGIN
				UPDATE  vandelay.queue
				SET     owner = dest_usr, name = name || suffix
				WHERE   id = renamable_row.id;
			EXCEPTION WHEN unique_violation THEN
				suffix := suffix || ' ';
				CONTINUE;
			END;
			EXIT;
		END LOOP;
	END LOOP;

    UPDATE vandelay.session_tracker SET usr = dest_usr WHERE usr = src_usr;

    -- NULL-ify addresses last so other cleanup (e.g. circ anonymization)
    -- can access the information before deletion.
	UPDATE actor.usr SET
		active = FALSE,
		card = NULL,
		mailing_address = NULL,
		billing_address = NULL
	WHERE id = src_usr;

END;

Function: actor.verify_passwd(test_passwd integer, pw_type text, pw_usr text)

Returns: boolean

Language: PLPGSQL

DECLARE
    pw_salt TEXT;
BEGIN
    /* Returns TRUE if the password provided matches the in-db password.  
     * If the password type is salted, we compare the output of CRYPT().
     * NOTE: test_passwd is MD5(salt || MD5(password)) for legacy 
     * 'main' passwords.
     */

    SELECT INTO pw_salt salt FROM actor.passwd 
        WHERE usr = pw_usr AND passwd_type = pw_type;

    IF NOT FOUND THEN
        -- no such password
        RETURN FALSE;
    END IF;

    IF pw_salt IS NULL THEN
        -- Password is unsalted, compare the un-CRYPT'ed values.
        RETURN EXISTS (
            SELECT TRUE FROM actor.passwd WHERE 
                usr = pw_usr AND
                passwd_type = pw_type AND
                passwd = test_passwd
        );
    END IF;

    RETURN EXISTS (
        SELECT TRUE FROM actor.passwd WHERE 
            usr = pw_usr AND
            passwd_type = pw_type AND
            passwd = CRYPT(test_passwd, pw_salt)
    );
END;

Schema asset


View: asset.active_copy_alert

asset.active_copy_alert Structure
F-Key Name Type Description
id bigint
alert_type integer
copy bigint
temp boolean
create_time timestamp with time zone
create_staff bigint
note text
ack_time timestamp with time zone
ack_staff bigint
SELECT copy_alert.id
,
    copy_alert.alert_type
,
    copy_alert.copy
,
    copy_alert.temp
,
    copy_alert.create_time
,
    copy_alert.create_staff
,
    copy_alert.note
,
    copy_alert.ack_time
,
    copy_alert.ack_staff
   
FROM asset.copy_alert
  
WHERE (copy_alert.ack_time IS NULL);

Index - Schema asset


Table: asset.call_number

asset.call_number Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id creator bigint NOT NULL
create_date timestamp with time zone DEFAULT now()
actor.usr.id editor bigint NOT NULL
edit_date timestamp with time zone DEFAULT now()
biblio.record_entry.id record bigint NOT NULL
actor.org_unit.id owning_lib integer NOT NULL
label text NOT NULL
deleted boolean NOT NULL DEFAULT false
asset.call_number_prefix.id prefix integer NOT NULL DEFAULT '-1'::integer
asset.call_number_suffix.id suffix integer NOT NULL DEFAULT '-1'::integer
asset.call_number_class.id label_class bigint NOT NULL
label_sortkey text

Tables referencing this one via Foreign Key Constraints:

asset_call_number_creator_idx creator asset_call_number_dewey_idx call_number_dewey(label) asset_call_number_editor_idx editor asset_call_number_label_sortkey oils_text_as_bytea(label_sortkey) asset_call_number_label_sortkey_browse oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib) WHERE ((deleted IS FALSE) OR (deleted = false) asset_call_number_record_idx record asset_call_number_upper_label_id_owning_lib_idx oils_text_as_bytea(label), id, owning_lib

Index - Schema asset


Table: asset.call_number_class

Defines the call number normalization database functions in the "normalizer" column and the tag/subfield combinations to use to lookup the call number in the "field" column for a given classification scheme. Tag/subfield combinations are delimited by commas.

asset.call_number_class Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
name text NOT NULL
normalizer text NOT NULL DEFAULT 'asset.normalize_generic'::text
field text NOT NULL DEFAULT '050ab,055ab,060ab,070ab,080ab,082ab,086ab,088ab,090,092,096,098,099'::text

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.call_number_note

asset.call_number_note Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
asset.call_number.id call_number bigint NOT NULL
actor.usr.id creator bigint NOT NULL
create_date timestamp with time zone DEFAULT now()
pub boolean NOT NULL DEFAULT false
title text NOT NULL
value text NOT NULL
asset_call_number_note_creator_idx creator

Index - Schema asset


Table: asset.call_number_prefix

asset.call_number_prefix Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owning_lib integer NOT NULL
label text NOT NULL
label_sortkey text

Tables referencing this one via Foreign Key Constraints:

asset_call_number_prefix_sortkey_idx label_sortkey

Index - Schema asset


Table: asset.call_number_suffix

asset.call_number_suffix Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owning_lib integer NOT NULL
label text NOT NULL
label_sortkey text

Tables referencing this one via Foreign Key Constraints:

asset_call_number_suffix_sortkey_idx label_sortkey

Index - Schema asset


Table: asset.copy

asset.copy Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.org_unit.id circ_lib integer NOT NULL
actor.usr.id creator bigint NOT NULL
asset.call_number.id call_number bigint NOT NULL
actor.usr.id editor bigint NOT NULL
create_date timestamp with time zone DEFAULT now()
edit_date timestamp with time zone DEFAULT now()
copy_number integer
config.copy_status.id status integer NOT NULL
asset.copy_location.id location integer NOT NULL DEFAULT 1
loan_duration integer NOT NULL
fine_level integer NOT NULL
age_protect integer
circulate boolean NOT NULL DEFAULT true
deposit boolean NOT NULL DEFAULT false
ref boolean NOT NULL DEFAULT false
holdable boolean NOT NULL DEFAULT true
deposit_amount numeric(6,2) NOT NULL DEFAULT 0.00
price numeric(8,2)
barcode text NOT NULL
config.circ_modifier.code circ_modifier text
circ_as_type text
dummy_title text
dummy_author text
alert_message text
opac_visible boolean NOT NULL DEFAULT true
deleted boolean NOT NULL DEFAULT false
config.floating_group.id floating integer
dummy_isbn text
status_changed_time timestamp with time zone
active_date timestamp with time zone
mint_condition boolean NOT NULL DEFAULT true
cost numeric(8,2)

 

asset.copy Constraints
Name Constraint
copy_fine_level_check CHECK ((fine_level = ANY (ARRAY[1, 2, 3])))
copy_loan_duration_check CHECK ((loan_duration = ANY (ARRAY[1, 2, 3])))

Tables referencing this one via Foreign Key Constraints:

cp_avail_cn_idx call_number cp_available_by_circ_lib_idx circ_lib) WHERE (status = ANY (ARRAY[0, 7]) cp_cn_idx call_number cp_create_date create_date cp_creator_idx creator cp_editor_idx editor

Index - Schema asset


Table: asset.copy_alert

asset.copy_alert Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
config.copy_alert_type.id alert_type integer NOT NULL
copy bigint NOT NULL
temp boolean NOT NULL DEFAULT false
create_time timestamp with time zone NOT NULL DEFAULT now()
actor.usr.id create_staff bigint NOT NULL
note text
ack_time timestamp with time zone
actor.usr.id ack_staff bigint

Index - Schema asset


Table: asset.copy_inventory

asset.copy_inventory Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.workstation.id inventory_workstation integer
inventory_date timestamp with time zone NOT NULL DEFAULT now()
copy bigint NOT NULL
copy_inventory_copy_idx copy

Index - Schema asset


Table: asset.copy_location

asset.copy_location Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL
actor.org_unit.id owning_lib integer NOT NULL
holdable boolean NOT NULL DEFAULT true
hold_verify boolean NOT NULL DEFAULT false
opac_visible boolean NOT NULL DEFAULT true
circulate boolean NOT NULL DEFAULT true
label_prefix text
label_suffix text
checkin_alert boolean NOT NULL DEFAULT false
deleted boolean NOT NULL DEFAULT false
url text

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.copy_location_group

asset.copy_location_group Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
pos integer NOT NULL
top boolean NOT NULL DEFAULT false
opac_visible boolean NOT NULL DEFAULT true

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.copy_location_group_map

asset.copy_location_group_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
asset.copy_location.id location integer UNIQUE#1 NOT NULL
asset.copy_location_group.id lgroup integer UNIQUE#1 NOT NULL

Index - Schema asset


Table: asset.copy_location_order

asset.copy_location_order Structure
F-Key Name Type Description
id serial PRIMARY KEY
asset.copy_location.id location integer UNIQUE#1 NOT NULL
actor.org_unit.id org integer UNIQUE#1 NOT NULL
position integer NOT NULL

Index - Schema asset


Table: asset.copy_note

asset.copy_note Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
owning_copy bigint NOT NULL
actor.usr.id creator bigint NOT NULL
create_date timestamp with time zone DEFAULT now()
pub boolean NOT NULL DEFAULT false
title text NOT NULL
value text NOT NULL
asset_copy_note_creator_idx creator asset_copy_note_owning_copy_idx owning_copy

Index - Schema asset


Table: asset.copy_part_map

asset.copy_part_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
target_copy bigint NOT NULL
biblio.monograph_part.id part integer NOT NULL

Index - Schema asset


Table: asset.copy_tag

asset.copy_tag Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.copy_tag_type.code tag_type text
label text NOT NULL
value text NOT NULL
index_vector tsvector NOT NULL
staff_note text
pub boolean DEFAULT true
actor.org_unit.id owner integer NOT NULL
url text

Tables referencing this one via Foreign Key Constraints:

asset_copy_tag_index_vector_idx index_vector asset_copy_tag_label_idx label asset_copy_tag_label_lower_idx lowercase(label) asset_copy_tag_owner_idx owner asset_copy_tag_tag_type_idx tag_type

Index - Schema asset


Table: asset.copy_tag_copy_map

asset.copy_tag_copy_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
copy bigint
asset.copy_tag.id tag integer
asset_copy_tag_copy_map_copy_idx copy asset_copy_tag_copy_map_tag_idx tag

Index - Schema asset


Table: asset.copy_template

asset.copy_template Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owning_lib integer NOT NULL
actor.usr.id creator bigint NOT NULL
actor.usr.id editor bigint NOT NULL
create_date timestamp with time zone DEFAULT now()
edit_date timestamp with time zone DEFAULT now()
name text NOT NULL
actor.org_unit.id circ_lib integer
config.copy_status.id status integer
asset.copy_location.id location integer
loan_duration integer
fine_level integer
age_protect integer
circulate boolean
deposit boolean
ref boolean
holdable boolean
deposit_amount numeric(6,2)
price numeric(8,2)
circ_modifier text
circ_as_type text
alert_message text
opac_visible boolean
config.floating_group.id floating integer
mint_condition boolean

 

asset.copy_template Constraints
Name Constraint
valid_fine_level CHECK (((fine_level IS NULL) OR (loan_duration = ANY (ARRAY[1, 2, 3]))))
valid_loan_duration CHECK (((loan_duration IS NULL) OR (loan_duration = ANY (ARRAY[1, 2, 3]))))

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.copy_vis_attr_cache

asset.copy_vis_attr_cache Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
record bigint NOT NULL
target_copy bigint NOT NULL
vis_attr_vector integer[]
copy_vis_attr_cache_copy_idx target_copy copy_vis_attr_cache_record_idx record

Index - Schema asset


Table: asset.course_module_course

asset.course_module_course Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL
course_number text NOT NULL
section_number text
actor.org_unit.id owning_lib integer
is_archived boolean DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.course_module_course_materials

asset.course_module_course_materials Structure
F-Key Name Type Description
id serial PRIMARY KEY
asset.course_module_course.id course integer UNIQUE#1 NOT NULL
asset.copy.id item integer UNIQUE#1
relationship text
biblio.record_entry.id record integer UNIQUE#1
temporary_record boolean
asset.copy_location.id original_location integer
config.copy_status.id original_status integer
config.circ_modifier.code original_circ_modifier text
asset.call_number.id original_callnumber integer
actor.org_unit.id original_circ_lib integer

Index - Schema asset


Table: asset.course_module_course_users

asset.course_module_course_users Structure
F-Key Name Type Description
id serial PRIMARY KEY
asset.course_module_course.id course integer NOT NULL
actor.usr.id usr integer NOT NULL
asset.course_module_role.id usr_role integer

Index - Schema asset


Table: asset.course_module_role

asset.course_module_role Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
is_public boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.course_module_term

asset.course_module_term Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
actor.org_unit.id owning_lib integer UNIQUE#1
start_date timestamp with time zone
end_date timestamp with time zone

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.course_module_term_course_map

asset.course_module_term_course_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
asset.course_module_term.id term integer NOT NULL
asset.course_module_course.id course integer NOT NULL

Index - Schema asset


View: asset.latest_inventory

asset.latest_inventory Structure
F-Key Name Type Description
id integer
inventory_workstation integer
inventory_date timestamp with time zone
copy bigint
SELECT DISTINCT 
ON (copy_inventory.copy) copy_inventory.id
,
    copy_inventory.inventory_workstation
,
    copy_inventory.inventory_date
,
    copy_inventory.copy
   
FROM asset.copy_inventory
  
ORDER BY copy_inventory.copy
, copy_inventory.inventory_date DESC;

Index - Schema asset


Table: asset.stat_cat

asset.stat_cat Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
opac_visible boolean NOT NULL DEFAULT false
name text UNIQUE#1 NOT NULL
required boolean NOT NULL DEFAULT false
asset.stat_cat_sip_fields.field sip_field character(2)
sip_format text
checkout_archive boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.stat_cat_entry

asset.stat_cat_entry Structure
F-Key Name Type Description
id serial PRIMARY KEY
asset.stat_cat.id stat_cat integer UNIQUE#1 NOT NULL
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
value text UNIQUE#1 NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.stat_cat_entry_copy_map

asset.stat_cat_entry_copy_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
asset.stat_cat.id stat_cat integer UNIQUE#1 NOT NULL
asset.stat_cat_entry.id stat_cat_entry integer NOT NULL
owning_copy bigint UNIQUE#1 NOT NULL
scecm_owning_copy_idx owning_copy

Index - Schema asset


Table: asset.stat_cat_entry_transparency_map

asset.stat_cat_entry_transparency_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
stat_cat integer UNIQUE#1 NOT NULL
stat_cat_entry integer NOT NULL
owning_transparency integer UNIQUE#1 NOT NULL

Index - Schema asset


Table: asset.stat_cat_sip_fields

Asset Statistical Category SIP Fields Contains the list of valid SIP Field identifiers for Statistical Categories.

asset.stat_cat_sip_fields Structure
F-Key Name Type Description
field character(2) PRIMARY KEY
name text NOT NULL
one_only boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.uri

asset.uri Structure
F-Key Name Type Description
id serial PRIMARY KEY
href text NOT NULL
label text
use_restriction text
active boolean NOT NULL DEFAULT true

Tables referencing this one via Foreign Key Constraints:

Index - Schema asset


Table: asset.uri_call_number_map

asset.uri_call_number_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
asset.uri.id uri integer UNIQUE#1 NOT NULL
asset.call_number.id call_number integer UNIQUE#1 NOT NULL
asset_uri_call_number_map_cn_idx call_number

Index - Schema asset


Function: asset.acp_created()

Returns: trigger

Language: PLPGSQL

BEGIN
    IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
        NEW.active_date := now();
    END IF;
    IF NEW.status_changed_time IS NULL THEN
        NEW.status_changed_time := now();
    END IF;
    RETURN NEW;
END;

Function: asset.acp_location_fixer()

Returns: trigger

Language: PLPGSQL

DECLARE
    new_copy_location INT;
BEGIN
    IF (TG_OP = 'UPDATE') THEN
        IF NEW.location = OLD.location AND NEW.call_number = OLD.call_number AND NEW.circ_lib = OLD.circ_lib THEN
            RETURN NEW;
        END IF;
    END IF;
    SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance((SELECT owning_lib FROM asset.call_number WHERE id = NEW.call_number)) aouad ON acpl.owning_lib = aouad.id WHERE deleted IS FALSE AND name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
    IF new_copy_location IS NULL THEN
        SELECT INTO new_copy_location acpl.id FROM asset.copy_location acpl JOIN actor.org_unit_ancestors_distance(NEW.circ_lib) aouad ON acpl.owning_lib = aouad.id WHERE deleted IS FALSE AND name = (SELECT name FROM asset.copy_location WHERE id = NEW.location) ORDER BY distance LIMIT 1;
    END IF;
    IF new_copy_location IS NOT NULL THEN
        NEW.location = new_copy_location;
    END IF;
    RETURN NEW;
END;

Function: asset.acp_status_changed()

Returns: trigger

Language: PLPGSQL

BEGIN
	IF NEW.status <> OLD.status AND NOT (NEW.status = 0 AND OLD.status = 7) THEN
        NEW.status_changed_time := now();
        IF NEW.active_date IS NULL AND NEW.status IN (SELECT id FROM config.copy_status WHERE copy_active = true) THEN
            NEW.active_date := now();
        END IF;
    END IF;
    RETURN NEW;
END;

Function: asset.all_visible_flags()

Returns: text

Language: SQL

    SELECT  '(' || STRING_AGG(search.calculate_visibility_attribute(1 << x, 'copy_flags')::TEXT,'&') || ')'
      FROM  GENERATE_SERIES(0,0) AS x; -- increment as new flags are added.

Function: asset.autogenerate_placeholder_barcode()

Returns: trigger

Language: PLPGSQL

BEGIN
	IF NEW.barcode LIKE '@@%' THEN
		NEW.barcode := '@@' || NEW.id;
	END IF;
	RETURN NEW;
END;

Function: asset.bib_source_default()

Returns: text

Language: SQL

    SELECT  '(' || STRING_AGG(search.calculate_visibility_attribute(id, 'bib_source')::TEXT,'|') || ')'
      FROM  config.bib_source
      WHERE transcendant;

Function: asset.cache_copy_visibility()

Returns: trigger

Language: PLPGSQL

DECLARE
    ocn     asset.call_number%ROWTYPE;
    ncn     asset.call_number%ROWTYPE;
    cid     BIGINT;
    dobib   BOOL;
BEGIN

    SELECT enabled = FALSE INTO dobib FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc';

    IF TG_TABLE_NAME = 'peer_bib_copy_map' THEN -- Only needs ON INSERT OR DELETE, so handle separately
        IF TG_OP = 'INSERT' THEN
            INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES (
                NEW.peer_record,
                NEW.target_copy,
                asset.calculate_copy_visibility_attribute_set(NEW.target_copy)
            );

            RETURN NEW;
        ELSIF TG_OP = 'DELETE' THEN
            DELETE FROM asset.copy_vis_attr_cache
              WHERE record = OLD.peer_record AND target_copy = OLD.target_copy;

            RETURN OLD;
        END IF;
    END IF;

    IF TG_OP = 'INSERT' THEN -- Handles ON INSERT. ON UPDATE is below.
        IF TG_TABLE_NAME IN ('copy', 'unit') THEN
            SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number;
            INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES (
                ncn.record,
                NEW.id,
                asset.calculate_copy_visibility_attribute_set(NEW.id)
            );
        ELSIF TG_TABLE_NAME = 'record_entry' THEN
            NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id, NEW.source, TRUE);
        ELSIF TG_TABLE_NAME = 'call_number' AND NEW.label = '##URI##' AND dobib THEN -- New located URI
            UPDATE  biblio.record_entry
              SET   vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(NEW.record)
              WHERE id = NEW.record;

        END IF;

        RETURN NEW;
    END IF;

    -- handle items first, since with circulation activity
    -- their statuses change frequently
    IF TG_TABLE_NAME IN ('copy', 'unit') THEN -- This handles ON UPDATE OR DELETE. ON INSERT above

        IF TG_OP = 'DELETE' THEN -- Shouldn't get here, normally
            DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id;
            RETURN OLD;
        END IF;

        SELECT * INTO ncn FROM asset.call_number cn WHERE id = NEW.call_number;

        IF OLD.deleted <> NEW.deleted THEN
            IF NEW.deleted THEN
                DELETE FROM asset.copy_vis_attr_cache WHERE target_copy = OLD.id;
            ELSE
                INSERT INTO asset.copy_vis_attr_cache (record, target_copy, vis_attr_vector) VALUES (
                    ncn.record,
                    NEW.id,
                    asset.calculate_copy_visibility_attribute_set(NEW.id)
                );
            END IF;

            RETURN NEW;
        ELSIF OLD.location   <> NEW.location OR
            OLD.status       <> NEW.status OR
            OLD.opac_visible <> NEW.opac_visible OR
            OLD.circ_lib     <> NEW.circ_lib OR
            OLD.call_number  <> NEW.call_number
        THEN
            IF OLD.call_number  <> NEW.call_number THEN -- Special check since it's more expensive than the next branch
                SELECT * INTO ocn FROM asset.call_number cn WHERE id = OLD.call_number;

                IF ncn.record <> ocn.record THEN
                    -- We have to use a record-specific WHERE clause
                    -- to avoid modifying the entries for peer-bib copies.
                    UPDATE  asset.copy_vis_attr_cache
                      SET   target_copy = NEW.id,
                            record = ncn.record
                      WHERE target_copy = OLD.id
                            AND record = ocn.record;

                END IF;
            ELSE
                -- Any of these could change visibility, but
                -- we'll save some queries and not try to calculate
                -- the change directly.  We want to update peer-bib
                -- entries in this case, unlike above.
                UPDATE  asset.copy_vis_attr_cache
                  SET   target_copy = NEW.id,
                        vis_attr_vector = asset.calculate_copy_visibility_attribute_set(NEW.id)
                  WHERE target_copy = OLD.id;
            END IF;
        END IF;

    ELSIF TG_TABLE_NAME = 'call_number' THEN

        IF TG_OP = 'DELETE' AND OLD.label = '##URI##' AND dobib THEN -- really deleted located URI, if the delete protection rule is disabled...
            UPDATE  biblio.record_entry
              SET   vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record)
              WHERE id = OLD.record;
            RETURN OLD;
        END IF;

        IF OLD.label = '##URI##' AND dobib THEN -- Located URI
            IF OLD.deleted <> NEW.deleted OR OLD.record <> NEW.record OR OLD.owning_lib <> NEW.owning_lib THEN
                UPDATE  biblio.record_entry
                  SET   vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(NEW.record)
                  WHERE id = NEW.record;

                IF OLD.record <> NEW.record THEN -- maybe on merge?
                    UPDATE  biblio.record_entry
                      SET   vis_attr_vector = biblio.calculate_bib_visibility_attribute_set(OLD.record)
                      WHERE id = OLD.record;
                END IF;
            END IF;

        ELSIF OLD.record <> NEW.record OR OLD.owning_lib <> NEW.owning_lib THEN
            UPDATE  asset.copy_vis_attr_cache
              SET   record = NEW.record,
                    vis_attr_vector = asset.calculate_copy_visibility_attribute_set(target_copy)
              WHERE target_copy IN (SELECT id FROM asset.copy WHERE call_number = NEW.id)
                    AND record = OLD.record;

        END IF;

    ELSIF TG_TABLE_NAME = 'record_entry' AND OLD.source IS DISTINCT FROM NEW.source THEN -- Only handles ON UPDATE, INSERT above
        NEW.vis_attr_vector := biblio.calculate_bib_visibility_attribute_set(NEW.id, NEW.source, TRUE);
    END IF;

    RETURN NEW;
END;

Function: asset.calculate_copy_visibility_attribute_set(copy_id bigint)

Returns: integer[]

Language: PLPGSQL

DECLARE
    copy_row    asset.copy%ROWTYPE;
    lgroup_map  asset.copy_location_group_map%ROWTYPE;
    attr_set    INT[] := '{}'::INT[];
BEGIN
    SELECT * INTO copy_row FROM asset.copy WHERE id = copy_id;

    attr_set := attr_set || search.calculate_visibility_attribute(copy_row.opac_visible::INT, 'copy_flags');
    attr_set := attr_set || search.calculate_visibility_attribute(copy_row.circ_lib, 'circ_lib');
    attr_set := attr_set || search.calculate_visibility_attribute(copy_row.status, 'status');
    attr_set := attr_set || search.calculate_visibility_attribute(copy_row.location, 'location');

    SELECT  ARRAY_APPEND(
                attr_set,
                search.calculate_visibility_attribute(owning_lib, 'owning_lib')
            ) INTO attr_set
      FROM  asset.call_number
      WHERE id = copy_row.call_number;

    FOR lgroup_map IN SELECT * FROM asset.copy_location_group_map WHERE location = copy_row.location LOOP
        attr_set := attr_set || search.calculate_visibility_attribute(lgroup_map.lgroup, 'location_group');
    END LOOP;

    RETURN attr_set;
END;

Function: asset.check_delete_copy_location(acpl_id integer)

Returns: void

Language: PLPGSQL

BEGIN
    PERFORM TRUE FROM asset.copy WHERE location = acpl_id AND NOT deleted LIMIT 1;

    IF FOUND THEN
        RAISE EXCEPTION
            'Copy location % contains active copies and cannot be deleted', acpl_id;
    END IF;
END;

Function: asset.circ_lib_default()

Returns: text

Language: SQL

    SELECT  * FROM asset.invisible_orgs('circ_lib');

Function: asset.copy_may_float_to_inventory_workstation()

Returns: trigger

Language: PLPGSQL

DECLARE
    copy asset.copy%ROWTYPE;
    workstation actor.workstation%ROWTYPE;
BEGIN
    SELECT * INTO copy FROM asset.copy WHERE id = NEW.copy;
    IF FOUND THEN
        SELECT * INTO workstation FROM actor.workstation WHERE id = NEW.inventory_workstation;
        IF FOUND THEN
           IF copy.floating IS NULL THEN
              IF copy.circ_lib <> workstation.owning_lib THEN
                 RAISE EXCEPTION 'Inventory workstation owning lib (%) does not match copy circ lib (%).',
                       workstation.owning_lib, copy.circ_lib;
              END IF;
           ELSE
              IF NOT evergreen.can_float(copy.floating, copy.circ_lib, workstation.owning_lib) THEN
                 RAISE EXCEPTION 'Copy (%) cannot float to inventory workstation owning lib (%).',
                       copy.id, workstation.owning_lib;
              END IF;
           END IF;
        END IF;
    END IF;
    RETURN NEW;
END;

Function: asset.copy_state(cid bigint)

Returns: text

Language: PLPGSQL

DECLARE
    last_circ_stop	TEXT;
    the_copy	    asset.copy%ROWTYPE;
BEGIN

    SELECT * INTO the_copy FROM asset.copy WHERE id = cid;
    IF NOT FOUND THEN RETURN NULL; END IF;

    IF the_copy.status = 3 THEN -- Lost
        RETURN 'LOST';
    ELSIF the_copy.status = 4 THEN -- Missing
        RETURN 'MISSING';
    ELSIF the_copy.status = 14 THEN -- Damaged
        RETURN 'DAMAGED';
    ELSIF the_copy.status = 17 THEN -- Lost and paid
        RETURN 'LOST_AND_PAID';
    END IF;

    SELECT stop_fines INTO last_circ_stop
      FROM  action.circulation
      WHERE target_copy = cid AND checkin_time IS NULL
      ORDER BY xact_start DESC LIMIT 1;

    IF FOUND THEN
        IF last_circ_stop IN (
            'CLAIMSNEVERCHECKEDOUT',
            'CLAIMSRETURNED',
            'LONGOVERDUE'
        ) THEN
            RETURN last_circ_stop;
        END IF;
    END IF;

    RETURN 'NORMAL';
END;

Function: asset.invisible_orgs(otype text)

Returns: text

Language: SQL

    SELECT  '!(' || STRING_AGG(search.calculate_visibility_attribute(id, $1)::TEXT,'|') || ')'
      FROM  actor.org_unit
      WHERE NOT opac_visible;

Function: asset.label_normalizer()

Returns: trigger

Language: PLPGSQL

DECLARE
    sortkey        TEXT := '';
BEGIN
    sortkey := NEW.label_sortkey;

    IF NEW.label_class IS NULL THEN
            NEW.label_class := COALESCE(
            (
                SELECT substring(value from E'\\d+')::integer
                FROM actor.org_unit_ancestor_setting('cat.default_classification_scheme', NEW.owning_lib)
            ), 1
        );
    END IF;

    EXECUTE 'SELECT ' || acnc.normalizer || '(' || 
       quote_literal( NEW.label ) || ')'
       FROM asset.call_number_class acnc
       WHERE acnc.id = NEW.label_class
       INTO sortkey;
    NEW.label_sortkey = sortkey;
    RETURN NEW;
END;

Function: asset.label_normalizer_dewey(text)

Returns: text

Language: PLPERLU

    # Derived from the Koha C4::ClassSortRoutine::Dewey module
    # Copyright (C) 2007 LibLime
    # Licensed under the GPL v2 or later

    use strict;
    use warnings;

    my $init = uc(shift);
    $init =~ s/^\s+//;
    $init =~ s/\s+$//;
    $init =~ s!/!!g;
    $init =~ s/^([\p{IsAlpha}]+)/$1 /;
    my @tokens = split /\.|\s+/, $init;
    my $digit_group_count = 0;
    my $first_digit_group_idx;
    for (my $i = 0; $i <= $#tokens; $i++) {
        if ($tokens[$i] =~ /^\d+$/) {
            $digit_group_count++;
            if ($digit_group_count == 1) {
                $first_digit_group_idx = $i;
            }
            if (2 == $digit_group_count) {
                $tokens[$i] = sprintf("%-15.15s", $tokens[$i]);
                $tokens[$i] =~ tr/ /0/;
            }
        }
    }
    # Pad the first digit_group if there was only one
    if (1 == $digit_group_count) {
        $tokens[$first_digit_group_idx] .= '_000000000000000'
    }
    my $key = join("_", @tokens);
    $key =~ s/[^\p{IsAlnum}_]//g;

    return $key;


Function: asset.label_normalizer_generic(text)

Returns: text

Language: PLPERLU

    # Created after looking at the Koha C4::ClassSortRoutine::Generic module,
    # thus could probably be considered a derived work, although nothing was
    # directly copied - but to err on the safe side of providing attribution:
    # Copyright (C) 2007 LibLime
    # Copyright (C) 2011 Equinox Software, Inc (Steve Callendar)
    # Licensed under the GPL v2 or later

    use strict;
    use warnings;

    # Converts the callnumber to uppercase
    # Strips spaces from start and end of the call number
    # Converts anything other than letters, digits, and periods into spaces
    # Collapses multiple spaces into a single underscore
    my $callnum = uc(shift);
    $callnum =~ s/^\s//g;
    $callnum =~ s/\s$//g;
    # NOTE: this previously used underscores, but this caused sorting issues
    # for the "before" half of page 0 on CN browse, sorting CNs containing a
    # decimal before "whole number" CNs
    $callnum =~ s/[^A-Z0-9_.]/ /g;
    $callnum =~ s/ {2,}/ /g;

    return $callnum;

Function: asset.label_normalizer_lc(text)

Returns: text

Language: PLPERLU

    use strict;
    use warnings;

    # Library::CallNumber::LC is currently hosted at http://code.google.com/p/library-callnumber-lc/
    # The author hopes to upload it to CPAN some day, which would make our lives easier
    use Library::CallNumber::LC;

    my $callnum = Library::CallNumber::LC->new(shift);
    return $callnum->normalize();


Function: asset.location_default()

Returns: text

Language: SQL

    SELECT  '!(' || STRING_AGG(search.calculate_visibility_attribute(id, 'location')::TEXT,'|') || ')'
      FROM  asset.copy_location
      WHERE NOT opac_visible;

Function: asset.location_group_default()

Returns: text

Language: SQL

    SELECT '!()'::TEXT; -- For now, as there's no way to cause a location group to hide all copies.
/*
    SELECT  '!(' || STRING_AGG(search.calculate_visibility_attribute(id, 'location_group')::TEXT,'|') || ')'
      FROM  asset.copy_location_group
      WHERE NOT opac_visible;
*/

Function: asset.luri_org_default()

Returns: text

Language: SQL

    SELECT  * FROM asset.invisible_orgs('luri_org');

Function: asset.merge_record_assets(source_record bigint, target_record bigint)

Returns: integer

Language: PLPGSQL

DECLARE
    moved_objects INT := 0;
    source_cn     asset.call_number%ROWTYPE;
    target_cn     asset.call_number%ROWTYPE;
    metarec       metabib.metarecord%ROWTYPE;
    hold          action.hold_request%ROWTYPE;
    ser_rec       serial.record_entry%ROWTYPE;
    ser_sub       serial.subscription%ROWTYPE;
    acq_lineitem  acq.lineitem%ROWTYPE;
    acq_request   acq.user_request%ROWTYPE;
    booking       booking.resource_type%ROWTYPE;
    source_part   biblio.monograph_part%ROWTYPE;
    target_part   biblio.monograph_part%ROWTYPE;
    multi_home    biblio.peer_bib_copy_map%ROWTYPE;
    uri_count     INT := 0;
    counter       INT := 0;
    uri_datafield TEXT;
    uri_text      TEXT := '';
BEGIN

    -- we don't merge bib -1
    IF target_record = -1 OR source_record = -1 THEN
       RETURN 0;
    END IF;

    -- move any 856 entries on records that have at least one MARC-mapped URI entry
    SELECT  INTO uri_count COUNT(*)
      FROM  asset.uri_call_number_map m
            JOIN asset.call_number cn ON (m.call_number = cn.id)
      WHERE cn.record = source_record;

    IF uri_count > 0 THEN
        
        -- This returns more nodes than you might expect:
        -- 7 instead of 1 for an 856 with $u $y $9
        SELECT  COUNT(*) INTO counter
          FROM  oils_xpath_table(
                    'id',
                    'marc',
                    'biblio.record_entry',
                    '//*[@tag="856"]',
                    'id=' || source_record
                ) as t(i int,c text);
    
        FOR i IN 1 .. counter LOOP
            SELECT  '<datafield xmlns="http://www.loc.gov/MARC21/slim"' || 
			' tag="856"' ||
			' ind1="' || FIRST(ind1) || '"'  ||
			' ind2="' || FIRST(ind2) || '">' ||
                        STRING_AGG(
                            '<subfield code="' || subfield || '">' ||
                            regexp_replace(
                                regexp_replace(
                                    regexp_replace(data,'&','&amp;','g'),
                                    '>', '&gt;', 'g'
                                ),
                                '<', '&lt;', 'g'
                            ) || '</subfield>', ''
                        ) || '</datafield>' INTO uri_datafield
              FROM  oils_xpath_table(
                        'id',
                        'marc',
                        'biblio.record_entry',
                        '//*[@tag="856"][position()=' || i || ']/@ind1|' ||
                        '//*[@tag="856"][position()=' || i || ']/@ind2|' ||
                        '//*[@tag="856"][position()=' || i || ']/*/@code|' ||
                        '//*[@tag="856"][position()=' || i || ']/*[@code]',
                        'id=' || source_record
                    ) as t(id int,ind1 text, ind2 text,subfield text,data text);

            -- As most of the results will be NULL, protect against NULLifying
            -- the valid content that we do generate
            uri_text := uri_text || COALESCE(uri_datafield, '');
        END LOOP;

        IF uri_text <> '' THEN
            UPDATE  biblio.record_entry
              SET   marc = regexp_replace(marc,'(</[^>]*record>)', uri_text || E'\\1')
              WHERE id = target_record;
        END IF;

    END IF;

	-- Find and move metarecords to the target record
	SELECT	INTO metarec *
	  FROM	metabib.metarecord
	  WHERE	master_record = source_record;

	IF FOUND THEN
		UPDATE	metabib.metarecord
		  SET	master_record = target_record,
			mods = NULL
		  WHERE	id = metarec.id;

		moved_objects := moved_objects + 1;
	END IF;

	-- Find call numbers attached to the source ...
	FOR source_cn IN SELECT * FROM asset.call_number WHERE record = source_record LOOP

		SELECT	INTO target_cn *
		  FROM	asset.call_number
		  WHERE	label = source_cn.label
            AND prefix = source_cn.prefix
            AND suffix = source_cn.suffix
			AND owning_lib = source_cn.owning_lib
			AND record = target_record
			AND NOT deleted;

		-- ... and if there's a conflicting one on the target ...
		IF FOUND THEN

			-- ... move the copies to that, and ...
			UPDATE	asset.copy
			  SET	call_number = target_cn.id
			  WHERE	call_number = source_cn.id;

			-- ... move V holds to the move-target call number
			FOR hold IN SELECT * FROM action.hold_request WHERE target = source_cn.id AND hold_type = 'V' LOOP
		
				UPDATE	action.hold_request
				  SET	target = target_cn.id
				  WHERE	id = hold.id;
		
				moved_objects := moved_objects + 1;
			END LOOP;
        
            UPDATE asset.call_number SET deleted = TRUE WHERE id = source_cn.id;

		-- ... if not ...
		ELSE
			-- ... just move the call number to the target record
			UPDATE	asset.call_number
			  SET	record = target_record
			  WHERE	id = source_cn.id;
		END IF;

		moved_objects := moved_objects + 1;
	END LOOP;

	-- Find T holds targeting the source record ...
	FOR hold IN SELECT * FROM action.hold_request WHERE target = source_record AND hold_type = 'T' LOOP

		-- ... and move them to the target record
		UPDATE	action.hold_request
		  SET	target = target_record
		  WHERE	id = hold.id;

		moved_objects := moved_objects + 1;
	END LOOP;

	-- Find serial records targeting the source record ...
	FOR ser_rec IN SELECT * FROM serial.record_entry WHERE record = source_record LOOP
		-- ... and move them to the target record
		UPDATE	serial.record_entry
		  SET	record = target_record
		  WHERE	id = ser_rec.id;

		moved_objects := moved_objects + 1;
	END LOOP;

	-- Find serial subscriptions targeting the source record ...
	FOR ser_sub IN SELECT * FROM serial.subscription WHERE record_entry = source_record LOOP
		-- ... and move them to the target record
		UPDATE	serial.subscription
		  SET	record_entry = target_record
		  WHERE	id = ser_sub.id;

		moved_objects := moved_objects + 1;
	END LOOP;

	-- Find booking resource types targeting the source record ...
	FOR booking IN SELECT * FROM booking.resource_type WHERE record = source_record LOOP
		-- ... and move them to the target record
		UPDATE	booking.resource_type
		  SET	record = target_record
		  WHERE	id = booking.id;

		moved_objects := moved_objects + 1;
	END LOOP;

	-- Find acq lineitems targeting the source record ...
	FOR acq_lineitem IN SELECT * FROM acq.lineitem WHERE eg_bib_id = source_record LOOP
		-- ... and move them to the target record
		UPDATE	acq.lineitem
		  SET	eg_bib_id = target_record
		  WHERE	id = acq_lineitem.id;

		moved_objects := moved_objects + 1;
	END LOOP;

	-- Find acq user purchase requests targeting the source record ...
	FOR acq_request IN SELECT * FROM acq.user_request WHERE eg_bib = source_record LOOP
		-- ... and move them to the target record
		UPDATE	acq.user_request
		  SET	eg_bib = target_record
		  WHERE	id = acq_request.id;

		moved_objects := moved_objects + 1;
	END LOOP;

	-- Find parts attached to the source ...
	FOR source_part IN SELECT * FROM biblio.monograph_part WHERE record = source_record LOOP

		SELECT	INTO target_part *
		  FROM	biblio.monograph_part
		  WHERE	label = source_part.label
			AND record = target_record;

		-- ... and if there's a conflicting one on the target ...
		IF FOUND THEN

			-- ... move the copy-part maps to that, and ...
			UPDATE	asset.copy_part_map
			  SET	part = target_part.id
			  WHERE	part = source_part.id;

			-- ... move P holds to the move-target part
			FOR hold IN SELECT * FROM action.hold_request WHERE target = source_part.id AND hold_type = 'P' LOOP
		
				UPDATE	action.hold_request
				  SET	target = target_part.id
				  WHERE	id = hold.id;
		
				moved_objects := moved_objects + 1;
			END LOOP;

		-- ... if not ...
		ELSE
			-- ... just move the part to the target record
			UPDATE	biblio.monograph_part
			  SET	record = target_record
			  WHERE	id = source_part.id;
		END IF;

		moved_objects := moved_objects + 1;
	END LOOP;

	-- Find multi_home items attached to the source ...
	FOR multi_home IN SELECT * FROM biblio.peer_bib_copy_map WHERE peer_record = source_record LOOP
		-- ... and move them to the target record
		UPDATE	biblio.peer_bib_copy_map
		  SET	peer_record = target_record
		  WHERE	id = multi_home.id;

		moved_objects := moved_objects + 1;
	END LOOP;

	-- And delete mappings where the item's home bib was merged with the peer bib
	DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = (
		SELECT (SELECT record FROM asset.call_number WHERE id = call_number)
		FROM asset.copy WHERE id = target_copy
	);

    -- Apply merge tracking
    UPDATE biblio.record_entry 
        SET merge_date = NOW() WHERE id = target_record;

    UPDATE biblio.record_entry
        SET merge_date = NOW(), merged_to = target_record
        WHERE id = source_record;

    -- replace book bag entries of source_record with target_record
    UPDATE container.biblio_record_entry_bucket_item
        SET target_biblio_record_entry = target_record
        WHERE bucket IN (SELECT id FROM container.biblio_record_entry_bucket WHERE btype = 'bookbag')
        AND target_biblio_record_entry = source_record;

    -- move over record notes 
    UPDATE biblio.record_note 
        SET record = target_record, value = CONCAT(value,'; note merged from ',source_record::TEXT) 
        WHERE record = source_record
        AND NOT deleted;

    -- add note to record merge 
    INSERT INTO biblio.record_note (record, value) 
        VALUES (target_record,CONCAT('record ',source_record::TEXT,' merged on ',NOW()::TEXT));

    -- Finally, "delete" the source record
    UPDATE biblio.record_entry SET active = FALSE WHERE id = source_record;
    DELETE FROM biblio.record_entry WHERE id = source_record;

	-- That's all, folks!
	RETURN moved_objects;
END;

Function: asset.metarecord_copy_count(transcendant integer, unshadow bigint, available boolean)

Returns: SET OF record

Language: PLPGSQL

BEGIN
    IF staff IS TRUE THEN
        IF place > 0 THEN
            RETURN QUERY SELECT * FROM asset.staff_ou_metarecord_copy_count( place, rid );
        ELSE
            RETURN QUERY SELECT * FROM asset.staff_lasso_metarecord_copy_count( -place, rid );
        END IF;
    ELSE
        IF place > 0 THEN
            RETURN QUERY SELECT * FROM asset.opac_ou_metarecord_copy_count( place, rid );
        ELSE
            RETURN QUERY SELECT * FROM asset.opac_lasso_metarecord_copy_count( -place, rid );
        END IF;
    END IF;

    RETURN;
END;

Function: asset.metarecord_has_holdable_copy(ou bigint, rid integer)

Returns: boolean

Language: PLPGSQL

BEGIN
    PERFORM 1
        FROM
            asset.copy acp
            JOIN asset.call_number acn ON acp.call_number = acn.id
            JOIN asset.copy_location acpl ON acp.location = acpl.id
            JOIN config.copy_status ccs ON acp.status = ccs.id
            JOIN metabib.metarecord_source_map mmsm ON acn.record = mmsm.source
        WHERE
            mmsm.metarecord = rid
            AND acp.holdable = true
            AND acpl.holdable = true
            AND ccs.holdable = true
            AND acp.deleted = false
            AND acpl.deleted = false
            AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
        LIMIT 1;
    IF FOUND THEN
        RETURN true;
    END IF;
    RETURN FALSE;
END;

Function: asset.normalize_affix_sortkey()

Returns: trigger

Language: PLPGSQL

BEGIN
    NEW.label_sortkey := REGEXP_REPLACE(
        evergreen.lpad_number_substrings(
            naco_normalize(NEW.label),
            '0',
            10
        ),
        E'\\s+',
        '',
        'g'
    );
    RETURN NEW;
END;

Function: asset.opac_lasso_metarecord_copy_count(transcendant integer, unshadow bigint)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    ans RECORD;
    trans INT;
BEGIN
    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;

    FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
        RETURN QUERY
        WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
             available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
             mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
        SELECT  -1,
                ans.id,
                COUNT( av.id ),
                SUM( (cp.status = ANY (available_statuses.ids))::INT ),
                COUNT( av.id ),
                trans
          FROM  mask,
                org_list,
                available_statuses,
                asset.copy_vis_attr_cache av
                JOIN asset.copy cp ON (cp.id = av.target_copy)
                JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record)
          WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
          GROUP BY 1,2,6;

        IF NOT FOUND THEN
            RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
        END IF;

    END LOOP;   
                
    RETURN;     
END;            

Function: asset.opac_lasso_record_copy_count(transcendant integer, unshadow bigint)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    ans RECORD;
    trans INT;
BEGIN
    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;

    FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
        RETURN QUERY
        WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
             available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
             mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
        SELECT  -1,
                ans.id,
                COUNT( av.id ),
                SUM( (cp.status = ANY (available_statuses.ids))::INT ),
                COUNT( av.id ),
                trans
          FROM  mask,
                org_list,
                asset.copy_vis_attr_cache av
                JOIN asset.copy cp ON (cp.id = av.target_copy AND av.record = rid)
          WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
          GROUP BY 1,2,6;

        IF NOT FOUND THEN
            RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
        END IF;

    END LOOP;   
                
    RETURN;     
END;            

Function: asset.opac_ou_metarecord_copy_count(transcendant integer, unshadow bigint)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    ans RECORD;
    trans INT;
BEGIN
    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;

    FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
        RETURN QUERY
        WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
             available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
             mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
        SELECT  ans.depth,
                ans.id,
                COUNT( av.id ),
                SUM( (cp.status = ANY (available_statuses.ids))::INT ),
                COUNT( av.id ),
                trans
          FROM  mask,
                org_list,
                available_statuses,
                asset.copy_vis_attr_cache av
                JOIN asset.copy cp ON (cp.id = av.target_copy)
                JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = av.record)
          WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
          GROUP BY 1,2,6;

        IF NOT FOUND THEN
            RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
        END IF;

    END LOOP;

    RETURN;
END;

Function: asset.opac_ou_record_copy_count(transcendant integer, unshadow bigint)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    ans RECORD;
    trans INT;
BEGIN
    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;

    FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
        RETURN QUERY
        WITH org_list AS (SELECT ARRAY_AGG(id)::BIGINT[] AS orgs FROM actor.org_unit_descendants(ans.id) x),
             available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
             mask AS (SELECT c_attrs FROM asset.patron_default_visibility_mask() x)
        SELECT  ans.depth,
                ans.id,
                COUNT( av.id ),
                SUM( (cp.status = ANY (available_statuses.ids))::INT ),
                COUNT( av.id ),
                trans
          FROM  mask,
                available_statuses,
                org_list,
                asset.copy_vis_attr_cache av
                JOIN asset.copy cp ON (cp.id = av.target_copy AND av.record = rid)
                JOIN asset.call_number cn ON (cp.call_number = cn.id AND not cn.deleted)
          WHERE cp.circ_lib = ANY (org_list.orgs) AND av.vis_attr_vector @@ mask.c_attrs::query_int
          GROUP BY 1,2,6;

        IF NOT FOUND THEN
            RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
        END IF;

    END LOOP;

    RETURN;
END;

Function: asset.owning_lib_default()

Returns: text

Language: SQL

    SELECT  * FROM asset.invisible_orgs('owning_lib');

Function: asset.patron_default_visibility_mask()

Returns: SET OF record

Language: PLPGSQL

DECLARE
    copy_flags      TEXT; -- "c" attr

    owning_lib      TEXT; -- "c" attr
    circ_lib        TEXT; -- "c" attr
    status          TEXT; -- "c" attr
    location        TEXT; -- "c" attr
    location_group  TEXT; -- "c" attr

    luri_org        TEXT; -- "b" attr
    bib_sources     TEXT; -- "b" attr

    bib_tests       TEXT := '';
BEGIN
    copy_flags      := asset.all_visible_flags(); -- Will always have at least one

    owning_lib      := NULLIF(asset.owning_lib_default(),'!()');

    circ_lib        := NULLIF(asset.circ_lib_default(),'!()');
    status          := NULLIF(asset.status_default(),'!()');
    location        := NULLIF(asset.location_default(),'!()');
    location_group  := NULLIF(asset.location_group_default(),'!()');

    -- LURIs will be handled at the perl layer directly
    -- luri_org        := NULLIF(asset.luri_org_default(),'!()');
    bib_sources     := NULLIF(asset.bib_source_default(),'()');


    IF luri_org IS NOT NULL AND bib_sources IS NOT NULL THEN
        bib_tests := '('||ARRAY_TO_STRING( ARRAY[luri_org,bib_sources], '|')||')&('||luri_org||')&';
    ELSIF luri_org IS NOT NULL THEN
        bib_tests := luri_org || '&';
    ELSIF bib_sources IS NOT NULL THEN
        bib_tests := bib_sources || '|';
    END IF;

    RETURN QUERY SELECT bib_tests,
        '('||ARRAY_TO_STRING(
            ARRAY[copy_flags,owning_lib,circ_lib,status,location,location_group]::TEXT[],
            '&'
        )||')';
END;

Function: asset.record_copy_count(transcendant integer, unshadow bigint, available boolean)

Returns: SET OF record

Language: PLPGSQL

BEGIN
    IF staff IS TRUE THEN
        IF place > 0 THEN
            RETURN QUERY SELECT * FROM asset.staff_ou_record_copy_count( place, rid );
        ELSE
            RETURN QUERY SELECT * FROM asset.staff_lasso_record_copy_count( -place, rid );
        END IF;
    ELSE
        IF place > 0 THEN
            RETURN QUERY SELECT * FROM asset.opac_ou_record_copy_count( place, rid );
        ELSE
            RETURN QUERY SELECT * FROM asset.opac_lasso_record_copy_count( -place, rid );
        END IF;
    END IF;

    RETURN;
END;

Function: asset.record_has_holdable_copy(ou bigint, rid integer)

Returns: boolean

Language: PLPGSQL

BEGIN
    PERFORM 1
        FROM
            asset.copy acp
            JOIN asset.call_number acn ON acp.call_number = acn.id
            JOIN asset.copy_location acpl ON acp.location = acpl.id
            JOIN config.copy_status ccs ON acp.status = ccs.id
        WHERE
            acn.record = rid
            AND acp.holdable = true
            AND acpl.holdable = true
            AND ccs.holdable = true
            AND acp.deleted = false
            AND acpl.deleted = false
            AND acp.circ_lib IN (SELECT id FROM actor.org_unit_descendants(COALESCE($2,(SELECT id FROM evergreen.org_top()))))
        LIMIT 1;
    IF FOUND THEN
        RETURN true;
    END IF;
    RETURN FALSE;
END;

Function: asset.set_copy_tag_value()

Returns: trigger

Language: PLPGSQL

BEGIN
    IF NEW.value IS NULL THEN
        NEW.value = NEW.label;        
    END IF;

    RETURN NEW;
END;

Function: asset.staff_lasso_metarecord_copy_count(transcendant integer, unshadow bigint)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    ans RECORD;
    trans INT;
BEGIN
    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;

    FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
        RETURN QUERY
        SELECT  -1,
                ans.id,
                COUNT( cp.id ),
                SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
                COUNT( cp.id ),
                trans
          FROM
                actor.org_unit_descendants(ans.id) d
                JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
                JOIN asset.call_number cn ON (cn.id = cp.call_number AND NOT cn.deleted)
                JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = cn.record)
          GROUP BY 1,2,6;

        IF NOT FOUND THEN
            RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
        END IF;

    END LOOP;

    RETURN;
END;

Function: asset.staff_lasso_record_copy_count(transcendant integer, unshadow bigint)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    ans RECORD;
    trans INT;
BEGIN
    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;

    FOR ans IN SELECT u.org_unit AS id FROM actor.org_lasso_map AS u WHERE lasso = i_lasso LOOP
        RETURN QUERY
        SELECT  -1,
                ans.id,
                COUNT( cp.id ),
                SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
                SUM( CASE WHEN cl.opac_visible AND cp.opac_visible THEN 1 ELSE 0 END),
                trans
          FROM
                actor.org_unit_descendants(ans.id) d
                JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
                JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
                JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
          GROUP BY 1,2,6;

        IF NOT FOUND THEN
            RETURN QUERY SELECT -1, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
        END IF;

    END LOOP;

    RETURN;
END;

Function: asset.staff_ou_metarecord_copy_count(transcendant integer, unshadow bigint)

Returns: SET OF record

Language: PLPGSQL

DECLARE         
    ans RECORD; 
    trans INT;
BEGIN
    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) JOIN metabib.metarecord_source_map m ON (m.source = b.id) WHERE src.transcendant AND m.metarecord = rid;

    FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
        RETURN QUERY
        SELECT  ans.depth,
                ans.id,
                COUNT( cp.id ),
                SUM( CASE WHEN cp.status IN (0,7,12) THEN 1 ELSE 0 END ),
                COUNT( cp.id ),
                trans
          FROM
                actor.org_unit_descendants(ans.id) d
                JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
                JOIN asset.call_number cn ON (cn.id = cp.call_number AND NOT cn.deleted)
                JOIN metabib.metarecord_source_map m ON (m.metarecord = rid AND m.source = cn.record)
          GROUP BY 1,2,6;

        IF NOT FOUND THEN
            RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
        END IF;

    END LOOP;

    RETURN;
END;

Function: asset.staff_ou_record_copy_count(transcendant integer, unshadow bigint)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    ans RECORD;
    trans INT;
BEGIN
    SELECT 1 INTO trans FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = rid;

    FOR ans IN SELECT u.id, t.depth FROM actor.org_unit_ancestors(org) AS u JOIN actor.org_unit_type t ON (u.ou_type = t.id) LOOP
        RETURN QUERY
        WITH available_statuses AS (SELECT ARRAY_AGG(id) AS ids FROM config.copy_status WHERE is_available),
            cp AS(
                SELECT  cp.id,
                        (cp.status = ANY (available_statuses.ids))::INT as available,
                        (cl.opac_visible AND cp.opac_visible)::INT as opac_visible
                  FROM
                        available_statuses,
                        actor.org_unit_descendants(ans.id) d
                        JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
                        JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
                        JOIN asset.call_number cn ON (cn.record = rid AND cn.id = cp.call_number AND NOT cn.deleted)
            ),
            peer AS (
                select  cp.id,
                        (cp.status = ANY  (available_statuses.ids))::INT as available,
                        (cl.opac_visible AND cp.opac_visible)::INT as opac_visible
                FROM
                        available_statuses,
                        actor.org_unit_descendants(ans.id) d
                        JOIN asset.copy cp ON (cp.circ_lib = d.id AND NOT cp.deleted)
                        JOIN asset.copy_location cl ON (cp.location = cl.id AND NOT cl.deleted)
                        JOIN biblio.peer_bib_copy_map bp ON (bp.peer_record = rid AND bp.target_copy = cp.id)
            )
        select ans.depth, ans.id, count(id), sum(x.available::int), sum(x.opac_visible::int), trans
        from ((select * from cp) union (select * from peer)) x
        group by 1,2,6;

        IF NOT FOUND THEN
            RETURN QUERY SELECT ans.depth, ans.id, 0::BIGINT, 0::BIGINT, 0::BIGINT, trans;
        END IF;

    END LOOP;
    RETURN;
END;

Function: asset.stat_cat_check()

Returns: trigger

Language: PLPGSQL

DECLARE
    sipfield asset.stat_cat_sip_fields%ROWTYPE;
    use_count INT;
BEGIN
    IF NEW.sip_field IS NOT NULL THEN
        SELECT INTO sipfield * FROM asset.stat_cat_sip_fields WHERE field = NEW.sip_field;
        IF sipfield.one_only THEN
            SELECT INTO use_count count(id) FROM asset.stat_cat WHERE sip_field = NEW.sip_field AND id != NEW.id;
            IF use_count > 0 THEN
                RAISE EXCEPTION 'Sip field cannot be used twice';
            END IF;
        END IF;
    END IF;
    RETURN NEW;
END;

Function: asset.status_default()

Returns: text

Language: SQL

    SELECT  '!(' || STRING_AGG(search.calculate_visibility_attribute(id, 'status')::TEXT,'|') || ')'
      FROM  config.copy_status
      WHERE NOT opac_visible;

Function: asset.visible_orgs(otype text)

Returns: text

Language: SQL

    SELECT  '(' || STRING_AGG(search.calculate_visibility_attribute(id, $1)::TEXT,'|') || ')'
      FROM  actor.org_unit
      WHERE opac_visible;

Schema auditor


Table: auditor.acq_fund_debit_history

auditor.acq_fund_debit_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id integer NOT NULL
fund integer NOT NULL
origin_amount numeric NOT NULL
origin_currency_type text NOT NULL
amount numeric NOT NULL
encumbrance boolean NOT NULL
debit_type text NOT NULL
xfer_destination integer
create_time timestamp with time zone NOT NULL
invoice_entry integer

Index - Schema auditor


View: auditor.acq_fund_debit_lifecycle

auditor.acq_fund_debit_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id integer
fund integer
origin_amount numeric
origin_currency_type text
amount numeric
encumbrance boolean
debit_type text
xfer_destination integer
create_time timestamp with time zone
invoice_entry integer
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    fund_debit.id
,
    fund_debit.fund
,
    fund_debit.origin_amount
,
    fund_debit.origin_currency_type
,
    fund_debit.amount
,
    fund_debit.encumbrance
,
    fund_debit.debit_type
,
    fund_debit.xfer_destination
,
    fund_debit.create_time
,
    fund_debit.invoice_entry
   
FROM acq.fund_debit

UNION ALL
 
SELECT acq_fund_debit_history.audit_id
,
    acq_fund_debit_history.audit_time
,
    acq_fund_debit_history.audit_action
,
    acq_fund_debit_history.audit_user
,
    acq_fund_debit_history.audit_ws
,
    acq_fund_debit_history.id
,
    acq_fund_debit_history.fund
,
    acq_fund_debit_history.origin_amount
,
    acq_fund_debit_history.origin_currency_type
,
    acq_fund_debit_history.amount
,
    acq_fund_debit_history.encumbrance
,
    acq_fund_debit_history.debit_type
,
    acq_fund_debit_history.xfer_destination
,
    acq_fund_debit_history.create_time
,
    acq_fund_debit_history.invoice_entry
   
FROM auditor.acq_fund_debit_history;

Index - Schema auditor


Table: auditor.acq_invoice_entry_history

auditor.acq_invoice_entry_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id integer NOT NULL
invoice integer NOT NULL
purchase_order integer
lineitem integer
inv_item_count integer NOT NULL
phys_item_count integer
note text
billed_per_item boolean
cost_billed numeric(8,2)
actual_cost numeric(8,2)
amount_paid numeric(8,2)

Index - Schema auditor


View: auditor.acq_invoice_entry_lifecycle

auditor.acq_invoice_entry_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id integer
invoice integer
purchase_order integer
lineitem integer
inv_item_count integer
phys_item_count integer
note text
billed_per_item boolean
cost_billed numeric(8,2)
actual_cost numeric(8,2)
amount_paid numeric(8,2)
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    invoice_entry.id
,
    invoice_entry.invoice
,
    invoice_entry.purchase_order
,
    invoice_entry.lineitem
,
    invoice_entry.inv_item_count
,
    invoice_entry.phys_item_count
,
    invoice_entry.note
,
    invoice_entry.billed_per_item
,
    invoice_entry.cost_billed
,
    invoice_entry.actual_cost
,
    invoice_entry.amount_paid
   
FROM acq.invoice_entry

UNION ALL
 
SELECT acq_invoice_entry_history.audit_id
,
    acq_invoice_entry_history.audit_time
,
    acq_invoice_entry_history.audit_action
,
    acq_invoice_entry_history.audit_user
,
    acq_invoice_entry_history.audit_ws
,
    acq_invoice_entry_history.id
,
    acq_invoice_entry_history.invoice
,
    acq_invoice_entry_history.purchase_order
,
    acq_invoice_entry_history.lineitem
,
    acq_invoice_entry_history.inv_item_count
,
    acq_invoice_entry_history.phys_item_count
,
    acq_invoice_entry_history.note
,
    acq_invoice_entry_history.billed_per_item
,
    acq_invoice_entry_history.cost_billed
,
    acq_invoice_entry_history.actual_cost
,
    acq_invoice_entry_history.amount_paid
   
FROM auditor.acq_invoice_entry_history;

Index - Schema auditor


Table: auditor.acq_invoice_history

auditor.acq_invoice_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id integer NOT NULL
receiver integer NOT NULL
provider integer NOT NULL
shipper integer NOT NULL
recv_date timestamp with time zone NOT NULL
recv_method text NOT NULL
inv_type text
inv_ident text NOT NULL
payment_auth text
payment_method text
note text
close_date timestamp with time zone
closed_by integer

Index - Schema auditor


Table: auditor.acq_invoice_item_history

auditor.acq_invoice_item_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id integer NOT NULL
invoice integer NOT NULL
purchase_order integer
fund_debit integer
inv_item_type text NOT NULL
title text
author text
note text
cost_billed numeric(8,2)
actual_cost numeric(8,2)
fund integer
amount_paid numeric(8,2)
po_item integer
target bigint

Index - Schema auditor


View: auditor.acq_invoice_item_lifecycle

auditor.acq_invoice_item_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id integer
invoice integer
purchase_order integer
fund_debit integer
inv_item_type text
title text
author text
note text
cost_billed numeric(8,2)
actual_cost numeric(8,2)
fund integer
amount_paid numeric(8,2)
po_item integer
target bigint
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    invoice_item.id
,
    invoice_item.invoice
,
    invoice_item.purchase_order
,
    invoice_item.fund_debit
,
    invoice_item.inv_item_type
,
    invoice_item.title
,
    invoice_item.author
,
    invoice_item.note
,
    invoice_item.cost_billed
,
    invoice_item.actual_cost
,
    invoice_item.fund
,
    invoice_item.amount_paid
,
    invoice_item.po_item
,
    invoice_item.target
   
FROM acq.invoice_item

UNION ALL
 
SELECT acq_invoice_item_history.audit_id
,
    acq_invoice_item_history.audit_time
,
    acq_invoice_item_history.audit_action
,
    acq_invoice_item_history.audit_user
,
    acq_invoice_item_history.audit_ws
,
    acq_invoice_item_history.id
,
    acq_invoice_item_history.invoice
,
    acq_invoice_item_history.purchase_order
,
    acq_invoice_item_history.fund_debit
,
    acq_invoice_item_history.inv_item_type
,
    acq_invoice_item_history.title
,
    acq_invoice_item_history.author
,
    acq_invoice_item_history.note
,
    acq_invoice_item_history.cost_billed
,
    acq_invoice_item_history.actual_cost
,
    acq_invoice_item_history.fund
,
    acq_invoice_item_history.amount_paid
,
    acq_invoice_item_history.po_item
,
    acq_invoice_item_history.target
   
FROM auditor.acq_invoice_item_history;

Index - Schema auditor


View: auditor.acq_invoice_lifecycle

auditor.acq_invoice_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id integer
receiver integer
provider integer
shipper integer
recv_date timestamp with time zone
recv_method text
inv_type text
inv_ident text
payment_auth text
payment_method text
note text
close_date timestamp with time zone
closed_by integer
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    invoice.id
,
    invoice.receiver
,
    invoice.provider
,
    invoice.shipper
,
    invoice.recv_date
,
    invoice.recv_method
,
    invoice.inv_type
,
    invoice.inv_ident
,
    invoice.payment_auth
,
    invoice.payment_method
,
    invoice.note
,
    invoice.close_date
,
    invoice.closed_by
   
FROM acq.invoice

UNION ALL
 
SELECT acq_invoice_history.audit_id
,
    acq_invoice_history.audit_time
,
    acq_invoice_history.audit_action
,
    acq_invoice_history.audit_user
,
    acq_invoice_history.audit_ws
,
    acq_invoice_history.id
,
    acq_invoice_history.receiver
,
    acq_invoice_history.provider
,
    acq_invoice_history.shipper
,
    acq_invoice_history.recv_date
,
    acq_invoice_history.recv_method
,
    acq_invoice_history.inv_type
,
    acq_invoice_history.inv_ident
,
    acq_invoice_history.payment_auth
,
    acq_invoice_history.payment_method
,
    acq_invoice_history.note
,
    acq_invoice_history.close_date
,
    acq_invoice_history.closed_by
   
FROM auditor.acq_invoice_history;

Index - Schema auditor


Table: auditor.actor_org_unit_history

auditor.actor_org_unit_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id integer NOT NULL
parent_ou integer
ou_type integer NOT NULL
ill_address integer
holds_address integer
mailing_address integer
billing_address integer
shortname text NOT NULL
name text NOT NULL
email text
phone text
opac_visible boolean NOT NULL
fiscal_calendar integer NOT NULL

Index - Schema auditor


View: auditor.actor_org_unit_lifecycle

auditor.actor_org_unit_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id integer
parent_ou integer
ou_type integer
ill_address integer
holds_address integer
mailing_address integer
billing_address integer
shortname text
name text
email text
phone text
opac_visible boolean
fiscal_calendar integer
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    org_unit.id
,
    org_unit.parent_ou
,
    org_unit.ou_type
,
    org_unit.ill_address
,
    org_unit.holds_address
,
    org_unit.mailing_address
,
    org_unit.billing_address
,
    org_unit.shortname
,
    org_unit.name
,
    org_unit.email
,
    org_unit.phone
,
    org_unit.opac_visible
,
    org_unit.fiscal_calendar
   
FROM actor.org_unit

UNION ALL
 
SELECT actor_org_unit_history.audit_id
,
    actor_org_unit_history.audit_time
,
    actor_org_unit_history.audit_action
,
    actor_org_unit_history.audit_user
,
    actor_org_unit_history.audit_ws
,
    actor_org_unit_history.id
,
    actor_org_unit_history.parent_ou
,
    actor_org_unit_history.ou_type
,
    actor_org_unit_history.ill_address
,
    actor_org_unit_history.holds_address
,
    actor_org_unit_history.mailing_address
,
    actor_org_unit_history.billing_address
,
    actor_org_unit_history.shortname
,
    actor_org_unit_history.name
,
    actor_org_unit_history.email
,
    actor_org_unit_history.phone
,
    actor_org_unit_history.opac_visible
,
    actor_org_unit_history.fiscal_calendar
   
FROM auditor.actor_org_unit_history;

Index - Schema auditor


Table: auditor.actor_usr_address_history

auditor.actor_usr_address_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id integer NOT NULL
valid boolean NOT NULL
within_city_limits boolean NOT NULL
address_type text NOT NULL
usr integer NOT NULL
street1 text NOT NULL
street2 text
city text NOT NULL
county text
state text
country text NOT NULL
post_code text NOT NULL
pending boolean NOT NULL
replaces integer
aud_actor_usr_address_hist_id_idx id

Index - Schema auditor


View: auditor.actor_usr_address_lifecycle

auditor.actor_usr_address_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id integer
valid boolean
within_city_limits boolean
address_type text
usr integer
street1 text
street2 text
city text
county text
state text
country text
post_code text
pending boolean
replaces integer
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    usr_address.id
,
    usr_address.valid
,
    usr_address.within_city_limits
,
    usr_address.address_type
,
    usr_address.usr
,
    usr_address.street1
,
    usr_address.street2
,
    usr_address.city
,
    usr_address.county
,
    usr_address.state
,
    usr_address.country
,
    usr_address.post_code
,
    usr_address.pending
,
    usr_address.replaces
   
FROM actor.usr_address

UNION ALL
 
SELECT actor_usr_address_history.audit_id
,
    actor_usr_address_history.audit_time
,
    actor_usr_address_history.audit_action
,
    actor_usr_address_history.audit_user
,
    actor_usr_address_history.audit_ws
,
    actor_usr_address_history.id
,
    actor_usr_address_history.valid
,
    actor_usr_address_history.within_city_limits
,
    actor_usr_address_history.address_type
,
    actor_usr_address_history.usr
,
    actor_usr_address_history.street1
,
    actor_usr_address_history.street2
,
    actor_usr_address_history.city
,
    actor_usr_address_history.county
,
    actor_usr_address_history.state
,
    actor_usr_address_history.country
,
    actor_usr_address_history.post_code
,
    actor_usr_address_history.pending
,
    actor_usr_address_history.replaces
   
FROM auditor.actor_usr_address_history;

Index - Schema auditor


Table: auditor.actor_usr_history

auditor.actor_usr_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id integer NOT NULL
card integer
profile integer NOT NULL
usrname text NOT NULL
email text
passwd text NOT NULL
standing integer NOT NULL
ident_type integer NOT NULL
ident_value text
ident_type2 integer
ident_value2 text
net_access_level integer NOT NULL
photo_url text
prefix text
first_given_name text NOT NULL
second_given_name text
family_name text NOT NULL
suffix text
guardian text
pref_prefix text
pref_first_given_name text
pref_second_given_name text
pref_family_name text
pref_suffix text
name_keywords text
name_kw_tsvector tsvector
alias text
day_phone text
evening_phone text
other_phone text
mailing_address integer
billing_address integer
home_ou integer NOT NULL
dob date
active boolean NOT NULL
master_account boolean NOT NULL
super_user boolean NOT NULL
barred boolean NOT NULL
deleted boolean NOT NULL
juvenile boolean NOT NULL
usrgroup integer NOT NULL
claims_returned_count integer NOT NULL
credit_forward_balance numeric(6,2) NOT NULL
last_xact_id text NOT NULL
create_date timestamp with time zone NOT NULL
expire_date timestamp with time zone NOT NULL
claims_never_checked_out_count integer NOT NULL
last_update_time timestamp with time zone
locale text
aud_actor_usr_hist_id_idx id

Index - Schema auditor


View: auditor.actor_usr_lifecycle

auditor.actor_usr_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id integer
card integer
profile integer
usrname text
email text
passwd text
standing integer
ident_type integer
ident_value text
ident_type2 integer
ident_value2 text
net_access_level integer
photo_url text
prefix text
first_given_name text
second_given_name text
family_name text
suffix text
guardian text
pref_prefix text
pref_first_given_name text
pref_second_given_name text
pref_family_name text
pref_suffix text
name_keywords text
name_kw_tsvector tsvector
alias text
day_phone text
evening_phone text
other_phone text
mailing_address integer
billing_address integer
home_ou integer
dob date
active boolean
master_account boolean
super_user boolean
barred boolean
deleted boolean
juvenile boolean
usrgroup integer
claims_returned_count integer
credit_forward_balance numeric(6,2)
last_xact_id text
create_date timestamp with time zone
expire_date timestamp with time zone
claims_never_checked_out_count integer
last_update_time timestamp with time zone
locale text
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    usr.id
,
    usr.card
,
    usr.profile
,
    usr.usrname
,
    usr.email
,
    usr.passwd
,
    usr.standing
,
    usr.ident_type
,
    usr.ident_value
,
    usr.ident_type2
,
    usr.ident_value2
,
    usr.net_access_level
,
    usr.photo_url
,
    usr.prefix
,
    usr.first_given_name
,
    usr.second_given_name
,
    usr.family_name
,
    usr.suffix
,
    usr.guardian
,
    usr.pref_prefix
,
    usr.pref_first_given_name
,
    usr.pref_second_given_name
,
    usr.pref_family_name
,
    usr.pref_suffix
,
    usr.name_keywords
,
    usr.name_kw_tsvector
,
    usr.alias
,
    usr.day_phone
,
    usr.evening_phone
,
    usr.other_phone
,
    usr.mailing_address
,
    usr.billing_address
,
    usr.home_ou
,
    usr.dob
,
    usr.active
,
    usr.master_account
,
    usr.super_user
,
    usr.barred
,
    usr.deleted
,
    usr.juvenile
,
    usr.usrgroup
,
    usr.claims_returned_count
,
    usr.credit_forward_balance
,
    usr.last_xact_id
,
    usr.create_date
,
    usr.expire_date
,
    usr.claims_never_checked_out_count
,
    usr.last_update_time
,
    usr.locale
   
FROM actor.usr

UNION ALL
 
SELECT actor_usr_history.audit_id
,
    actor_usr_history.audit_time
,
    actor_usr_history.audit_action
,
    actor_usr_history.audit_user
,
    actor_usr_history.audit_ws
,
    actor_usr_history.id
,
    actor_usr_history.card
,
    actor_usr_history.profile
,
    actor_usr_history.usrname
,
    actor_usr_history.email
,
    actor_usr_history.passwd
,
    actor_usr_history.standing
,
    actor_usr_history.ident_type
,
    actor_usr_history.ident_value
,
    actor_usr_history.ident_type2
,
    actor_usr_history.ident_value2
,
    actor_usr_history.net_access_level
,
    actor_usr_history.photo_url
,
    actor_usr_history.prefix
,
    actor_usr_history.first_given_name
,
    actor_usr_history.second_given_name
,
    actor_usr_history.family_name
,
    actor_usr_history.suffix
,
    actor_usr_history.guardian
,
    actor_usr_history.pref_prefix
,
    actor_usr_history.pref_first_given_name
,
    actor_usr_history.pref_second_given_name
,
    actor_usr_history.pref_family_name
,
    actor_usr_history.pref_suffix
,
    actor_usr_history.name_keywords
,
    actor_usr_history.name_kw_tsvector
,
    actor_usr_history.alias
,
    actor_usr_history.day_phone
,
    actor_usr_history.evening_phone
,
    actor_usr_history.other_phone
,
    actor_usr_history.mailing_address
,
    actor_usr_history.billing_address
,
    actor_usr_history.home_ou
,
    actor_usr_history.dob
,
    actor_usr_history.active
,
    actor_usr_history.master_account
,
    actor_usr_history.super_user
,
    actor_usr_history.barred
,
    actor_usr_history.deleted
,
    actor_usr_history.juvenile
,
    actor_usr_history.usrgroup
,
    actor_usr_history.claims_returned_count
,
    actor_usr_history.credit_forward_balance
,
    actor_usr_history.last_xact_id
,
    actor_usr_history.create_date
,
    actor_usr_history.expire_date
,
    actor_usr_history.claims_never_checked_out_count
,
    actor_usr_history.last_update_time
,
    actor_usr_history.locale
   
FROM auditor.actor_usr_history;

Index - Schema auditor


Table: auditor.asset_call_number_history

auditor.asset_call_number_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id bigint NOT NULL
creator bigint NOT NULL
create_date timestamp with time zone
editor bigint NOT NULL
edit_date timestamp with time zone
record bigint NOT NULL
owning_lib integer NOT NULL
label text NOT NULL
deleted boolean NOT NULL
prefix integer NOT NULL
suffix integer NOT NULL
label_class bigint NOT NULL
label_sortkey text
aud_asset_cn_hist_creator_idx creator aud_asset_cn_hist_editor_idx editor

Index - Schema auditor


View: auditor.asset_call_number_lifecycle

auditor.asset_call_number_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id bigint
creator bigint
create_date timestamp with time zone
editor bigint
edit_date timestamp with time zone
record bigint
owning_lib integer
label text
deleted boolean
prefix integer
suffix integer
label_class bigint
label_sortkey text
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    call_number.id
,
    call_number.creator
,
    call_number.create_date
,
    call_number.editor
,
    call_number.edit_date
,
    call_number.record
,
    call_number.owning_lib
,
    call_number.label
,
    call_number.deleted
,
    call_number.prefix
,
    call_number.suffix
,
    call_number.label_class
,
    call_number.label_sortkey
   
FROM asset.call_number

UNION ALL
 
SELECT asset_call_number_history.audit_id
,
    asset_call_number_history.audit_time
,
    asset_call_number_history.audit_action
,
    asset_call_number_history.audit_user
,
    asset_call_number_history.audit_ws
,
    asset_call_number_history.id
,
    asset_call_number_history.creator
,
    asset_call_number_history.create_date
,
    asset_call_number_history.editor
,
    asset_call_number_history.edit_date
,
    asset_call_number_history.record
,
    asset_call_number_history.owning_lib
,
    asset_call_number_history.label
,
    asset_call_number_history.deleted
,
    asset_call_number_history.prefix
,
    asset_call_number_history.suffix
,
    asset_call_number_history.label_class
,
    asset_call_number_history.label_sortkey
   
FROM auditor.asset_call_number_history;

Index - Schema auditor


Table: auditor.asset_copy_history

auditor.asset_copy_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id bigint NOT NULL
circ_lib integer NOT NULL
creator bigint NOT NULL
call_number bigint NOT NULL
editor bigint NOT NULL
create_date timestamp with time zone
edit_date timestamp with time zone
copy_number integer
status integer NOT NULL
location integer NOT NULL
loan_duration integer NOT NULL
fine_level integer NOT NULL
age_protect integer
circulate boolean NOT NULL
deposit boolean NOT NULL
ref boolean NOT NULL
holdable boolean NOT NULL
deposit_amount numeric(6,2) NOT NULL
price numeric(8,2)
barcode text NOT NULL
circ_modifier text
circ_as_type text
dummy_title text
dummy_author text
alert_message text
opac_visible boolean NOT NULL
deleted boolean NOT NULL
floating integer
dummy_isbn text
status_changed_time timestamp with time zone
active_date timestamp with time zone
mint_condition boolean NOT NULL
cost numeric(8,2)
aud_asset_cp_hist_creator_idx creator aud_asset_cp_hist_editor_idx editor

Index - Schema auditor


View: auditor.asset_copy_lifecycle

auditor.asset_copy_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id bigint
circ_lib integer
creator bigint
call_number bigint
editor bigint
create_date timestamp with time zone
edit_date timestamp with time zone
copy_number integer
status integer
location integer
loan_duration integer
fine_level integer
age_protect integer
circulate boolean
deposit boolean
ref boolean
holdable boolean
deposit_amount numeric(6,2)
price numeric(8,2)
barcode text
circ_modifier text
circ_as_type text
dummy_title text
dummy_author text
alert_message text
opac_visible boolean
deleted boolean
floating integer
dummy_isbn text
status_changed_time timestamp with time zone
active_date timestamp with time zone
mint_condition boolean
cost numeric(8,2)
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    copy.id
,
    copy.circ_lib
,
    copy.creator
,
    copy.call_number
,
    copy.editor
,
    copy.create_date
,
    copy.edit_date
,
    copy.copy_number
,
    copy.status
,
    copy.location
,
    copy.loan_duration
,
    copy.fine_level
,
    copy.age_protect
,
    copy.circulate
,
    copy.deposit
,
    copy.ref
,
    copy.holdable
,
    copy.deposit_amount
,
    copy.price
,
    copy.barcode
,
    copy.circ_modifier
,
    copy.circ_as_type
,
    copy.dummy_title
,
    copy.dummy_author
,
    copy.alert_message
,
    copy.opac_visible
,
    copy.deleted
,
    copy.floating
,
    copy.dummy_isbn
,
    copy.status_changed_time
,
    copy.active_date
,
    copy.mint_condition
,
    copy.cost
   
FROM asset.copy

UNION ALL
 
SELECT asset_copy_history.audit_id
,
    asset_copy_history.audit_time
,
    asset_copy_history.audit_action
,
    asset_copy_history.audit_user
,
    asset_copy_history.audit_ws
,
    asset_copy_history.id
,
    asset_copy_history.circ_lib
,
    asset_copy_history.creator
,
    asset_copy_history.call_number
,
    asset_copy_history.editor
,
    asset_copy_history.create_date
,
    asset_copy_history.edit_date
,
    asset_copy_history.copy_number
,
    asset_copy_history.status
,
    asset_copy_history.location
,
    asset_copy_history.loan_duration
,
    asset_copy_history.fine_level
,
    asset_copy_history.age_protect
,
    asset_copy_history.circulate
,
    asset_copy_history.deposit
,
    asset_copy_history.ref
,
    asset_copy_history.holdable
,
    asset_copy_history.deposit_amount
,
    asset_copy_history.price
,
    asset_copy_history.barcode
,
    asset_copy_history.circ_modifier
,
    asset_copy_history.circ_as_type
,
    asset_copy_history.dummy_title
,
    asset_copy_history.dummy_author
,
    asset_copy_history.alert_message
,
    asset_copy_history.opac_visible
,
    asset_copy_history.deleted
,
    asset_copy_history.floating
,
    asset_copy_history.dummy_isbn
,
    asset_copy_history.status_changed_time
,
    asset_copy_history.active_date
,
    asset_copy_history.mint_condition
,
    asset_copy_history.cost
   
FROM auditor.asset_copy_history;

Index - Schema auditor


Table: auditor.biblio_record_entry_history

auditor.biblio_record_entry_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id bigint NOT NULL
creator integer NOT NULL
editor integer NOT NULL
source integer
quality integer
create_date timestamp with time zone NOT NULL
edit_date timestamp with time zone NOT NULL
active boolean NOT NULL
deleted boolean NOT NULL
fingerprint text
tcn_source text NOT NULL
tcn_value text NOT NULL
marc text NOT NULL
last_xact_id text NOT NULL
vis_attr_vector integer[]
owner integer
share_depth integer
merge_date timestamp with time zone
merged_to bigint
aud_bib_rec_entry_hist_creator_idx creator aud_bib_rec_entry_hist_editor_idx editor

Index - Schema auditor


View: auditor.biblio_record_entry_lifecycle

auditor.biblio_record_entry_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id bigint
creator integer
editor integer
source integer
quality integer
create_date timestamp with time zone
edit_date timestamp with time zone
active boolean
deleted boolean
fingerprint text
tcn_source text
tcn_value text
marc text
last_xact_id text
vis_attr_vector integer[]
owner integer
share_depth integer
merge_date timestamp with time zone
merged_to bigint
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    record_entry.id
,
    record_entry.creator
,
    record_entry.editor
,
    record_entry.source
,
    record_entry.quality
,
    record_entry.create_date
,
    record_entry.edit_date
,
    record_entry.active
,
    record_entry.deleted
,
    record_entry.fingerprint
,
    record_entry.tcn_source
,
    record_entry.tcn_value
,
    record_entry.marc
,
    record_entry.last_xact_id
,
    record_entry.vis_attr_vector
,
    record_entry.owner
,
    record_entry.share_depth
,
    record_entry.merge_date
,
    record_entry.merged_to
   
FROM biblio.record_entry

UNION ALL
 
SELECT biblio_record_entry_history.audit_id
,
    biblio_record_entry_history.audit_time
,
    biblio_record_entry_history.audit_action
,
    biblio_record_entry_history.audit_user
,
    biblio_record_entry_history.audit_ws
,
    biblio_record_entry_history.id
,
    biblio_record_entry_history.creator
,
    biblio_record_entry_history.editor
,
    biblio_record_entry_history.source
,
    biblio_record_entry_history.quality
,
    biblio_record_entry_history.create_date
,
    biblio_record_entry_history.edit_date
,
    biblio_record_entry_history.active
,
    biblio_record_entry_history.deleted
,
    biblio_record_entry_history.fingerprint
,
    biblio_record_entry_history.tcn_source
,
    biblio_record_entry_history.tcn_value
,
    biblio_record_entry_history.marc
,
    biblio_record_entry_history.last_xact_id
,
    biblio_record_entry_history.vis_attr_vector
,
    biblio_record_entry_history.owner
,
    biblio_record_entry_history.share_depth
,
    biblio_record_entry_history.merge_date
,
    biblio_record_entry_history.merged_to
   
FROM auditor.biblio_record_entry_history;

Index - Schema auditor


Table: auditor.serial_unit_history

auditor.serial_unit_history Structure
F-Key Name Type Description
audit_id bigint PRIMARY KEY
audit_time timestamp with time zone NOT NULL
audit_action text NOT NULL
audit_user integer
audit_ws integer
id bigint NOT NULL
circ_lib integer NOT NULL
creator bigint NOT NULL
call_number bigint NOT NULL
editor bigint NOT NULL
create_date timestamp with time zone
edit_date timestamp with time zone
copy_number integer
status integer NOT NULL
location integer NOT NULL
loan_duration integer NOT NULL
fine_level integer NOT NULL
age_protect integer
circulate boolean NOT NULL
deposit boolean NOT NULL
ref boolean NOT NULL
holdable boolean NOT NULL
deposit_amount numeric(6,2) NOT NULL
price numeric(8,2)
barcode text NOT NULL
circ_modifier text
circ_as_type text
dummy_title text
dummy_author text
alert_message text
opac_visible boolean NOT NULL
deleted boolean NOT NULL
floating integer
dummy_isbn text
status_changed_time timestamp with time zone
active_date timestamp with time zone
mint_condition boolean NOT NULL
cost numeric(8,2)
sort_key text
detailed_contents text NOT NULL
summary_contents text NOT NULL
aud_serial_unit_hist_creator_idx creator aud_serial_unit_hist_editor_idx editor

Index - Schema auditor


View: auditor.serial_unit_lifecycle

auditor.serial_unit_lifecycle Structure
F-Key Name Type Description
audit_id bigint
audit_time timestamp with time zone
audit_action text
audit_user integer
audit_ws integer
id bigint
circ_lib integer
creator bigint
call_number bigint
editor bigint
create_date timestamp with time zone
edit_date timestamp with time zone
copy_number integer
status integer
location integer
loan_duration integer
fine_level integer
age_protect integer
circulate boolean
deposit boolean
ref boolean
holdable boolean
deposit_amount numeric(6,2)
price numeric(8,2)
barcode text
circ_modifier text
circ_as_type text
dummy_title text
dummy_author text
alert_message text
opac_visible boolean
deleted boolean
floating integer
dummy_isbn text
status_changed_time timestamp with time zone
active_date timestamp with time zone
mint_condition boolean
cost numeric(8,2)
sort_key text
detailed_contents text
summary_contents text
SELECT'-1'::integer AS audit_id
,
    now
() AS audit_time
,
    '-'::text AS audit_action
,
    '-1'::integer AS audit_user
,
    '-1'::integer AS audit_ws
,
    unit.id
,
    unit.circ_lib
,
    unit.creator
,
    unit.call_number
,
    unit.editor
,
    unit.create_date
,
    unit.edit_date
,
    unit.copy_number
,
    unit.status
,
    unit.location
,
    unit.loan_duration
,
    unit.fine_level
,
    unit.age_protect
,
    unit.circulate
,
    unit.deposit
,
    unit.ref
,
    unit.holdable
,
    unit.deposit_amount
,
    unit.price
,
    unit.barcode
,
    unit.circ_modifier
,
    unit.circ_as_type
,
    unit.dummy_title
,
    unit.dummy_author
,
    unit.alert_message
,
    unit.opac_visible
,
    unit.deleted
,
    unit.floating
,
    unit.dummy_isbn
,
    unit.status_changed_time
,
    unit.active_date
,
    unit.mint_condition
,
    unit.cost
,
    unit.sort_key
,
    unit.detailed_contents
,
    unit.summary_contents
   
FROM serial.unit

UNION ALL
 
SELECT serial_unit_history.audit_id
,
    serial_unit_history.audit_time
,
    serial_unit_history.audit_action
,
    serial_unit_history.audit_user
,
    serial_unit_history.audit_ws
,
    serial_unit_history.id
,
    serial_unit_history.circ_lib
,
    serial_unit_history.creator
,
    serial_unit_history.call_number
,
    serial_unit_history.editor
,
    serial_unit_history.create_date
,
    serial_unit_history.edit_date
,
    serial_unit_history.copy_number
,
    serial_unit_history.status
,
    serial_unit_history.location
,
    serial_unit_history.loan_duration
,
    serial_unit_history.fine_level
,
    serial_unit_history.age_protect
,
    serial_unit_history.circulate
,
    serial_unit_history.deposit
,
    serial_unit_history.ref
,
    serial_unit_history.holdable
,
    serial_unit_history.deposit_amount
,
    serial_unit_history.price
,
    serial_unit_history.barcode
,
    serial_unit_history.circ_modifier
,
    serial_unit_history.circ_as_type
,
    serial_unit_history.dummy_title
,
    serial_unit_history.dummy_author
,
    serial_unit_history.alert_message
,
    serial_unit_history.opac_visible
,
    serial_unit_history.deleted
,
    serial_unit_history.floating
,
    serial_unit_history.dummy_isbn
,
    serial_unit_history.status_changed_time
,
    serial_unit_history.active_date
,
    serial_unit_history.mint_condition
,
    serial_unit_history.cost
,
    serial_unit_history.sort_key
,
    serial_unit_history.detailed_contents
,
    serial_unit_history.summary_contents
   
FROM auditor.serial_unit_history;

Index - Schema auditor


Function: auditor.audit_acq_fund_debit_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.acq_fund_debit_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, fund, origin_amount, origin_currency_type, amount, encumbrance, debit_type, xfer_destination, create_time, invoice_entry )
                SELECT  nextval('auditor.acq_fund_debit_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.fund, OLD.origin_amount, OLD.origin_currency_type, OLD.amount, OLD.encumbrance, OLD.debit_type, OLD.xfer_destination, OLD.create_time, OLD.invoice_entry
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_acq_invoice_entry_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.acq_invoice_entry_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, invoice, purchase_order, lineitem, inv_item_count, phys_item_count, note, billed_per_item, cost_billed, actual_cost, amount_paid )
                SELECT  nextval('auditor.acq_invoice_entry_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.invoice, OLD.purchase_order, OLD.lineitem, OLD.inv_item_count, OLD.phys_item_count, OLD.note, OLD.billed_per_item, OLD.cost_billed, OLD.actual_cost, OLD.amount_paid
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_acq_invoice_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.acq_invoice_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, receiver, provider, shipper, recv_date, recv_method, inv_type, inv_ident, payment_auth, payment_method, note, close_date, closed_by )
                SELECT  nextval('auditor.acq_invoice_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.receiver, OLD.provider, OLD.shipper, OLD.recv_date, OLD.recv_method, OLD.inv_type, OLD.inv_ident, OLD.payment_auth, OLD.payment_method, OLD.note, OLD.close_date, OLD.closed_by
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_acq_invoice_item_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.acq_invoice_item_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, invoice, purchase_order, fund_debit, inv_item_type, title, author, note, cost_billed, actual_cost, fund, amount_paid, po_item, target )
                SELECT  nextval('auditor.acq_invoice_item_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.invoice, OLD.purchase_order, OLD.fund_debit, OLD.inv_item_type, OLD.title, OLD.author, OLD.note, OLD.cost_billed, OLD.actual_cost, OLD.fund, OLD.amount_paid, OLD.po_item, OLD.target
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_actor_org_unit_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.actor_org_unit_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, parent_ou, ou_type, ill_address, holds_address, mailing_address, billing_address, shortname, name, email, phone, opac_visible, fiscal_calendar )
                SELECT  nextval('auditor.actor_org_unit_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.parent_ou, OLD.ou_type, OLD.ill_address, OLD.holds_address, OLD.mailing_address, OLD.billing_address, OLD.shortname, OLD.name, OLD.email, OLD.phone, OLD.opac_visible, OLD.fiscal_calendar
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_actor_usr_address_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.actor_usr_address_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, valid, within_city_limits, address_type, usr, street1, street2, city, county, state, country, post_code, pending, replaces )
                SELECT  nextval('auditor.actor_usr_address_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.valid, OLD.within_city_limits, OLD.address_type, OLD.usr, OLD.street1, OLD.street2, OLD.city, OLD.county, OLD.state, OLD.country, OLD.post_code, OLD.pending, OLD.replaces
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_actor_usr_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.actor_usr_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, card, profile, usrname, email, passwd, standing, ident_type, ident_value, ident_type2, ident_value2, net_access_level, photo_url, prefix, first_given_name, second_given_name, family_name, suffix, guardian, pref_prefix, pref_first_given_name, pref_second_given_name, pref_family_name, pref_suffix, name_keywords, name_kw_tsvector, alias, day_phone, evening_phone, other_phone, mailing_address, billing_address, home_ou, dob, active, master_account, super_user, barred, deleted, juvenile, usrgroup, claims_returned_count, credit_forward_balance, last_xact_id, create_date, expire_date, claims_never_checked_out_count, last_update_time, locale )
                SELECT  nextval('auditor.actor_usr_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.card, OLD.profile, OLD.usrname, OLD.email, OLD.passwd, OLD.standing, OLD.ident_type, OLD.ident_value, OLD.ident_type2, OLD.ident_value2, OLD.net_access_level, OLD.photo_url, OLD.prefix, OLD.first_given_name, OLD.second_given_name, OLD.family_name, OLD.suffix, OLD.guardian, OLD.pref_prefix, OLD.pref_first_given_name, OLD.pref_second_given_name, OLD.pref_family_name, OLD.pref_suffix, OLD.name_keywords, OLD.name_kw_tsvector, OLD.alias, OLD.day_phone, OLD.evening_phone, OLD.other_phone, OLD.mailing_address, OLD.billing_address, OLD.home_ou, OLD.dob, OLD.active, OLD.master_account, OLD.super_user, OLD.barred, OLD.deleted, OLD.juvenile, OLD.usrgroup, OLD.claims_returned_count, OLD.credit_forward_balance, OLD.last_xact_id, OLD.create_date, OLD.expire_date, OLD.claims_never_checked_out_count, OLD.last_update_time, OLD.locale
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_asset_call_number_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.asset_call_number_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, creator, create_date, editor, edit_date, record, owning_lib, label, deleted, prefix, suffix, label_class, label_sortkey )
                SELECT  nextval('auditor.asset_call_number_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.creator, OLD.create_date, OLD.editor, OLD.edit_date, OLD.record, OLD.owning_lib, OLD.label, OLD.deleted, OLD.prefix, OLD.suffix, OLD.label_class, OLD.label_sortkey
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_asset_copy_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.asset_copy_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, circ_lib, creator, call_number, editor, create_date, edit_date, copy_number, status, location, loan_duration, fine_level, age_protect, circulate, deposit, ref, holdable, deposit_amount, price, barcode, circ_modifier, circ_as_type, dummy_title, dummy_author, alert_message, opac_visible, deleted, floating, dummy_isbn, status_changed_time, active_date, mint_condition, cost )
                SELECT  nextval('auditor.asset_copy_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.circ_lib, OLD.creator, OLD.call_number, OLD.editor, OLD.create_date, OLD.edit_date, OLD.copy_number, OLD.status, OLD.location, OLD.loan_duration, OLD.fine_level, OLD.age_protect, OLD.circulate, OLD.deposit, OLD.ref, OLD.holdable, OLD.deposit_amount, OLD.price, OLD.barcode, OLD.circ_modifier, OLD.circ_as_type, OLD.dummy_title, OLD.dummy_author, OLD.alert_message, OLD.opac_visible, OLD.deleted, OLD.floating, OLD.dummy_isbn, OLD.status_changed_time, OLD.active_date, OLD.mint_condition, OLD.cost
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_biblio_record_entry_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.biblio_record_entry_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, creator, editor, source, quality, create_date, edit_date, active, deleted, fingerprint, tcn_source, tcn_value, marc, last_xact_id, vis_attr_vector, owner, share_depth, merge_date, merged_to )
                SELECT  nextval('auditor.biblio_record_entry_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.creator, OLD.editor, OLD.source, OLD.quality, OLD.create_date, OLD.edit_date, OLD.active, OLD.deleted, OLD.fingerprint, OLD.tcn_source, OLD.tcn_value, OLD.marc, OLD.last_xact_id, OLD.vis_attr_vector, OLD.owner, OLD.share_depth, OLD.merge_date, OLD.merged_to
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.audit_serial_unit_func()

Returns: trigger

Language: PLPGSQL

        BEGIN
            INSERT INTO auditor.serial_unit_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, id, circ_lib, creator, call_number, editor, create_date, edit_date, copy_number, status, location, loan_duration, fine_level, age_protect, circulate, deposit, ref, holdable, deposit_amount, price, barcode, circ_modifier, circ_as_type, dummy_title, dummy_author, alert_message, opac_visible, deleted, floating, dummy_isbn, status_changed_time, active_date, mint_condition, cost, sort_key, detailed_contents, summary_contents )
                SELECT  nextval('auditor.serial_unit_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.id, OLD.circ_lib, OLD.creator, OLD.call_number, OLD.editor, OLD.create_date, OLD.edit_date, OLD.copy_number, OLD.status, OLD.location, OLD.loan_duration, OLD.fine_level, OLD.age_protect, OLD.circulate, OLD.deposit, OLD.ref, OLD.holdable, OLD.deposit_amount, OLD.price, OLD.barcode, OLD.circ_modifier, OLD.circ_as_type, OLD.dummy_title, OLD.dummy_author, OLD.alert_message, OLD.opac_visible, OLD.deleted, OLD.floating, OLD.dummy_isbn, OLD.status_changed_time, OLD.active_date, OLD.mint_condition, OLD.cost, OLD.sort_key, OLD.detailed_contents, OLD.summary_contents
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        

Function: auditor.clear_audit_info()

Returns: void

Language: PLPERLU

    delete($_SHARED{"eg_audit_user"});
    delete($_SHARED{"eg_audit_ws"});

Function: auditor.create_auditor(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    PERFORM auditor.create_auditor_seq(sch, tbl);
    PERFORM auditor.create_auditor_history(sch, tbl);
    PERFORM auditor.create_auditor_func(sch, tbl);
    PERFORM auditor.create_auditor_update_trigger(sch, tbl);
    PERFORM auditor.create_auditor_lifecycle(sch, tbl);
    RETURN TRUE;
END;

Function: auditor.create_auditor_func(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

DECLARE
    column_list TEXT[];
BEGIN
    SELECT INTO column_list array_agg(a.attname)
        FROM pg_catalog.pg_attribute a
            JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
        WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;

    EXECUTE $$
        CREATE OR REPLACE FUNCTION auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ()
        RETURNS TRIGGER AS $func$
        BEGIN
            INSERT INTO auditor.$$ || sch || $$_$$ || tbl || $$_history ( audit_id, audit_time, audit_action, audit_user, audit_ws, $$
            || array_to_string(column_list, ', ') || $$ )
                SELECT  nextval('auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq'),
                    now(),
                    SUBSTR(TG_OP,1,1),
                    eg_user,
                    eg_ws,
                    OLD.$$ || array_to_string(column_list, ', OLD.') || $$
                FROM auditor.get_audit_info();
            RETURN NULL;
        END;
        $func$ LANGUAGE 'plpgsql';
    $$;
    RETURN TRUE;
END;

Function: auditor.create_auditor_history(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    EXECUTE $$
        CREATE TABLE auditor.$$ || sch || $$_$$ || tbl || $$_history (
            audit_id	BIGINT				PRIMARY KEY,
            audit_time	TIMESTAMP WITH TIME ZONE	NOT NULL,
            audit_action	TEXT				NOT NULL,
            audit_user  INT,
            audit_ws    INT,
            LIKE $$ || sch || $$.$$ || tbl || $$
        );
    $$;
	RETURN TRUE;
END;

Function: auditor.create_auditor_lifecycle(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

DECLARE
    column_list TEXT[];
BEGIN
    SELECT INTO column_list array_agg(a.attname)
        FROM pg_catalog.pg_attribute a
            JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
        WHERE relkind = 'r' AND n.nspname = sch AND c.relname = tbl AND a.attnum > 0 AND NOT a.attisdropped;

    EXECUTE $$
        CREATE VIEW auditor.$$ || sch || $$_$$ || tbl || $$_lifecycle AS
            SELECT -1 AS audit_id,
                   now() AS audit_time,
                   '-' AS audit_action,
                   -1 AS audit_user,
                   -1 AS audit_ws,
                   $$ || array_to_string(column_list, ', ') || $$
              FROM $$ || sch || $$.$$ || tbl || $$
                UNION ALL
            SELECT audit_id, audit_time, audit_action, audit_user, audit_ws,
            $$ || array_to_string(column_list, ', ') || $$
              FROM auditor.$$ || sch || $$_$$ || tbl || $$_history;
    $$;
    RETURN TRUE;
END;

Function: auditor.create_auditor_seq(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    EXECUTE $$
        CREATE SEQUENCE auditor.$$ || sch || $$_$$ || tbl || $$_pkey_seq;
    $$;
	RETURN TRUE;
END;

Function: auditor.create_auditor_update_trigger(tbl text, sch text)

Returns: boolean

Language: PLPGSQL

BEGIN
    EXECUTE $$
        CREATE TRIGGER audit_$$ || sch || $$_$$ || tbl || $$_update_trigger
            AFTER UPDATE OR DELETE ON $$ || sch || $$.$$ || tbl || $$ FOR EACH ROW
            EXECUTE PROCEDURE auditor.audit_$$ || sch || $$_$$ || tbl || $$_func ();
    $$;
	RETURN TRUE;
END;

Function: auditor.fix_columns()

Returns: void

Language: PLPGSQL

DECLARE
    current_table TEXT = ''; -- Storage for post-loop main table name
    current_audit_table TEXT = ''; -- Storage for post-loop audit table name
    query TEXT = ''; -- Storage for built query
    cr RECORD; -- column record object
    alter_t BOOL = false; -- Has the alter table command been appended yet
    auditor_cores TEXT[] = ARRAY[]::TEXT[]; -- Core auditor function list (filled inside of loop)
    core_column TEXT; -- The current core column we are adding
BEGIN
    FOR cr IN
        WITH audit_tables AS ( -- Basic grab of auditor tables. Anything in the auditor namespace, basically. With oids.
            SELECT c.oid AS audit_oid, c.relname AS audit_table
            FROM pg_catalog.pg_class c
            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
            WHERE relkind='r' AND nspname = 'auditor'
        ),
        table_set AS ( -- Union of auditor tables with their "main" tables. With oids.
            SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
            FROM pg_catalog.pg_class c
            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
            JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
            WHERE relkind = 'r'
        ),
        column_lists AS ( -- All columns associated with the auditor or main table, grouped by the main table's oid.
            SELECT DISTINCT ON (main_oid, attname) t.main_oid, a.attname
            FROM table_set t
            JOIN pg_catalog.pg_attribute a ON a.attrelid IN (t.main_oid, t.audit_oid)
            WHERE attnum > 0 AND NOT attisdropped
        ),
        column_defs AS ( -- The motherload, every audit table and main table plus column names and defs.
            SELECT audit_table,
                   main_namespace,
                   main_table,
                   a.attname AS main_column, -- These two will be null for columns that have since been deleted, or for auditor core columns
                   pg_catalog.format_type(a.atttypid, a.atttypmod) AS main_column_def,
                   b.attname AS audit_column, -- These two will be null for columns that have since been added
                   pg_catalog.format_type(b.atttypid, b.atttypmod) AS audit_column_def
            FROM table_set t
            JOIN column_lists c USING (main_oid)
            LEFT JOIN pg_catalog.pg_attribute a ON a.attname = c.attname AND a.attrelid = t.main_oid AND a.attnum > 0 AND NOT a.attisdropped
            LEFT JOIN pg_catalog.pg_attribute b ON b.attname = c.attname AND b.attrelid = t.audit_oid AND b.attnum > 0 AND NOT b.attisdropped
        )
        -- Nice sorted output from the above
        SELECT * FROM column_defs WHERE main_column_def IS DISTINCT FROM audit_column_def ORDER BY main_namespace, main_table, main_column, audit_column
    LOOP
        IF current_table <> (cr.main_namespace || '.' || cr.main_table) THEN -- New table?
            FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Update missing core auditor columns
                IF NOT alter_t THEN -- Add ALTER TABLE if we haven't already
                    query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
                    alter_t:=TRUE;
                ELSE
                    query:=query || $$,$$;
                END IF;
                -- Bit of a sneaky bit here. Create audit_id as a bigserial so it gets automatic values and doesn't complain about nulls when becoming a PRIMARY KEY.
                query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
            END LOOP;
            IF alter_t THEN -- Open alter table = needs a semicolon
                query:=query || $$; $$;
                alter_t:=FALSE;
                IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
                    -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
                    -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
                    EXECUTE query;
                    EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
                        $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
                    EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
                        $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
                    query:='';
                END IF;
            END IF;
            -- New table means we reset the list of needed auditor core columns
            auditor_cores = ARRAY['audit_id bigint', 'audit_time timestamp with time zone', 'audit_action text', 'audit_user integer', 'audit_ws integer'];
            -- And store some values for use later, because we can't rely on cr in all places.
            current_table:=cr.main_namespace || '.' || cr.main_table;
            current_audit_table:=cr.audit_table;
        END IF;
        IF cr.main_column IS NULL AND cr.audit_column LIKE 'audit_%' THEN -- Core auditor column?
            -- Remove core from list of cores
            SELECT INTO auditor_cores array_agg(core) FROM unnest(auditor_cores) AS core WHERE core != (cr.audit_column || ' ' || cr.audit_column_def);
        ELSIF cr.main_column IS NULL THEN -- Main column doesn't exist, and it isn't an auditor column. Needs dropping from the auditor.
            IF NOT alter_t THEN
                query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
                alter_t:=TRUE;
            ELSE
                query:=query || $$,$$;
            END IF;
            query:=query || $$ DROP COLUMN $$ || cr.audit_column;
        ELSIF cr.audit_column IS NULL AND cr.main_column IS NOT NULL THEN -- New column auditor doesn't have. Add it.
            IF NOT alter_t THEN
                query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
                alter_t:=TRUE;
            ELSE
                query:=query || $$,$$;
            END IF;
            query:=query || $$ ADD COLUMN $$ || cr.main_column || $$ $$ || cr.main_column_def;
        ELSIF cr.main_column IS NOT NULL AND cr.audit_column IS NOT NULL THEN -- Both sides have this column, but types differ. Fix that.
            IF NOT alter_t THEN
                query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
                alter_t:=TRUE;
            ELSE
                query:=query || $$,$$;
            END IF;
            query:=query || $$ ALTER COLUMN $$ || cr.audit_column || $$ TYPE $$ || cr.main_column_def;
        END IF;
    END LOOP;
    FOR core_column IN SELECT DISTINCT unnest(auditor_cores) LOOP -- Repeat this outside of the loop to catch the last table
        IF NOT alter_t THEN
            query:=query || $$ALTER TABLE auditor.$$ || current_audit_table;
            alter_t:=TRUE;
        ELSE
            query:=query || $$,$$;
        END IF;
        -- Bit of a sneaky bit here. Create audit_id as a bigserial so it gets automatic values and doesn't complain about nulls when becoming a PRIMARY KEY.
        query:=query || $$ ADD COLUMN $$ || CASE WHEN core_column = 'audit_id bigint' THEN $$audit_id bigserial PRIMARY KEY$$ ELSE core_column END;
    END LOOP;
    IF alter_t THEN -- Open alter table = needs a semicolon
        query:=query || $$;$$;
        IF 'audit_id bigint' = ANY(auditor_cores) THEN -- We added a primary key...
            -- Fun! Drop the default on audit_id, drop the auto-created sequence, create a new one, and set the current value
            -- For added fun, we have to execute in chunks due to the parser checking setval/currval arguments at parse time.
            EXECUTE query;
            EXECUTE $$ALTER TABLE auditor.$$ || current_audit_table || $$ ALTER COLUMN audit_id DROP DEFAULT; $$ ||
                $$CREATE SEQUENCE auditor.$$ || current_audit_table || $$_pkey_seq;$$;
            EXECUTE $$SELECT setval('auditor.$$ || current_audit_table || $$_pkey_seq', currval('auditor.$$ || current_audit_table || $$_audit_id_seq')); $$ ||
                $$DROP SEQUENCE auditor.$$ || current_audit_table || $$_audit_id_seq;$$;
            query:='';
        END IF;
    END IF;
    EXECUTE query;
END;

Function: auditor.get_audit_info()

Returns: SET OF record

Language: PLPERLU

    return [{eg_user => $_SHARED{"eg_audit_user"}, eg_ws => $_SHARED{"eg_audit_ws"}}];

Function: auditor.set_audit_info(integer, integer)

Returns: void

Language: PLPERLU

    $_SHARED{"eg_audit_user"} = $_[0];
    $_SHARED{"eg_audit_ws"} = $_[1];

Function: auditor.update_auditors()

Returns: boolean

Language: PLPGSQL

DECLARE
    auditor_name TEXT;
    table_schema TEXT;
    table_name TEXT;
BEGIN
    -- Drop Lifecycle view(s) before potential column changes
    FOR auditor_name IN
        SELECT c.relname
            FROM pg_catalog.pg_class c
                JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
            WHERE relkind = 'v' AND n.nspname = 'auditor' LOOP
        EXECUTE $$ DROP VIEW auditor.$$ || auditor_name || $$;$$;
    END LOOP;
    -- Fix all column discrepencies
    PERFORM auditor.fix_columns();
    -- Re-create trigger functions and lifecycle views
    FOR table_schema, table_name IN
        WITH audit_tables AS (
            SELECT c.oid AS audit_oid, c.relname AS audit_table
            FROM pg_catalog.pg_class c
            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
            WHERE relkind='r' AND nspname = 'auditor'
        ),
        table_set AS (
            SELECT a.audit_oid, a.audit_table, c.oid AS main_oid, n.nspname as main_namespace, c.relname as main_table
            FROM pg_catalog.pg_class c
            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
            JOIN audit_tables a ON a.audit_table = n.nspname || '_' || c.relname || '_history'
            WHERE relkind = 'r'
        )
        SELECT main_namespace, main_table FROM table_set LOOP
        
        PERFORM auditor.create_auditor_func(table_schema, table_name);
        PERFORM auditor.create_auditor_lifecycle(table_schema, table_name);
    END LOOP;
    RETURN TRUE;
END;

Schema authority


Table: authority.authority_linking

authority.authority_linking Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
authority.record_entry.id source bigint NOT NULL
authority.record_entry.id target bigint NOT NULL
authority.control_set_authority_field.id field integer NOT NULL

Index - Schema authority


Table: authority.bib_linking

authority.bib_linking Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id bib bigint NOT NULL
authority.record_entry.id authority bigint NOT NULL
authority_bl_bib_idx bib

Index - Schema authority


Table: authority.browse_axis

authority.browse_axis Structure
F-Key Name Type Description
code text PRIMARY KEY
name text UNIQUE NOT NULL
config.record_attr_definition.name sorter text
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema authority


Table: authority.browse_axis_authority_field_map

authority.browse_axis_authority_field_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
authority.browse_axis.code axis text NOT NULL
authority.control_set_authority_field.id field integer NOT NULL

Index - Schema authority


Table: authority.control_set

authority.control_set Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema authority


View: authority.control_set_auth_field_metabib_field_map_blind_main

metabib fields for main entry auth fields that can't be linked to other records

authority.control_set_auth_field_metabib_field_map_blind_main Structure
F-Key Name Type Description
authority_field integer
metabib_field integer
SELECT r.authority_field
,
    r.metabib_field
   
FROM (authority.control_set_auth_field_metabib_field_map_main r
     
  JOIN authority.control_set_authority_field a 
    ON (
           (r.authority_field = a.id)
     )
)
  
WHERE (a.linking_subfield IS NULL);

Index - Schema authority


View: authority.control_set_auth_field_metabib_field_map_blind_refs

metabib fields for all auth fields that can't be linked to other records

authority.control_set_auth_field_metabib_field_map_blind_refs Structure
F-Key Name Type Description
authority_field integer
metabib_field integer
SELECT r.authority_field
,
    r.metabib_field
   
FROM (authority.control_set_auth_field_metabib_field_map_refs r
     
  JOIN authority.control_set_authority_field a 
    ON (
           (r.authority_field = a.id)
     )
)
  
WHERE (a.linking_subfield IS NULL);

Index - Schema authority


View: authority.control_set_auth_field_metabib_field_map_blind_refs_only

metabib fields for NON-main entry auth fields that can't be linked to other records

authority.control_set_auth_field_metabib_field_map_blind_refs_only Structure
F-Key Name Type Description
authority_field integer
metabib_field integer
SELECT r.authority_field
,
    r.metabib_field
   
FROM (authority.control_set_auth_field_metabib_field_map_refs_only r
     
  JOIN authority.control_set_authority_field a 
    ON (
           (r.authority_field = a.id)
     )
)
  
WHERE (a.linking_subfield IS NULL);

Index - Schema authority


View: authority.control_set_auth_field_metabib_field_map_main

metabib fields for main entry auth fields

authority.control_set_auth_field_metabib_field_map_main Structure
F-Key Name Type Description
authority_field integer
metabib_field integer
SELECT DISTINCT b.authority_field
,
    m.metabib_field
   
FROM (authority.control_set_bib_field_metabib_field_map m
     
  JOIN authority.control_set_bib_field b 
    ON (
           (b.id = m.bib_field)
     )
);

Index - Schema authority


View: authority.control_set_auth_field_metabib_field_map_refs

metabib fields for all auth fields

authority.control_set_auth_field_metabib_field_map_refs Structure
F-Key Name Type Description
authority_field integer
metabib_field integer
SELECT control_set_auth_field_metabib_field_map_main.authority_field
,
    control_set_auth_field_metabib_field_map_main.metabib_field
   
FROM authority.control_set_auth_field_metabib_field_map_main

UNION
 
SELECT control_set_auth_field_metabib_field_map_refs_only.authority_field
,
    control_set_auth_field_metabib_field_map_refs_only.metabib_field
   
FROM authority.control_set_auth_field_metabib_field_map_refs_only;

Index - Schema authority


View: authority.control_set_auth_field_metabib_field_map_refs_only

metabib fields for NON-main entry auth fields

authority.control_set_auth_field_metabib_field_map_refs_only Structure
F-Key Name Type Description
authority_field integer
metabib_field integer
SELECT DISTINCT a.id AS authority_field
,
    m.metabib_field
   
FROM (
     (
           (
                 (authority.control_set_authority_field a
     
                    JOIN authority.control_set_authority_field ame 
                      ON (
                             (a.main_entry = ame.id)
                       )
                 )
     
              JOIN authority.control_set_bib_field b 
                ON (
                       (b.authority_field = ame.id)
                 )
           )
     
        JOIN authority.control_set_bib_field_metabib_field_map mf 
          ON (
                 (mf.bib_field = b.id)
           )
     )
     
  JOIN authority.control_set_auth_field_metabib_field_map_main m 
    ON (
           (ame.id = m.authority_field)
     )
);

Index - Schema authority


Table: authority.control_set_authority_field

authority.control_set_authority_field Structure
F-Key Name Type Description
id serial PRIMARY KEY
authority.control_set_authority_field.id main_entry integer
authority.control_set.id control_set integer NOT NULL
tag character(3) NOT NULL
nfi character(1)
sf_list text NOT NULL
display_sf_list text NOT NULL
name text NOT NULL
description text
joiner text
linking_subfield character(1)
authority.heading_field.id heading_field integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema authority


Table: authority.control_set_bib_field

authority.control_set_bib_field Structure
F-Key Name Type Description
id serial PRIMARY KEY
authority.control_set_authority_field.id authority_field integer NOT NULL
tag character(3) NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema authority


Table: authority.control_set_bib_field_metabib_field_map

authority.control_set_bib_field_metabib_field_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
authority.control_set_bib_field.id bib_field integer UNIQUE#1 NOT NULL
config.metabib_field.id metabib_field integer UNIQUE#1 NOT NULL

Index - Schema authority


Table: authority.full_rec

authority.full_rec Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
record bigint NOT NULL
tag character(3) NOT NULL
ind1 text
ind2 text
subfield text
value text NOT NULL
index_vector tsvector NOT NULL
authority_full_rec_index_vector_idx index_vector authority_full_rec_record_idx record authority_full_rec_subfield_a_idx value) WHERE (subfield = 'a'::text authority_full_rec_tag_part_idx "substring"((tag)::text, 2) authority_full_rec_tag_subfield_idx tag, subfield authority_full_rec_value_index "substring"(value, 1, 1024) authority_full_rec_value_tpo_index "substring"(value, 1, 1024) text_pattern_ops

Index - Schema authority


Table: authority.heading_field

authority.heading_field Structure
F-Key Name Type Description
id serial PRIMARY KEY
heading_type authority.heading_type NOT NULL
heading_purpose authority.heading_purpose NOT NULL
label text NOT NULL
config.xml_transform.name format text NOT NULL DEFAULT 'mads21'::text
heading_xpath text NOT NULL
component_xpath text NOT NULL
type_xpath text
thesaurus_xpath text
thesaurus_override_xpath text
joiner text

Tables referencing this one via Foreign Key Constraints:

Index - Schema authority


Table: authority.heading_field_norm_map

authority.heading_field_norm_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
authority.heading_field.id field integer NOT NULL
config.index_normalizer.id norm integer NOT NULL
params text
pos integer NOT NULL

Index - Schema authority


Table: authority.rec_descriptor

authority.rec_descriptor Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
record bigint
record_status text
encoding_level text
thesaurus text
authority_rec_descriptor_record_idx record

Index - Schema authority


Table: authority.record_entry

authority.record_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
create_date timestamp with time zone NOT NULL DEFAULT now()
edit_date timestamp with time zone NOT NULL DEFAULT now()
creator integer NOT NULL DEFAULT 1
editor integer NOT NULL DEFAULT 1
active boolean NOT NULL DEFAULT true
deleted boolean NOT NULL DEFAULT false
source integer
authority.control_set.id control_set integer
marc text NOT NULL
last_xact_id text NOT NULL
owner integer
heading text
simple_heading text

Tables referencing this one via Foreign Key Constraints:

authority_record_deleted_idx deleted) WHERE ((deleted IS FALSE) OR (deleted = false) authority_record_entry_create_date_idx create_date authority_record_entry_creator_idx creator authority_record_entry_edit_date_idx edit_date authority_record_entry_editor_idx editor by_heading simple_heading) WHERE ((deleted IS FALSE) OR (deleted = false) by_heading_and_thesaurus heading) WHERE ((deleted IS FALSE) OR (deleted = false)

Index - Schema authority


Table: authority.record_note

authority.record_note Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
authority.record_entry.id record bigint NOT NULL
value text NOT NULL
creator integer NOT NULL DEFAULT 1
editor integer NOT NULL DEFAULT 1
create_date timestamp with time zone NOT NULL DEFAULT now()
edit_date timestamp with time zone NOT NULL DEFAULT now()
authority_record_note_creator_idx creator authority_record_note_editor_idx editor authority_record_note_record_idx record

Index - Schema authority


Table: authority.simple_heading

authority.simple_heading Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
authority.record_entry.id record bigint NOT NULL
authority.control_set_authority_field.id atag integer NOT NULL
value text NOT NULL
sort_value text NOT NULL
index_vector tsvector NOT NULL
thesaurus text

Tables referencing this one via Foreign Key Constraints:

authority_simple_heading_index_vector_idx index_vector authority_simple_heading_record_idx record authority_simple_heading_sort_value_idx sort_value authority_simple_heading_thesaurus_idx thesaurus authority_simple_heading_value_idx value

Index - Schema authority


Table: authority.thesaurus

authority.thesaurus Structure
F-Key Name Type Description
code text PRIMARY KEY
authority.control_set.id control_set integer
name text UNIQUE NOT NULL
description text
short_code text
uri text

Index - Schema authority


View: authority.tracing_links

authority.tracing_links Structure
F-Key Name Type Description
record bigint
main_id bigint
main_tag character(3)
main_value text
relationship text
use_restriction text
deprecation text
display_restriction text
link_id bigint
link_tag character(3)
link_value text
normalized_main_value text
SELECT main.record
,
    main.id AS main_id
,
    main.tag AS main_tag
,
    oils_xpath_string
(
     (
           ('//*[@tag="'::text || 
                 (main.tag)::text
           ) || 
'"]/*[local-name()="subfield"]'::text
)
, are.marc
)      AS main_value
,     
    substr
(     link.value
, 1
, 1
)      AS relationship
,     
    substr
(     link.value
, 2
, 1
)      AS use_restriction
,     
    substr
(     link.value
, 3
, 1
)      AS deprecation
,     
    substr
(     link.value
, 4
, 1
)      AS display_restriction
,     
    link.id AS link_id
,     
    link.tag AS link_tag
,     
    oils_xpath_string
(     
(
     ('//*[@tag="'::text || 
           (link.tag)::text
     ) || 
'"]/*[local-name()="subfield"]'::text
)     
,      are.marc
)            AS link_value
,           
    are.heading AS normalized_main_value
   
FROM         (
(     
(
     (authority.full_rec main
     
        JOIN authority.record_entry are 
          ON (
                 (main.record = are.id)
           )
     )
     
  JOIN authority.control_set_authority_field main_entry 
    ON (
           (
                 (main_entry.tag = main.tag)
               AND (main_entry.main_entry IS NULL)
               AND (main.subfield = 'a'::text)
           )
     )
)
     
JOIN authority.control_set_authority_field sub_entry 
ON (
     (main_entry.id = sub_entry.main_entry)
)
)     
     
JOIN   authority.full_rec link 
ON     (
(
     (link.record = main.record)
   AND (link.tag = sub_entry.tag)
   AND (link.subfield = 'w'::text)
)
)     
)           ;

Index - Schema authority


Function: authority.atag_authority_tags(atag text)

Returns: integer[]

Language: SQL

    SELECT ARRAY_AGG(id) FROM authority.control_set_authority_field WHERE tag = $1

Function: authority.atag_authority_tags_refs(atag text)

Returns: integer[]

Language: SQL

    SELECT ARRAY_AGG(y) from (
        SELECT  unnest(ARRAY_CAT(
                    ARRAY[a.id],
                    (SELECT ARRAY_AGG(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.id)
                )) y
      FROM  authority.control_set_authority_field a
      WHERE a.tag = $1) x

Function: authority.atag_browse_center(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags($1), $2, $3, $4, $5)

Function: authority.atag_browse_center_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.atag_browse_top(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags($1), $2, $3, $4, $5)

Function: authority.atag_browse_top_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.atag_search_heading(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags($1), $2, $3, $4, $5)

Function: authority.atag_search_heading_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.atag_search_rank(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags($1), $2, $3, $4, $5)

Function: authority.atag_search_rank_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.axis_authority_tags(a text)

Returns: integer[]

Language: SQL

    SELECT ARRAY_AGG(field) FROM authority.browse_axis_authority_field_map WHERE axis = $1;

Function: authority.axis_authority_tags_refs(a text)

Returns: integer[]

Language: SQL

    SELECT ARRAY_AGG(y) from (
       SELECT  unnest(ARRAY_CAT(
                 ARRAY[a.field],
                 (SELECT ARRAY_AGG(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.field)
             )) y
       FROM  authority.browse_axis_authority_field_map a
       WHERE axis = $1) x

Function: authority.axis_browse_center(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags($1), $2, $3, $4, $5)

Function: authority.axis_browse_center_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.axis_browse_top(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags($1), $2, $3, $4, $5)

Function: authority.axis_browse_top_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.axis_search_heading(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags($1), $2, $3, $4, $5)

Function: authority.axis_search_heading_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.axis_search_rank(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags($1), $2, $3, $4, $5)

Function: authority.axis_search_rank_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.btag_authority_tags(btag text)

Returns: integer[]

Language: SQL

    SELECT ARRAY_AGG(authority_field) FROM authority.control_set_bib_field WHERE tag = $1

Function: authority.btag_authority_tags_refs(btag text)

Returns: integer[]

Language: SQL

    SELECT ARRAY_AGG(y) from (
        SELECT  unnest(ARRAY_CAT(
                    ARRAY[a.authority_field],
                    (SELECT ARRAY_AGG(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.authority_field)
                )) y
      FROM  authority.control_set_bib_field a
      WHERE a.tag = $1) x

Function: authority.btag_browse_center(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags($1), $2, $3, $4, $5)

Function: authority.btag_browse_center_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.btag_browse_top(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags($1), $2, $3, $4, $5)

Function: authority.btag_browse_top_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.btag_search_heading(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags($1), $2, $3, $4, $5)

Function: authority.btag_search_heading_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.btag_search_rank(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags($1), $2, $3, $4, $5)

Function: authority.btag_search_rank_refs(thesauruses text, pagesize text, page integer, q integer, a text)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags_refs($1), $2, $3, $4, $5)

Function: authority.calculate_authority_linking(rec_marc_xml bigint, rec_control_set integer, rec_id xml)

Returns: SET OF authority_linking

Language: PLPGSQL

DECLARE
    acsaf       authority.control_set_authority_field%ROWTYPE;
    link        TEXT;
    aal         authority.authority_linking%ROWTYPE;
BEGIN
    IF rec_control_set IS NULL THEN
        -- No control_set on record?  Guess at one
        SELECT control_set INTO rec_control_set
            FROM authority.control_set_authority_field
            WHERE tag IN (
                SELECT UNNEST(
                    XPATH('//*[starts-with(@tag,"1")]/@tag',rec_marc_xml)::TEXT[]
                )
            ) LIMIT 1;

        IF NOT FOUND THEN
            RAISE WARNING 'Could not even guess at control set for authority record %', rec_id;
            RETURN;
        END IF;
    END IF;

    aal.source := rec_id;

    FOR acsaf IN
        SELECT * FROM authority.control_set_authority_field
        WHERE control_set = rec_control_set
            AND linking_subfield IS NOT NULL
            AND main_entry IS NOT NULL
    LOOP
        -- Loop over the trailing-number contents of all linking subfields
        FOR link IN
            SELECT  SUBSTRING( x::TEXT, '\d+$' )
              FROM  UNNEST(
                        XPATH(
                            '//*[@tag="'
                                || acsaf.tag
                                || '"]/*[@code="'
                                || acsaf.linking_subfield
                                || '"]/text()',
                            rec_marc_xml
                        )
                    ) x
        LOOP

            -- Ignore links that are null, malformed, circular, or point to
            -- non-existent authority records.
            IF link IS NOT NULL AND link::BIGINT <> rec_id THEN
                PERFORM * FROM authority.record_entry WHERE id = link::BIGINT;
                IF FOUND THEN
                    aal.target := link::BIGINT;
                    aal.field := acsaf.id;
                    RETURN NEXT aal;
                END IF;
            END IF;
        END LOOP;
    END LOOP;
END;

Function: authority.extract_headings(restrict bigint, rid integer[])

Returns: SET OF heading

Language: PLPGSQL

DECLARE
    auth        authority.record_entry%ROWTYPE;
    output_row  authority.heading;
BEGIN
    -- Get the record
    SELECT INTO auth * FROM authority.record_entry WHERE id = rid;

    RETURN QUERY SELECT * FROM authority.extract_headings(auth.marc, restrict);
END;

Function: authority.extract_headings(restrict text, marc integer[])

Returns: SET OF heading

Language: PLPGSQL

DECLARE
    idx         authority.heading_field%ROWTYPE;
    xfrm        config.xml_transform%ROWTYPE;
    prev_xfrm   TEXT;
    transformed_xml TEXT;
    heading_node    TEXT;
    heading_node_list   TEXT[];
    component_node    TEXT;
    component_node_list   TEXT[];
    raw_text    TEXT;
    normalized_text    TEXT;
    normalizer  RECORD;
    curr_text   TEXT;
    joiner      TEXT;
    type_value  TEXT;
    base_thesaurus TEXT := NULL;
    output_row  authority.heading;
BEGIN

    -- Loop over the indexing entries
    FOR idx IN SELECT * FROM authority.heading_field WHERE restrict IS NULL OR id = ANY (restrict) ORDER BY format LOOP

        output_row.field   := idx.id;
        output_row.type    := idx.heading_type;
        output_row.purpose := idx.heading_purpose;

        joiner := COALESCE(idx.joiner, ' ');

        SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;

        -- See if we can skip the XSLT ... it's expensive
        IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
            -- Can't skip the transform
            IF xfrm.xslt <> '---' THEN
                transformed_xml := oils_xslt_process(marc, xfrm.xslt);
            ELSE
                transformed_xml := marc;
            END IF;

            prev_xfrm := xfrm.name;
        END IF;

        IF idx.thesaurus_xpath IS NOT NULL THEN
            base_thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), '');
        END IF;

        heading_node_list := oils_xpath( idx.heading_xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );

        FOR heading_node IN SELECT x FROM unnest(heading_node_list) AS x LOOP

            CONTINUE WHEN heading_node !~ E'^\\s*<';

            output_row.variant_type := NULL;
            output_row.related_type := NULL;
            output_row.thesaurus    := NULL;
            output_row.heading      := NULL;

            IF idx.heading_purpose = 'variant' AND idx.type_xpath IS NOT NULL THEN
                type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), '');
                BEGIN
                    output_row.variant_type := type_value;
                EXCEPTION WHEN invalid_text_representation THEN
                    RAISE NOTICE 'Do not recognize variant heading type %', type_value;
                END;
            END IF;
            IF idx.heading_purpose = 'related' AND idx.type_xpath IS NOT NULL THEN
                type_value := ARRAY_TO_STRING(oils_xpath(idx.type_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), '');
                BEGIN
                    output_row.related_type := type_value;
                EXCEPTION WHEN invalid_text_representation THEN
                    RAISE NOTICE 'Do not recognize related heading type %', type_value;
                END;
            END IF;
 
            IF idx.thesaurus_override_xpath IS NOT NULL THEN
                output_row.thesaurus := ARRAY_TO_STRING(oils_xpath(idx.thesaurus_override_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]), '');
            END IF;
            IF output_row.thesaurus IS NULL THEN
                output_row.thesaurus := base_thesaurus;
            END IF;

            raw_text := NULL;

            -- now iterate over components of heading
            component_node_list := oils_xpath( idx.component_xpath, heading_node, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
            FOR component_node IN SELECT x FROM unnest(component_node_list) AS x LOOP
            -- XXX much of this should be moved into oils_xpath_string...
                curr_text := ARRAY_TO_STRING(array_remove(array_remove(
                    oils_xpath( '//text()', -- get the content of all the nodes within the main selected node
                        REGEXP_REPLACE( component_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space
                    ), ' '), ''),  -- throw away morally empty (bankrupt?) strings
                    joiner
                );

                CONTINUE WHEN curr_text IS NULL OR curr_text = '';

                IF raw_text IS NOT NULL THEN
                    raw_text := raw_text || joiner;
                END IF;

                raw_text := COALESCE(raw_text,'') || curr_text;
            END LOOP;

            IF raw_text IS NOT NULL THEN
                output_row.heading := raw_text;
                normalized_text := raw_text;

                FOR normalizer IN
                    SELECT  n.func AS func,
                            n.param_count AS param_count,
                            m.params AS params
                    FROM  config.index_normalizer n
                            JOIN authority.heading_field_norm_map m ON (m.norm = n.id)
                    WHERE m.field = idx.id
                    ORDER BY m.pos LOOP
            
                        EXECUTE 'SELECT ' || normalizer.func || '(' ||
                            quote_literal( normalized_text ) ||
                            CASE
                                WHEN normalizer.param_count > 0
                                    THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
                                    ELSE ''
                                END ||
                            ')' INTO normalized_text;
            
                END LOOP;
            
                output_row.normalized_heading := normalized_text;
            
                RETURN NEXT output_row;
            END IF;
        END LOOP;

    END LOOP;
END;

Function: authority.extract_thesaurus(marcxml text)

Returns: text

Language: PLPGSQL

DECLARE
    thes_code TEXT;
BEGIN
    thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
    IF thes_code IS NULL THEN
        thes_code := '|';
    ELSIF thes_code = 'z' THEN
        thes_code := COALESCE( oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml), 'z' );
    ELSE
        SELECT code INTO thes_code FROM authority.thesaurus WHERE short_code = thes_code;
        IF NOT FOUND THEN
            thes_code := '|'; -- default
        END IF;
    END IF;
    RETURN thes_code;
END;

Function: authority.flatten_marc(rid bigint)

Returns: SET OF full_rec

Language: PLPGSQL

DECLARE
	auth	authority.record_entry%ROWTYPE;
	output	authority.full_rec%ROWTYPE;
	field	RECORD;
BEGIN
	SELECT INTO auth * FROM authority.record_entry WHERE id = rid;

	FOR field IN SELECT * FROM vandelay.flatten_marc( auth.marc ) LOOP
		output.record := rid;
		output.ind1 := field.ind1;
		output.ind2 := field.ind2;
		output.tag := field.tag;
		output.subfield := field.subfield;
		output.value := field.value;

		RETURN NEXT output;
	END LOOP;
END;

Function: authority.generate_overlay_template(bigint)

Returns: text

Language: SQL

    SELECT authority.generate_overlay_template( marc ) FROM authority.record_entry WHERE id = $1;

Function: authority.generate_overlay_template(source_xml text)

Returns: text

Language: PLPGSQL

DECLARE
    cset                INT;
    main_entry          authority.control_set_authority_field%ROWTYPE;
    bib_field           authority.control_set_bib_field%ROWTYPE;
    auth_id             INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', source_xml)::INT;
    tmp_data            XML;
    replace_data        XML[] DEFAULT '{}'::XML[];
    replace_rules       TEXT[] DEFAULT '{}'::TEXT[];
    auth_field          XML[];
    auth_i1             TEXT;
    auth_i2             TEXT;
BEGIN
    IF auth_id IS NULL THEN
        RETURN NULL;
    END IF;

    -- Default to the LoC controll set
    SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;

    -- if none, make a best guess
    IF cset IS NULL THEN
        SELECT  control_set INTO cset
          FROM  authority.control_set_authority_field
          WHERE tag IN (
                    SELECT  UNNEST(XPATH('//*[local-name()="datafield" and starts-with(@tag,"1")]/@tag',marc::XML)::TEXT[])
                      FROM  authority.record_entry
                      WHERE id = auth_id
                )
          LIMIT 1;
    END IF;

    -- if STILL none, no-op change
    IF cset IS NULL THEN
        RETURN XMLELEMENT(
            name record,
            XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
            XMLELEMENT( name leader, '00881nam a2200193   4500'),
            XMLELEMENT(
                name datafield,
                XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
                XMLELEMENT(
                    name subfield,
                    XMLATTRIBUTES('d' AS code),
                    '901c'
                )
            )
        )::TEXT;
    END IF;

    FOR main_entry IN SELECT * FROM authority.control_set_authority_field acsaf WHERE acsaf.control_set = cset AND acsaf.main_entry IS NULL LOOP
        auth_field := XPATH('//*[local-name()="datafield" and @tag="'||main_entry.tag||'"][1]',source_xml::XML);
        auth_i1 := (XPATH('//*[local-name()="datafield"]/@ind1',auth_field[1]))[1];
        auth_i2 := (XPATH('//*[local-name()="datafield"]/@ind2',auth_field[1]))[1];
        IF ARRAY_LENGTH(auth_field,1) > 0 THEN
            FOR bib_field IN SELECT * FROM authority.control_set_bib_field WHERE authority_field = main_entry.id LOOP
                SELECT XMLELEMENT( -- XMLAGG avoids magical <element> creation, but requires unnest subquery
                    name datafield,
                    XMLATTRIBUTES(bib_field.tag AS tag, auth_i1 AS ind1, auth_i2 AS ind2),
                    XMLAGG(UNNEST)
                ) INTO tmp_data FROM UNNEST(XPATH('//*[local-name()="subfield"]', auth_field[1]));
                replace_data := replace_data || tmp_data;
                replace_rules := replace_rules || ( bib_field.tag || main_entry.sf_list || E'[0~\\)' || auth_id || '$]' );
                tmp_data = NULL;
            END LOOP;
            EXIT;
        END IF;
    END LOOP;

    SELECT XMLAGG(UNNEST) INTO tmp_data FROM UNNEST(replace_data);

    RETURN XMLELEMENT(
        name record,
        XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
        XMLELEMENT( name leader, '00881nam a2200193   4500'),
        tmp_data,
        XMLELEMENT(
            name datafield,
            XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
            XMLELEMENT(
                name subfield,
                XMLATTRIBUTES('r' AS code),
                ARRAY_TO_STRING(replace_rules,',')
            )
        )
    )::TEXT;
END;

Function: authority.indexing_ingest_or_delete()

Returns: trigger

Language: PLPGSQL

DECLARE
    ashs    authority.simple_heading%ROWTYPE;
    mbe_row metabib.browse_entry%ROWTYPE;
    mbe_id  BIGINT;
    ash_id  BIGINT;
BEGIN

    IF NEW.deleted IS TRUE THEN -- If this authority is deleted
        DELETE FROM authority.bib_linking WHERE authority = NEW.id; -- Avoid updating fields in bibs that are no longer visible
        DELETE FROM authority.full_rec WHERE record = NEW.id; -- Avoid validating fields against deleted authority records
        DELETE FROM authority.simple_heading WHERE record = NEW.id;
          -- Should remove matching $0 from controlled fields at the same time?

        -- XXX What do we about the actual linking subfields present in
        -- authority records that target this one when this happens?
        DELETE FROM authority.authority_linking
            WHERE source = NEW.id OR target = NEW.id;

        RETURN NEW; -- and we're done
    END IF;

    IF TG_OP = 'UPDATE' THEN -- re-ingest?
        PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;

        IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
            RETURN NEW;
        END IF;

        -- Unless there's a setting stopping us, propagate these updates to any linked bib records when the heading changes
        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_auto_update' AND enabled;

        IF NOT FOUND AND NEW.heading <> OLD.heading THEN
            PERFORM authority.propagate_changes(NEW.id);
        END IF;
	
        DELETE FROM authority.simple_heading WHERE record = NEW.id;
        DELETE FROM authority.authority_linking WHERE source = NEW.id;
    END IF;

    INSERT INTO authority.authority_linking (source, target, field)
        SELECT source, target, field FROM authority.calculate_authority_linking(
            NEW.id, NEW.control_set, NEW.marc::XML
        );

    FOR ashs IN SELECT * FROM authority.simple_heading_set(NEW.marc) LOOP

        INSERT INTO authority.simple_heading (record,atag,value,sort_value,thesaurus)
            VALUES (ashs.record, ashs.atag, ashs.value, ashs.sort_value, ashs.thesaurus);
            ash_id := CURRVAL('authority.simple_heading_id_seq'::REGCLASS);

        SELECT INTO mbe_row * FROM metabib.browse_entry
            WHERE value = ashs.value AND sort_value = ashs.sort_value;

        IF FOUND THEN
            mbe_id := mbe_row.id;
        ELSE
            INSERT INTO metabib.browse_entry
                ( value, sort_value ) VALUES
                ( ashs.value, ashs.sort_value );

            mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
        END IF;

        INSERT INTO metabib.browse_entry_simple_heading_map (entry,simple_heading) VALUES (mbe_id,ash_id);

    END LOOP;

    -- Flatten and insert the afr data
    PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_full_rec' AND enabled;
    IF NOT FOUND THEN
        PERFORM authority.reingest_authority_full_rec(NEW.id);
        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_rec_descriptor' AND enabled;
        IF NOT FOUND THEN
            PERFORM authority.reingest_authority_rec_descriptor(NEW.id);
        END IF;
    END IF;

    RETURN NEW;
END;

Function: authority.map_thesaurus_to_control_set()

Returns: trigger

Language: PLPGSQL

BEGIN
    IF NEW.control_set IS NULL THEN
        SELECT  control_set INTO NEW.control_set
          FROM  authority.thesaurus
          WHERE authority.extract_thesaurus(NEW.marc) = code;
    END IF;

    RETURN NEW;
END;

Function: authority.merge_records(source_record bigint, target_record bigint)

Returns: integer

Language: PLPGSQL

DECLARE
    moved_objects INT := 0;
    bib_id        INT := 0;
    bib_rec       biblio.record_entry%ROWTYPE;
    auth_link     authority.bib_linking%ROWTYPE;
    ingest_same   boolean;
BEGIN

    -- Defining our terms:
    -- "target record" = the record that will survive the merge
    -- "source record" = the record that is sacrifing its existence and being
    --   replaced by the target record

    -- 1. Update all bib records with the ID from target_record in their $0
    FOR bib_rec IN
            SELECT  bre.*
              FROM  biblio.record_entry bre 
                    JOIN authority.bib_linking abl ON abl.bib = bre.id
              WHERE abl.authority = source_record
        LOOP

        UPDATE  biblio.record_entry
          SET   marc = REGEXP_REPLACE(
                    marc,
                    E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
                    E'\\1' || target_record || '<',
                    'g'
                )
          WHERE id = bib_rec.id;

          moved_objects := moved_objects + 1;
    END LOOP;

    -- 2. Grab the current value of reingest on same MARC flag
    SELECT  enabled INTO ingest_same
      FROM  config.internal_flag
      WHERE name = 'ingest.reingest.force_on_same_marc'
    ;

    -- 3. Temporarily set reingest on same to TRUE
    UPDATE  config.internal_flag
      SET   enabled = TRUE
      WHERE name = 'ingest.reingest.force_on_same_marc'
    ;

    -- 4. Make a harmless update to target_record to trigger auto-update
    --    in linked bibliographic records
    UPDATE  authority.record_entry
      SET   deleted = FALSE
      WHERE id = target_record;

    -- 5. "Delete" source_record
    DELETE FROM authority.record_entry WHERE id = source_record;

    -- 6. Set "reingest on same MARC" flag back to initial value
    UPDATE  config.internal_flag
      SET   enabled = ingest_same
      WHERE name = 'ingest.reingest.force_on_same_marc'
    ;

    RETURN moved_objects;
END;

Function: authority.normalize_heading(marcxml text)

Returns: text

Language: SQL

Extract the authority heading, thesaurus, and NACO-normalized values from an authority record. The primary purpose is to build a unique index to defend against duplicated authority records from the same thesaurus.

    SELECT authority.normalize_heading($1, FALSE);

Function: authority.normalize_heading(no_thesaurus text, marcxml boolean)

Returns: text

Language: PLPGSQL

DECLARE
    acsaf           authority.control_set_authority_field%ROWTYPE;
    tag_used        TEXT;
    nfi_used        TEXT;
    sf              TEXT;
    sf_node         TEXT;
    tag_node        TEXT;
    thes_code       TEXT;
    cset            INT;
    heading_text    TEXT;
    tmp_text        TEXT;
    first_sf        BOOL;
    auth_id         INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT;
BEGIN
    SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;

    IF cset IS NULL THEN
        SELECT  control_set INTO cset
          FROM  authority.control_set_authority_field
          WHERE tag IN (SELECT UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
          LIMIT 1;
    END IF;

    heading_text := '';
    FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
        tag_used := acsaf.tag;
        nfi_used := acsaf.nfi;
        first_sf := TRUE;

        FOR tag_node IN SELECT unnest(oils_xpath('//*[@tag="'||tag_used||'"]',marcxml))
        LOOP
            FOR sf_node IN SELECT unnest(oils_xpath('//*[local-name() = "subfield" and contains("'||acsaf.sf_list||'",@code)]',tag_node))
            LOOP

                tmp_text := oils_xpath_string('.', sf_node);
                sf := oils_xpath_string('//*/@code', sf_node);

                IF first_sf AND tmp_text IS NOT NULL AND nfi_used IS NOT NULL THEN

                    tmp_text := SUBSTRING(
                        tmp_text FROM
                        COALESCE(
                            NULLIF(
                                REGEXP_REPLACE(
                                    oils_xpath_string('//*[local-name() = "datafield"]/@ind'||nfi_used, tag_node),
                                    $$\D+$$,
                                    '',
                                    'g'
                                ),
                                ''
                            )::INT,
                            0
                        ) + 1
                    );

                END IF;

                first_sf := FALSE;

                IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
                    heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
                END IF;
            END LOOP;

            EXIT WHEN heading_text <> '';
        END LOOP;

        EXIT WHEN heading_text <> '';
    END LOOP;

    IF heading_text <> '' THEN
        IF no_thesaurus IS TRUE THEN
            heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
        ELSE
            thes_code := authority.extract_thesaurus(marcxml);
            heading_text := tag_used || '_' || COALESCE(nfi_used,'-') || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
        END IF;
    ELSE
        heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
    END IF;

    RETURN heading_text;
END;

Function: authority.normalize_heading_for_upsert()

Returns: trigger

Language: PLPGSQL

BEGIN
    NEW.heading := authority.normalize_heading( NEW.marc );
    NEW.simple_heading := authority.simple_normalize_heading( NEW.marc );
    RETURN NEW;
END;

Function: authority.propagate_changes(aid bigint)

Returns: SET OF bigint

Language: SQL

    SELECT authority.propagate_changes( authority, bib ) FROM authority.bib_linking WHERE authority = $1;

Function: authority.propagate_changes(bid bigint, aid bigint)

Returns: bigint

Language: PLPGSQL

DECLARE
    bib_rec biblio.record_entry%ROWTYPE;
    new_marc TEXT;
BEGIN

    SELECT INTO bib_rec * FROM biblio.record_entry WHERE id = bid;

    new_marc := vandelay.merge_record_xml(
        bib_rec.marc, authority.generate_overlay_template(aid));

    IF new_marc = bib_rec.marc THEN
        -- Authority record change had no impact on this bib record.
        -- Nothing left to do.
        RETURN aid;
    END IF;

    PERFORM 1 FROM config.global_flag 
        WHERE name = 'ingest.disable_authority_auto_update_bib_meta' 
            AND enabled;

    IF NOT FOUND THEN 
        -- update the bib record editor and edit_date
        bib_rec.editor := (
            SELECT editor FROM authority.record_entry WHERE id = aid);
        bib_rec.edit_date = NOW();
    END IF;

    UPDATE biblio.record_entry SET
        marc = new_marc,
        editor = bib_rec.editor,
        edit_date = bib_rec.edit_date
    WHERE id = bid;

    RETURN aid;

END;

Function: authority.reingest_authority_full_rec(auth_id bigint)

Returns: void

Language: PLPGSQL

BEGIN
    DELETE FROM authority.full_rec WHERE record = auth_id;
    INSERT INTO authority.full_rec (record, tag, ind1, ind2, subfield, value)
        SELECT record, tag, ind1, ind2, subfield, value FROM authority.flatten_marc( auth_id );

    RETURN;
END;

Function: authority.reingest_authority_rec_descriptor(auth_id bigint)

Returns: void

Language: PLPGSQL

BEGIN
    DELETE FROM authority.rec_descriptor WHERE record = auth_id;
    INSERT INTO authority.rec_descriptor (record, record_status, encoding_level, thesaurus)
        SELECT  auth_id,
                vandelay.marc21_extract_fixed_field(marc,'RecStat'),
                vandelay.marc21_extract_fixed_field(marc,'ELvl'),
                authority.extract_thesaurus(marc)
          FROM  authority.record_entry
          WHERE id = auth_id;
    RETURN;
END;

Function: authority.simple_heading_browse_center(thesauruses integer[], pagesize text, page integer, q integer, atag_list text)

Returns: SET OF bigint

Language: PLPGSQL

DECLARE
    pivot_sort_value    TEXT;
    boffset             INT DEFAULT 0;
    aoffset             INT DEFAULT 0;
    blimit              INT DEFAULT 0;
    alimit              INT DEFAULT 0;
BEGIN

    pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q,thesauruses);

    IF page = 0 THEN
        blimit := pagesize / 2;
        alimit := blimit;

        IF pagesize % 2 <> 0 THEN
            alimit := alimit + 1;
        END IF;
    ELSE
        blimit := pagesize;
        alimit := blimit;

        boffset := pagesize / 2;
        aoffset := boffset;

        IF pagesize % 2 <> 0 THEN
            boffset := boffset + 1;
        END IF;
    END IF;

    IF page <= 0 THEN
        RETURN QUERY
            -- "bottom" half of the browse results
            SELECT id FROM (
                SELECT  ash.id,
                        row_number() over ()
                  FROM  authority.simple_heading ash
                  WHERE ash.atag = ANY (atag_list)
                        AND CASE thesauruses
                            WHEN '' THEN TRUE
                            ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
                            END
                        AND ash.sort_value < pivot_sort_value
                  ORDER BY ash.sort_value DESC
                  LIMIT blimit
                  OFFSET ABS(page) * pagesize - boffset
            ) x ORDER BY row_number DESC;
    END IF;

    IF page >= 0 THEN
        RETURN QUERY
            -- "bottom" half of the browse results
            SELECT  ash.id
              FROM  authority.simple_heading ash
              WHERE ash.atag = ANY (atag_list)
                    AND CASE thesauruses
                        WHEN '' THEN TRUE
                        ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
                        END
                    AND ash.sort_value >= pivot_sort_value
              ORDER BY ash.sort_value
              LIMIT alimit
              OFFSET ABS(page) * pagesize - aoffset;
    END IF;
END;

Function: authority.simple_heading_browse_top(thesauruses integer[], pagesize text, page integer, q integer, atag_list text)

Returns: SET OF bigint

Language: PLPGSQL

DECLARE
    pivot_sort_value    TEXT;
BEGIN

    pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q,thesauruses);

    IF page < 0 THEN
        RETURN QUERY
            -- "bottom" half of the browse results
            SELECT id FROM (
                SELECT  ash.id,
                        row_number() over ()
                  FROM  authority.simple_heading ash
                  WHERE ash.atag = ANY (atag_list)
                        AND CASE thesauruses
                            WHEN '' THEN TRUE
                            ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
                            END
                        AND ash.sort_value < pivot_sort_value
                  ORDER BY ash.sort_value DESC
                  LIMIT pagesize
                  OFFSET (ABS(page) - 1) * pagesize
            ) x ORDER BY row_number DESC;
    END IF;

    IF page >= 0 THEN
        RETURN QUERY
            -- "bottom" half of the browse results
            SELECT  ash.id
              FROM  authority.simple_heading ash
              WHERE ash.atag = ANY (atag_list)
                AND CASE thesauruses
                    WHEN '' THEN TRUE
                    ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
                    END
                    AND ash.sort_value >= pivot_sort_value
              ORDER BY ash.sort_value
              LIMIT pagesize
              OFFSET ABS(page) * pagesize ;
    END IF;
END;

Function: authority.simple_heading_find_pivot(thesauruses integer[], q text, a text)

Returns: text

Language: PLPGSQL

DECLARE
    sort_value_row  RECORD;
    value_row       RECORD;
    t_term          TEXT;
BEGIN

    t_term := public.naco_normalize(q);

    SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
                + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
            ash.sort_value
      INTO  sort_value_row
      FROM  authority.simple_heading ash
      WHERE ash.atag = ANY (a)
            AND ash.sort_value >= t_term
            AND CASE thesauruses
                WHEN '' THEN TRUE
                ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
                END
      ORDER BY rank DESC, ash.sort_value
      LIMIT 1;

    SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
                + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
            ash.sort_value
      INTO  value_row
      FROM  authority.simple_heading ash
      WHERE ash.atag = ANY (a)
            AND ash.value >= t_term
            AND CASE thesauruses
                WHEN '' THEN TRUE
                ELSE ash.thesaurus = ANY(regexp_split_to_array(thesauruses, ','))
                END
      ORDER BY rank DESC, ash.sort_value
      LIMIT 1;

    IF value_row.rank > sort_value_row.rank THEN
        RETURN value_row.sort_value;
    ELSE
        RETURN sort_value_row.sort_value;
    END IF;
END;

Function: authority.simple_heading_search_heading(thesauruses integer[], pagesize text, page integer, q integer, atag_list text)

Returns: SET OF bigint

Language: SQL

    SELECT  ash.id
      FROM  authority.simple_heading ash,
            public.naco_normalize($2) t(term),
            plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
      WHERE ash.atag = ANY ($1)
            AND ash.index_vector @@ ptsq.term
            AND CASE $5
                WHEN '' THEN TRUE
                ELSE ash.thesaurus = ANY(regexp_split_to_array($5, ','))
                END
      ORDER BY ash.sort_value
      LIMIT $4
      OFFSET $4 * $3;

Function: authority.simple_heading_search_rank(thesauruses integer[], pagesize text, page integer, q integer, atag_list text)

Returns: SET OF bigint

Language: SQL

    SELECT  ash.id
      FROM  authority.simple_heading ash,
            public.naco_normalize($2) t(term),
            plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
      WHERE ash.atag = ANY ($1)
            AND ash.index_vector @@ ptsq.term
            AND CASE $5
                WHEN '' THEN TRUE
                ELSE ash.thesaurus = ANY(regexp_split_to_array($5, ','))
                END
      ORDER BY ts_rank_cd(ash.index_vector,ptsq.term,14)::numeric
                    + CASE WHEN ash.sort_value LIKE t.term || '%' THEN 2 ELSE 0 END
                    + CASE WHEN ash.value LIKE t.term || '%' THEN 1 ELSE 0 END DESC
      LIMIT $4
      OFFSET $4 * $3;

Function: authority.simple_heading_set(marcxml text)

Returns: SET OF simple_heading

Language: PLPGSQL

DECLARE
    res             authority.simple_heading%ROWTYPE;
    acsaf           authority.control_set_authority_field%ROWTYPE;
    heading_row     authority.heading%ROWTYPE;
    tag_used        TEXT;
    nfi_used        TEXT;
    sf              TEXT;
    cset            INT;
    heading_text    TEXT;
    joiner_text     TEXT;
    sort_text       TEXT;
    tmp_text        TEXT;
    tmp_xml         TEXT;
    first_sf        BOOL;
    auth_id         INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT;
BEGIN

    SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;

    IF cset IS NULL THEN
        SELECT  control_set INTO cset
          FROM  authority.control_set_authority_field
          WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
          LIMIT 1;
    END IF;

    res.record := auth_id;
    res.thesaurus := authority.extract_thesaurus(marcxml);

    FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
        res.atag := acsaf.id;

        IF acsaf.heading_field IS NULL THEN
            tag_used := acsaf.tag;
            nfi_used := acsaf.nfi;
            joiner_text := COALESCE(acsaf.joiner, ' ');

            FOR tmp_xml IN SELECT UNNEST(XPATH('//*[@tag="'||tag_used||'"]', marcxml::XML)::TEXT[]) LOOP

                heading_text := COALESCE(
                    oils_xpath_string('//*[local-name()="subfield" and contains("'||acsaf.display_sf_list||'",@code)]', tmp_xml, joiner_text),
                    ''
                );

                IF nfi_used IS NOT NULL THEN

                    sort_text := SUBSTRING(
                        heading_text FROM
                        COALESCE(
                            NULLIF(
                                REGEXP_REPLACE(
                                    oils_xpath_string('//*[local-name()="datafield"]/@ind'||nfi_used, tmp_xml::TEXT),
                                    $$\D+$$,
                                    '',
                                    'g'
                                ),
                                ''
                            )::INT,
                            0
                        ) + 1
                    );

                ELSE
                    sort_text := heading_text;
                END IF;

                IF heading_text IS NOT NULL AND heading_text <> '' THEN
                    res.value := heading_text;
                    res.sort_value := public.naco_normalize(sort_text);
                    res.index_vector = to_tsvector('keyword'::regconfig, res.sort_value);
                    RETURN NEXT res;
                END IF;

            END LOOP;
        ELSE
            FOR heading_row IN SELECT * FROM authority.extract_headings(marcxml, ARRAY[acsaf.heading_field]) LOOP
                res.value := heading_row.heading;
                res.sort_value := heading_row.normalized_heading;
                res.index_vector = to_tsvector('keyword'::regconfig, res.sort_value);
                RETURN NEXT res;
            END LOOP;
        END IF;
    END LOOP;

    RETURN;
END;

Function: authority.simple_normalize_heading(marcxml text)

Returns: text

Language: SQL

    SELECT authority.normalize_heading($1, TRUE);

Schema biblio


Table: biblio.monograph_part

biblio.monograph_part Structure
F-Key Name Type Description
id serial PRIMARY KEY
biblio.record_entry.id record bigint NOT NULL
label text NOT NULL
label_sortkey text NOT NULL
deleted boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema biblio


Table: biblio.peer_bib_copy_map

biblio.peer_bib_copy_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
biblio.peer_type.id peer_type integer NOT NULL
biblio.record_entry.id peer_record bigint NOT NULL
target_copy bigint NOT NULL
peer_bib_copy_map_copy_idx target_copy peer_bib_copy_map_record_idx peer_record

Index - Schema biblio


Table: biblio.peer_type

biblio.peer_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema biblio


Table: biblio.record_entry

biblio.record_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id creator integer NOT NULL DEFAULT 1
actor.usr.id editor integer NOT NULL DEFAULT 1
source integer
quality integer
create_date timestamp with time zone NOT NULL DEFAULT now()
edit_date timestamp with time zone NOT NULL DEFAULT now()
active boolean NOT NULL DEFAULT true
deleted boolean NOT NULL DEFAULT false
fingerprint text
tcn_source text NOT NULL DEFAULT 'AUTOGEN'::text
tcn_value text NOT NULL DEFAULT biblio.next_autogen_tcn_value()
marc text NOT NULL
last_xact_id text NOT NULL
vis_attr_vector integer[]
actor.org_unit.id owner integer
share_depth integer
merge_date timestamp with time zone
biblio.record_entry.id merged_to bigint

Tables referencing this one via Foreign Key Constraints:

biblio_record_entry_create_date_idx create_date biblio_record_entry_creator_idx creator biblio_record_entry_edit_date_idx edit_date biblio_record_entry_editor_idx editor biblio_record_entry_fp_idx fingerprint

Index - Schema biblio


Table: biblio.record_note

biblio.record_note Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id record bigint NOT NULL
value text NOT NULL
actor.usr.id creator integer NOT NULL DEFAULT 1
actor.usr.id editor integer NOT NULL DEFAULT 1
deleted boolean NOT NULL DEFAULT false
create_date timestamp with time zone NOT NULL DEFAULT now()
edit_date timestamp with time zone NOT NULL DEFAULT now()
biblio_record_note_creator_idx creator biblio_record_note_editor_idx editor biblio_record_note_record_idx record

Index - Schema biblio


Function: biblio.calculate_bib_visibility_attribute_set(force_source bigint, new_source integer, bib_id boolean)

Returns: integer[]

Language: PLPGSQL

DECLARE
    bib_row     biblio.record_entry%ROWTYPE;
    cn_row      asset.call_number%ROWTYPE;
    attr_set    INT[] := '{}'::INT[];
BEGIN
    SELECT * INTO bib_row FROM biblio.record_entry WHERE id = bib_id;

    IF force_source THEN
        IF new_source IS NOT NULL THEN
            attr_set := attr_set || search.calculate_visibility_attribute(new_source, 'bib_source');
        END IF;
    ELSIF bib_row.source IS NOT NULL THEN
        attr_set := attr_set || search.calculate_visibility_attribute(bib_row.source, 'bib_source');
    END IF;

    FOR cn_row IN
        SELECT  *
          FROM  asset.call_number
          WHERE record = bib_id
                AND label = '##URI##'
                AND NOT deleted
    LOOP
        attr_set := attr_set || search.calculate_visibility_attribute(cn_row.owning_lib, 'luri_org');
    END LOOP;

    RETURN attr_set;
END;

Function: biblio.check_marcxml_well_formed()

Returns: trigger

Language: PLPGSQL

BEGIN

    IF xml_is_well_formed(NEW.marc) THEN
        RETURN NEW;
    ELSE
        RAISE EXCEPTION 'Attempted to % MARCXML that is not well formed', TG_OP;
    END IF;
    
END;

Function: biblio.extract_fingerprint(marc text)

Returns: text

Language: PLPGSQL

DECLARE
	idx		config.biblio_fingerprint%ROWTYPE;
	xfrm		config.xml_transform%ROWTYPE;
	prev_xfrm	TEXT;
	transformed_xml	TEXT;
	xml_node	TEXT;
	xml_node_list	TEXT[];
	raw_text	TEXT;
    output_text TEXT := '';
BEGIN

    IF marc IS NULL OR marc = '' THEN
        RETURN NULL;
    END IF;

	-- Loop over the indexing entries
	FOR idx IN SELECT * FROM config.biblio_fingerprint ORDER BY format, id LOOP

		SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;

		-- See if we can skip the XSLT ... it's expensive
		IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
			-- Can't skip the transform
			IF xfrm.xslt <> '---' THEN
				transformed_xml := oils_xslt_process(marc,xfrm.xslt);
			ELSE
				transformed_xml := marc;
			END IF;

			prev_xfrm := xfrm.name;
		END IF;

		raw_text := COALESCE(
            naco_normalize(
                ARRAY_TO_STRING(
                    oils_xpath(
                        '//text()',
                        (oils_xpath(
                            idx.xpath,
                            transformed_xml,
                            ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] 
                        ))[1]
                    ),
                    ''
                )
            ),
            ''
        );

        raw_text := REGEXP_REPLACE(raw_text, E'\\[.+?\\]', E'');
        raw_text := REGEXP_REPLACE(raw_text, E'\\mthe\\M|\\man?d?d\\M', E'', 'g'); -- arg! the pain!

        IF idx.first_word IS TRUE THEN
            raw_text := REGEXP_REPLACE(raw_text, E'^(\\w+).*?$', E'\\1');
        END IF;

		output_text := output_text || idx.name || ':' ||
					   REGEXP_REPLACE(raw_text, E'\\s+', '', 'g') || ' ';

	END LOOP;

    RETURN BTRIM(output_text);

END;

Function: biblio.extract_located_uris(editor_id bigint, marcxml text, bib_id integer)

Returns: void

Language: PLPGSQL

DECLARE
    uris            TEXT[];
    uri_xml         TEXT;
    uri_label       TEXT;
    uri_href        TEXT;
    uri_use         TEXT;
    uri_owner_list  TEXT[];
    uri_owner       TEXT;
    uri_owner_id    INT;
    uri_id          INT;
    uri_cn_id       INT;
    uri_map_id      INT;
    current_uri     INT;
    current_map     INT;
    uri_map_count   INT;
    current_uri_map_list    INT[];
    current_map_owner_list  INT[];

BEGIN

    uris := oils_xpath('//*[@tag="856" and (@ind1="4" or @ind1="1") and (@ind2="0" or @ind2="1")]',marcxml);
    IF ARRAY_UPPER(uris,1) > 0 THEN
        FOR i IN 1 .. ARRAY_UPPER(uris, 1) LOOP
            -- First we pull info out of the 856
            uri_xml     := uris[i];

            uri_href    := (oils_xpath('//*[@code="u"]/text()',uri_xml))[1];
            uri_label   := (oils_xpath('//*[@code="y"]/text()|//*[@code="3"]/text()',uri_xml))[1];
            uri_use     := (oils_xpath('//*[@code="z"]/text()|//*[@code="2"]/text()|//*[@code="n"]/text()',uri_xml))[1];

            IF uri_label IS NULL THEN
                uri_label := uri_href;
            END IF;
            CONTINUE WHEN uri_href IS NULL;

            -- Get the distinct list of libraries wanting to use 
            SELECT  ARRAY_AGG(
                        DISTINCT REGEXP_REPLACE(
                            x,
                            $re$^.*?\((\w+)\).*$$re$,
                            E'\\1'
                        )
                    ) INTO uri_owner_list
              FROM  UNNEST(
                        oils_xpath(
                            '//*[@code="9"]/text()|//*[@code="w"]/text()|//*[@code="n"]/text()',
                            uri_xml
                        )
                    )x;

            IF ARRAY_UPPER(uri_owner_list,1) > 0 THEN

                -- look for a matching uri
                IF uri_use IS NULL THEN
                    SELECT id INTO uri_id
                        FROM asset.uri
                        WHERE label = uri_label AND href = uri_href AND use_restriction IS NULL AND active
                        ORDER BY id LIMIT 1;
                    IF NOT FOUND THEN -- create one
                        INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
                        SELECT id INTO uri_id
                            FROM asset.uri
                            WHERE label = uri_label AND href = uri_href AND use_restriction IS NULL AND active;
                    END IF;
                ELSE
                    SELECT id INTO uri_id
                        FROM asset.uri
                        WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active
                        ORDER BY id LIMIT 1;
                    IF NOT FOUND THEN -- create one
                        INSERT INTO asset.uri (label, href, use_restriction) VALUES (uri_label, uri_href, uri_use);
                        SELECT id INTO uri_id
                            FROM asset.uri
                            WHERE label = uri_label AND href = uri_href AND use_restriction = uri_use AND active;
                    END IF;
                END IF;

                FOR j IN 1 .. ARRAY_UPPER(uri_owner_list, 1) LOOP
                    uri_owner := uri_owner_list[j];

                    SELECT id INTO uri_owner_id FROM actor.org_unit WHERE shortname = BTRIM(REPLACE(uri_owner,chr(160),''));
                    CONTINUE WHEN NOT FOUND;

                    -- we need a call number to link through
                    SELECT id INTO uri_cn_id FROM asset.call_number WHERE owning_lib = uri_owner_id AND record = bib_id AND label = '##URI##' AND NOT deleted;
                    IF NOT FOUND THEN
                        INSERT INTO asset.call_number (owning_lib, record, create_date, edit_date, creator, editor, label)
                            VALUES (uri_owner_id, bib_id, 'now', 'now', editor_id, editor_id, '##URI##');
                        SELECT id INTO uri_cn_id FROM asset.call_number WHERE owning_lib = uri_owner_id AND record = bib_id AND label = '##URI##' AND NOT deleted;
                    END IF;

                    -- now, link them if they're not already
                    SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
                    IF NOT FOUND THEN
                        INSERT INTO asset.uri_call_number_map (call_number, uri) VALUES (uri_cn_id, uri_id);
                        SELECT id INTO uri_map_id FROM asset.uri_call_number_map WHERE call_number = uri_cn_id AND uri = uri_id;
                    END IF;

                    current_uri_map_list := current_uri_map_list || uri_map_id;
                    current_map_owner_list := current_map_owner_list || uri_cn_id;

                END LOOP;

            END IF;

        END LOOP;
    END IF;

    -- Clear any orphaned URIs, URI mappings and call
    -- numbers for this bib that weren't mapped above.
    FOR current_map IN
        SELECT  m.id
          FROM  asset.uri_call_number_map m
                LEFT JOIN asset.call_number cn ON (cn.id = m.call_number)
          WHERE cn.record = bib_id
                AND cn.label = '##URI##'
                AND (NOT (m.id = ANY (current_uri_map_list))
                     OR current_uri_map_list is NULL)
    LOOP
        SELECT uri INTO current_uri FROM asset.uri_call_number_map WHERE id = current_map;
        DELETE FROM asset.uri_call_number_map WHERE id = current_map;

        SELECT COUNT(*) INTO uri_map_count FROM asset.uri_call_number_map WHERE uri = current_uri;
        IF uri_map_count = 0 THEN
            DELETE FROM asset.uri WHERE id = current_uri;
        END IF;
    END LOOP;

    UPDATE asset.call_number
    SET deleted = TRUE, edit_date = now(), editor = editor_id
    WHERE id IN (
        SELECT  id
          FROM  asset.call_number
          WHERE record = bib_id
                AND label = '##URI##'
                AND NOT deleted
                AND (NOT (id = ANY (current_map_owner_list))
                     OR current_map_owner_list is NULL)
    );

    RETURN;
END;

Function: biblio.extract_metabib_field_entry(only_fields bigint, field_types text, default_joiner text[], rid integer[])

Returns: SET OF field_entry_template

Language: PLPGSQL

DECLARE
    bib     biblio.record_entry%ROWTYPE;
    idx     config.metabib_field%ROWTYPE;
    xfrm        config.xml_transform%ROWTYPE;
    prev_xfrm   TEXT;
    transformed_xml TEXT;
    xml_node    TEXT;
    xml_node_list   TEXT[];
    facet_text  TEXT;
    display_text TEXT;
    browse_text TEXT;
    sort_value  TEXT;
    raw_text    TEXT;
    curr_text   TEXT;
    joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
    authority_text TEXT;
    authority_link BIGINT;
    output_row  metabib.field_entry_template%ROWTYPE;
    process_idx BOOL;
BEGIN

    -- Start out with no field-use bools set
    output_row.browse_nocase = FALSE;
    output_row.browse_field = FALSE;
    output_row.facet_field = FALSE;
    output_row.display_field = FALSE;
    output_row.search_field = FALSE;

    -- Get the record
    SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;

    -- Loop over the indexing entries
    FOR idx IN SELECT * FROM config.metabib_field WHERE id = ANY (only_fields) ORDER BY format LOOP
        CONTINUE WHEN idx.xpath IS NULL OR idx.xpath = ''; -- pure virtual field

        process_idx := FALSE;
        IF idx.display_field AND 'display' = ANY (field_types) THEN process_idx = TRUE; END IF;
        IF idx.browse_field AND 'browse' = ANY (field_types) THEN process_idx = TRUE; END IF;
        IF idx.search_field AND 'search' = ANY (field_types) THEN process_idx = TRUE; END IF;
        IF idx.facet_field AND 'facet' = ANY (field_types) THEN process_idx = TRUE; END IF;
        CONTINUE WHEN process_idx = FALSE; -- disabled for all types

        joiner := COALESCE(idx.joiner, default_joiner);

        SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;

        -- See if we can skip the XSLT ... it's expensive
        IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
            -- Can't skip the transform
            IF xfrm.xslt <> '---' THEN
                transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
            ELSE
                transformed_xml := bib.marc;
            END IF;

            prev_xfrm := xfrm.name;
        END IF;

        xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );

        raw_text := NULL;
        FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP
            CONTINUE WHEN xml_node !~ E'^\\s*<';

            -- XXX much of this should be moved into oils_xpath_string...
            curr_text := ARRAY_TO_STRING(array_remove(array_remove(
                oils_xpath( '//text()', -- get the content of all the nodes within the main selected node
                    REGEXP_REPLACE( xml_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space
                ), ' '), ''),  -- throw away morally empty (bankrupt?) strings
                joiner
            );

            CONTINUE WHEN curr_text IS NULL OR curr_text = '';

            IF raw_text IS NOT NULL THEN
                raw_text := raw_text || joiner;
            END IF;

            raw_text := COALESCE(raw_text,'') || curr_text;

            -- autosuggest/metabib.browse_entry
            IF idx.browse_field THEN
                output_row.browse_nocase = idx.browse_nocase;

                IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN
                    browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
                ELSE
                    browse_text := curr_text;
                END IF;

                IF idx.browse_sort_xpath IS NOT NULL AND
                    idx.browse_sort_xpath <> '' THEN

                    sort_value := oils_xpath_string(
                        idx.browse_sort_xpath, xml_node, joiner,
                        ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
                    );
                ELSE
                    sort_value := browse_text;
                END IF;

                output_row.field_class = idx.field_class;
                output_row.field = idx.id;
                output_row.source = rid;
                output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g'));
                output_row.sort_value :=
                    public.naco_normalize(sort_value);

                output_row.authority := NULL;

                IF idx.authority_xpath IS NOT NULL AND idx.authority_xpath <> '' THEN
                    authority_text := oils_xpath_string(
                        idx.authority_xpath, xml_node, joiner,
                        ARRAY[
                            ARRAY[xfrm.prefix, xfrm.namespace_uri],
                            ARRAY['xlink','http://www.w3.org/1999/xlink']
                        ]
                    );

                    IF authority_text ~ '^\d+$' THEN
                        authority_link := authority_text::BIGINT;
                        PERFORM * FROM authority.record_entry WHERE id = authority_link;
                        IF FOUND THEN
                            output_row.authority := authority_link;
                        END IF;
                    END IF;

                END IF;

                output_row.browse_field = TRUE;
                -- Returning browse rows with search_field = true for search+browse
                -- configs allows us to retain granularity of being able to search
                -- browse fields with "starts with" type operators (for example, for
                -- titles of songs in music albums)
                IF idx.search_field THEN
                    output_row.search_field = TRUE;
                END IF;
                RETURN NEXT output_row;
                output_row.browse_nocase = FALSE;
                output_row.browse_field = FALSE;
                output_row.search_field = FALSE;
                output_row.sort_value := NULL;
            END IF;

            -- insert raw node text for faceting
            IF idx.facet_field THEN

                IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
                    facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
                ELSE
                    facet_text := curr_text;
                END IF;

                output_row.field_class = idx.field_class;
                output_row.field = -1 * idx.id;
                output_row.source = rid;
                output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));

                output_row.facet_field = TRUE;
                RETURN NEXT output_row;
                output_row.facet_field = FALSE;
            END IF;

            -- insert raw node text for display
            IF idx.display_field THEN

                IF idx.display_xpath IS NOT NULL AND idx.display_xpath <> '' THEN
                    display_text := oils_xpath_string( idx.display_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
                ELSE
                    display_text := curr_text;
                END IF;

                output_row.field_class = idx.field_class;
                output_row.field = -1 * idx.id;
                output_row.source = rid;
                output_row.value = BTRIM(REGEXP_REPLACE(display_text, E'\\s+', ' ', 'g'));

                output_row.display_field = TRUE;
                RETURN NEXT output_row;
                output_row.display_field = FALSE;
            END IF;

        END LOOP;

        CONTINUE WHEN raw_text IS NULL OR raw_text = '';

        -- insert combined node text for searching
        IF idx.search_field THEN
            output_row.field_class = idx.field_class;
            output_row.field = idx.id;
            output_row.source = rid;
            output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));

            output_row.search_field = TRUE;
            RETURN NEXT output_row;
            output_row.search_field = FALSE;
        END IF;

    END LOOP;

END;

Function: biblio.extract_quality(best_type text, best_lang text, marc text)

Returns: integer

Language: PLPGSQL

DECLARE
    qual        INT;
    ldr         TEXT;
    tval        TEXT;
    tval_rec    RECORD;
    bval        TEXT;
    bval_rec    RECORD;
    type_map    RECORD;
    ff_pos      RECORD;
    ff_tag_data TEXT;
BEGIN

    IF marc IS NULL OR marc = '' THEN
        RETURN NULL;
    END IF;

    -- First, the count of tags
    qual := ARRAY_UPPER(oils_xpath('//*[local-name()="datafield"]', marc), 1);

    -- now go through a bunch of pain to get the record type
    IF best_type IS NOT NULL THEN
        ldr := (oils_xpath('//*[local-name()="leader"]/text()', marc))[1];

        IF ldr IS NOT NULL THEN
            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 type_map FROM config.marc21_rec_type_map WHERE type_val LIKE '%' || tval || '%' AND blvl_val LIKE '%' || bval || '%';

            IF type_map.code IS NOT NULL THEN
                IF best_type = type_map.code THEN
                    qual := qual + qual / 2;
                END IF;

                FOR ff_pos IN SELECT * FROM config.marc21_ff_pos_map WHERE fixed_field = 'Lang' AND rec_type = type_map.code ORDER BY tag DESC LOOP
                    ff_tag_data := SUBSTRING((oils_xpath('//*[@tag="' || ff_pos.tag || '"]/text()',marc))[1], ff_pos.start_pos + 1, ff_pos.length);
                    IF ff_tag_data = best_lang THEN
                            qual := qual + 100;
                    END IF;
                END LOOP;
            END IF;
        END IF;
    END IF;

    -- Now look for some quality metrics
    -- DCL record?
    IF ARRAY_UPPER(oils_xpath('//*[@tag="040"]/*[@code="a" and contains(.,"DLC")]', marc), 1) = 1 THEN
        qual := qual + 10;
    END IF;

    -- From OCLC?
    IF (oils_xpath('//*[@tag="003"]/text()', marc))[1] ~* E'oclo?c' THEN
        qual := qual + 10;
    END IF;

    RETURN qual;

END;

Function: biblio.fingerprint_trigger()

Returns: trigger

Language: PLPGSQL

BEGIN

    -- For TG_ARGV, first param is language (like 'eng'), second is record type (like 'BKS')

    IF NEW.deleted IS TRUE THEN -- we don't much care, then, do we?
        RETURN NEW;
    END IF;

    NEW.fingerprint := biblio.extract_fingerprint(NEW.marc);
    NEW.quality := biblio.extract_quality(NEW.marc, TG_ARGV[0], TG_ARGV[1]);

    RETURN NEW;

END;

Function: biblio.flatten_marc(rid bigint)

Returns: SET OF full_rec

Language: PLPGSQL

DECLARE
	bib	biblio.record_entry%ROWTYPE;
	output	metabib.full_rec%ROWTYPE;
	field	RECORD;
BEGIN
	SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;

	FOR field IN SELECT * FROM vandelay.flatten_marc( bib.marc ) LOOP
		output.record := rid;
		output.ind1 := field.ind1;
		output.ind2 := field.ind2;
		output.tag := field.tag;
		output.subfield := field.subfield;
		output.value := field.value;

		RETURN NEXT output;
	END LOOP;
END;

Function: biblio.indexing_ingest_or_delete()

Returns: trigger

Language: PLPGSQL

DECLARE
    tmp_bool BOOL;
BEGIN

    IF NEW.deleted THEN -- If this bib is deleted

        PERFORM * FROM config.internal_flag WHERE
            name = 'ingest.metarecord_mapping.preserve_on_delete' AND enabled;

        tmp_bool := FOUND; -- Just in case this is changed by some other statement

        PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint, TRUE, tmp_bool );

        IF NOT tmp_bool THEN
            -- One needs to keep these around to support searches
            -- with the #deleted modifier, so one should turn on the named
            -- internal flag for that functionality.
            DELETE FROM metabib.record_attr_vector_list WHERE source = NEW.id;
        END IF;

        DELETE FROM authority.bib_linking WHERE bib = NEW.id; -- Avoid updating fields in bibs that are no longer visible
        DELETE FROM biblio.peer_bib_copy_map WHERE peer_record = NEW.id; -- Separate any multi-homed items
        DELETE FROM metabib.browse_entry_def_map WHERE source = NEW.id; -- Don't auto-suggest deleted bibs
        RETURN NEW; -- and we're done
    END IF;

    IF TG_OP = 'UPDATE' AND OLD.deleted IS FALSE THEN -- re-ingest?
        PERFORM * FROM config.internal_flag WHERE name = 'ingest.reingest.force_on_same_marc' AND enabled;

        IF NOT FOUND AND OLD.marc = NEW.marc THEN -- don't do anything if the MARC didn't change
            RETURN NEW;
        END IF;
    END IF;

    -- Record authority linking
    PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_authority_linking' AND enabled;
    IF NOT FOUND THEN
        PERFORM biblio.map_authority_linking( NEW.id, NEW.marc );
    END IF;

    -- Flatten and insert the mfr data
    PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_full_rec' AND enabled;
    IF NOT FOUND THEN
        PERFORM metabib.reingest_metabib_full_rec(NEW.id);

        -- Now we pull out attribute data, which is dependent on the mfr for all but XPath-based fields
        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_metabib_rec_descriptor' AND enabled;
        IF NOT FOUND THEN
            PERFORM metabib.reingest_record_attributes(NEW.id, NULL, NEW.marc, TG_OP = 'INSERT' OR OLD.deleted);
        END IF;
    END IF;

    -- Gather and insert the field entry data
    PERFORM metabib.reingest_metabib_field_entries(NEW.id);

    -- Located URI magic
    PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_located_uri' AND enabled;
    IF NOT FOUND THEN PERFORM biblio.extract_located_uris( NEW.id, NEW.marc, NEW.editor ); END IF;

    -- (re)map metarecord-bib linking
    IF TG_OP = 'INSERT' THEN -- if not deleted and performing an insert, check for the flag
        PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_insert' AND enabled;
        IF NOT FOUND THEN
            PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
        END IF;
    ELSE -- we're doing an update, and we're not deleted, remap
        PERFORM * FROM config.internal_flag WHERE name = 'ingest.metarecord_mapping.skip_on_update' AND enabled;
        IF NOT FOUND THEN
            PERFORM metabib.remap_metarecord_for_bib( NEW.id, NEW.fingerprint );
        END IF;
    END IF;

    RETURN NEW;
END;

Function: biblio.map_authority_linking(marc bigint, bibid text)

Returns: bigint

Language: SQL

    DELETE FROM authority.bib_linking WHERE bib = $1;
    INSERT INTO authority.bib_linking (bib, authority)
        SELECT  y.bib,
                y.authority
          FROM (    SELECT  DISTINCT $1 AS bib,
                            BTRIM(remove_paren_substring(txt))::BIGINT AS authority
                      FROM  unnest(oils_xpath('//*[@code="0"]/text()',$2)) x(txt)
                      WHERE BTRIM(remove_paren_substring(txt)) ~ $re$^\d+$$re$
                ) y JOIN authority.record_entry r ON r.id = y.authority;
    SELECT $1;

Function: biblio.marc21_extract_all_fixed_fields(rid bigint)

Returns: SET OF record_ff_map

Language: SQL

    SELECT $1 AS record, ff_name, ff_value FROM vandelay.marc21_extract_all_fixed_fields( (SELECT marc FROM biblio.record_entry WHERE id = $1), TRUE );

Function: biblio.marc21_extract_fixed_field(ff bigint, rid text)

Returns: text

Language: SQL

    SELECT * FROM vandelay.marc21_extract_fixed_field( (SELECT marc FROM biblio.record_entry WHERE id = $1), $2, TRUE );

Function: biblio.marc21_extract_fixed_field_list(ff bigint, rid text)

Returns: text[]

Language: SQL

    SELECT * FROM vandelay.marc21_extract_fixed_field_list( (SELECT marc FROM biblio.record_entry WHERE id = $1), $2, TRUE );

Function: biblio.marc21_physical_characteristics(rid bigint)

Returns: SET OF marc21_physical_characteristics

Language: SQL

    SELECT id, $1 AS record, ptype, subfield, value FROM vandelay.marc21_physical_characteristics( (SELECT marc FROM biblio.record_entry WHERE id = $1) );

Function: biblio.next_autogen_tcn_value()

Returns: text

Language: PLPGSQL

	BEGIN RETURN 'AUTOGENERATED-' || nextval('biblio.autogen_tcn_value_seq'::TEXT); END;

Function: biblio.normalize_biblio_monograph_part_sortkey()

Returns: trigger

Language: PLPGSQL

BEGIN
    NEW.label_sortkey := REGEXP_REPLACE(
        evergreen.lpad_number_substrings(
            naco_normalize(NEW.label),
            '0',
            10
        ),
        E'\\s+',
        '',
        'g'
    );
    RETURN NEW;
END;

Schema booking


Table: booking.reservation

booking.reservation Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.billable_xact_id_seq'::regclass)
actor.usr.id usr integer NOT NULL
xact_start timestamp with time zone NOT NULL DEFAULT now()
xact_finish timestamp with time zone
unrecovered boolean
request_time timestamp with time zone NOT NULL DEFAULT now()
start_time timestamp with time zone
end_time timestamp with time zone
capture_time timestamp with time zone
cancel_time timestamp with time zone
pickup_time timestamp with time zone
return_time timestamp with time zone
booking_interval interval
fine_interval interval
fine_amount numeric(8,2)
max_fine numeric(8,2)
booking.resource_type.id target_resource_type integer NOT NULL
booking.resource.id target_resource integer
booking.resource.id current_resource integer
actor.org_unit.id request_lib integer NOT NULL
actor.org_unit.id pickup_lib integer
actor.usr.id capture_staff integer
email_notify boolean NOT NULL DEFAULT false
note text

Table booking.reservation Inherits billable_xact,

Tables referencing this one via Foreign Key Constraints:

Index - Schema booking


Table: booking.reservation_attr_value_map

booking.reservation_attr_value_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
booking.reservation.id reservation integer UNIQUE#1 NOT NULL
booking.resource_attr_value.id attr_value integer UNIQUE#1 NOT NULL

Index - Schema booking


Table: booking.resource

booking.resource Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
booking.resource_type.id type integer NOT NULL
overbook boolean NOT NULL DEFAULT false
barcode text UNIQUE#1 NOT NULL
deposit boolean NOT NULL DEFAULT false
deposit_amount numeric(8,2) NOT NULL DEFAULT 0.00
user_fee numeric(8,2) NOT NULL DEFAULT 0.00

Tables referencing this one via Foreign Key Constraints:

Index - Schema booking


Table: booking.resource_attr

booking.resource_attr Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer NOT NULL
name text UNIQUE#1 NOT NULL
booking.resource_type.id resource_type integer UNIQUE#1 NOT NULL
required boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema booking


Table: booking.resource_attr_map

booking.resource_attr_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
booking.resource.id resource integer UNIQUE#1 NOT NULL
booking.resource_attr.id resource_attr integer UNIQUE#1 NOT NULL
booking.resource_attr_value.id value integer NOT NULL

Index - Schema booking


Table: booking.resource_attr_value

booking.resource_attr_value Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
booking.resource_attr.id attr integer UNIQUE#1 NOT NULL
valid_value text UNIQUE#1 NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema booking


Table: booking.resource_type

booking.resource_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
elbow_room interval
fine_interval interval
fine_amount numeric(8,2) NOT NULL
max_fine numeric(8,2)
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
catalog_item boolean NOT NULL DEFAULT false
transferable boolean NOT NULL DEFAULT false
biblio.record_entry.id record bigint UNIQUE#1

Tables referencing this one via Foreign Key Constraints:

Index - Schema booking


Schema config

The config schema holds static configuration data for the Evergreen installation.


View: config.audience_map

config.audience_map Structure
F-Key Name Type Description
code text
value text
description text
SELECT coded_value_map.code
,
    coded_value_map.value
,
    coded_value_map.description
   
FROM config.coded_value_map
  
WHERE (coded_value_map.ctype = 'audience'::text);

Index - Schema config


Table: config.barcode_completion

config.barcode_completion Structure
F-Key Name Type Description
id serial PRIMARY KEY
active boolean NOT NULL DEFAULT true
actor.org_unit.id org_unit integer NOT NULL
prefix text
suffix text
length integer NOT NULL
padding text
padding_end boolean NOT NULL DEFAULT false
asset boolean NOT NULL DEFAULT true
actor boolean NOT NULL DEFAULT true

Index - Schema config


Table: config.best_hold_order

config.best_hold_order Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE
pprox integer
hprox integer
owning_lib_to_home_lib_prox integer
aprox integer
approx integer
priority integer
cut integer
depth integer
htime integer
rtime integer
shtime integer

 

config.best_hold_order Constraints
Name Constraint
best_hold_order_check CHECK (((pprox IS NOT NULL) OR (hprox IS NOT NULL) OR (owning_lib_to_home_lib_prox IS NOT NULL) OR (aprox IS NOT NULL) OR (priority IS NOT NULL) OR (cut IS NOT NULL) OR (depth IS NOT NULL) OR (htime IS NOT NULL) OR (rtime IS NOT NULL)))

Index - Schema config


View: config.bib_level_map

config.bib_level_map Structure
F-Key Name Type Description
code text
value text
SELECT coded_value_map.code
,
    coded_value_map.value
   
FROM config.coded_value_map
  
WHERE (coded_value_map.ctype = 'bib_level'::text);

Index - Schema config


Table: config.bib_source

This is table is used to set up the relative "quality" of each MARC source, such as OCLC. Also identifies "transcendant" sources, i.e., sources of bib records that should display in the OPAC even if no copies or located URIs are attached. Also indicates if the source is allowed to have actual copies on its bibs. Volumes for targeted URIs are unaffected by this setting.

config.bib_source Structure
F-Key Name Type Description
id serial PRIMARY KEY
quality integer
source text UNIQUE NOT NULL
transcendant boolean NOT NULL DEFAULT false
can_have_copies boolean NOT NULL DEFAULT true

 

config.bib_source Constraints
Name Constraint
bib_source_quality_check CHECK (((quality >= 0) AND (quality <= 100)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.biblio_fingerprint

config.biblio_fingerprint Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL
xpath text NOT NULL
first_word boolean NOT NULL DEFAULT false
format text NOT NULL DEFAULT 'marcxml'::text

Index - Schema config


Table: config.billing_type

config.billing_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
default_price numeric(6,2)

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.carousel_type

config.carousel_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL
automatic boolean NOT NULL DEFAULT true
filter_by_age boolean NOT NULL DEFAULT false
filter_by_copy_owning_lib boolean NOT NULL DEFAULT false
filter_by_copy_location boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.circ_limit_group

config.circ_limit_group Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.circ_limit_set

config.circ_limit_set Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
actor.org_unit.id owning_lib integer NOT NULL
items_out integer NOT NULL
depth integer NOT NULL
global boolean NOT NULL DEFAULT false
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.circ_limit_set_circ_mod_map

config.circ_limit_set_circ_mod_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.circ_limit_set.id limit_set integer UNIQUE#1 NOT NULL
config.circ_modifier.code circ_mod text UNIQUE#1 NOT NULL

Index - Schema config


Table: config.circ_limit_set_copy_loc_map

config.circ_limit_set_copy_loc_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.circ_limit_set.id limit_set integer UNIQUE#1 NOT NULL
asset.copy_location.id copy_loc integer UNIQUE#1 NOT NULL

Index - Schema config


Table: config.circ_limit_set_group_map

config.circ_limit_set_group_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.circ_limit_set.id limit_set integer UNIQUE#1 NOT NULL
config.circ_limit_group.id limit_group integer UNIQUE#1 NOT NULL
check_only boolean NOT NULL DEFAULT false

Index - Schema config


Table: config.circ_matrix_limit_set_map

config.circ_matrix_limit_set_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.circ_matrix_matchpoint.id matchpoint integer UNIQUE#1 NOT NULL
config.circ_limit_set.id limit_set integer UNIQUE#1 NOT NULL
fallthrough boolean NOT NULL DEFAULT false
active boolean NOT NULL DEFAULT true

Index - Schema config


Table: config.circ_matrix_matchpoint

config.circ_matrix_matchpoint Structure
F-Key Name Type Description
id serial PRIMARY KEY
active boolean NOT NULL DEFAULT true
actor.org_unit.id org_unit integer NOT NULL
permission.grp_tree.id grp integer NOT NULL
config.circ_modifier.code circ_modifier text
asset.copy_location.id copy_location integer
marc_type text
marc_form text
marc_bib_level text
marc_vr_format text
actor.org_unit.id copy_circ_lib integer
actor.org_unit.id copy_owning_lib integer
actor.org_unit.id user_home_ou integer
ref_flag boolean
juvenile_flag boolean
is_renewal boolean
usr_age_lower_bound interval
usr_age_upper_bound interval
item_age interval
circulate boolean
config.rule_circ_duration.id duration_rule integer
config.rule_recurring_fine.id recurring_fine_rule integer
config.rule_max_fine.id max_fine_rule integer
config.hard_due_date.id hard_due_date integer
renewals integer
grace_period interval
script_test text
total_copy_hold_ratio double precision
available_copy_hold_ratio double precision
description text
renew_extends_due_date boolean NOT NULL DEFAULT false
renew_extend_min_interval interval

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.circ_matrix_weights

config.circ_matrix_weights Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
org_unit numeric(6,2) NOT NULL
grp numeric(6,2) NOT NULL
circ_modifier numeric(6,2) NOT NULL
copy_location numeric(6,2) NOT NULL
marc_type numeric(6,2) NOT NULL
marc_form numeric(6,2) NOT NULL
marc_bib_level numeric(6,2) NOT NULL
marc_vr_format numeric(6,2) NOT NULL
copy_circ_lib numeric(6,2) NOT NULL
copy_owning_lib numeric(6,2) NOT NULL
user_home_ou numeric(6,2) NOT NULL
ref_flag numeric(6,2) NOT NULL
juvenile_flag numeric(6,2) NOT NULL
is_renewal numeric(6,2) NOT NULL
usr_age_lower_bound numeric(6,2) NOT NULL
usr_age_upper_bound numeric(6,2) NOT NULL
item_age numeric(6,2) NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.circ_modifier

config.circ_modifier Structure
F-Key Name Type Description
code text PRIMARY KEY
name text UNIQUE NOT NULL
description text NOT NULL
sip2_media_type text NOT NULL
magnetic_media boolean NOT NULL DEFAULT true
avg_wait_time interval

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.coded_value_map

config.coded_value_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.record_attr_definition.name ctype text NOT NULL
code text NOT NULL
value text NOT NULL
description text
opac_visible boolean NOT NULL DEFAULT true
search_label text
is_simple boolean NOT NULL DEFAULT false
concept_uri text

Tables referencing this one via Foreign Key Constraints:

config_coded_value_map_ctype_idx ctype

Index - Schema config


Table: config.composite_attr_entry_definition

config.composite_attr_entry_definition Structure
F-Key Name Type Description
config.coded_value_map.id coded_value integer PRIMARY KEY
definition text NOT NULL

Index - Schema config


Table: config.copy_alert_type

config.copy_alert_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id scope_org integer NOT NULL
active boolean NOT NULL DEFAULT true
name text UNIQUE NOT NULL
state config.copy_alert_type_state
event config.copy_alert_type_event
in_renew boolean
invert_location boolean NOT NULL DEFAULT false
at_circ boolean
at_owning boolean
next_status integer[]

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.copy_status

Copy Statuses The available copy statuses, and whether a copy in that status is available for hold request capture. 0 (zero) is the only special number in this set, meaning that the item is available for immediate checkout, and is counted as available in the OPAC. Statuses with an ID below 100 are not removable, and have special meaning in the code. Do not change them except to translate the textual name. You may add and remove statuses above 100, and these can be used to remove items from normal circulation without affecting the rest of the copy's values or its location.

config.copy_status Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
holdable boolean NOT NULL DEFAULT false
opac_visible boolean NOT NULL DEFAULT false
copy_active boolean NOT NULL DEFAULT false
restrict_copy_delete boolean NOT NULL DEFAULT false
is_available boolean NOT NULL DEFAULT false
hopeless_prone boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.copy_tag_type

config.copy_tag_type Structure
F-Key Name Type Description
code text PRIMARY KEY
label text NOT NULL
actor.org_unit.id owner integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

config_copy_tag_type_owner_idx owner

Index - Schema config


Table: config.db_patch_dependencies

config.db_patch_dependencies Structure
F-Key Name Type Description
db_patch text PRIMARY KEY
supersedes text[]
deprecates text[]

Index - Schema config


Table: config.display_field_map

config.display_field_map Structure
F-Key Name Type Description
name text PRIMARY KEY
config.metabib_field.id field integer
multi boolean DEFAULT false

Index - Schema config


Table: config.filter_dialog_filter_set

config.filter_dialog_filter_set Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
actor.org_unit.id owning_lib integer UNIQUE#1 NOT NULL
actor.usr.id creator integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
config.filter_dialog_interface.key interface text NOT NULL
filters text NOT NULL

 

config.filter_dialog_filter_set Constraints
Name Constraint
config_filter_dialog_filter_set_filters_check CHECK (is_json(filters))

Index - Schema config


Table: config.filter_dialog_interface

config.filter_dialog_interface Structure
F-Key Name Type Description
key text PRIMARY KEY
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.floating_group

config.floating_group Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
manual boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.floating_group_member

config.floating_group_member Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.floating_group.id floating_group integer NOT NULL
actor.org_unit.id org_unit integer NOT NULL
stop_depth integer NOT NULL
max_depth integer
exclude boolean NOT NULL DEFAULT false

Index - Schema config


Table: config.geolocation_service

config.geolocation_service Structure
F-Key Name Type Description
id serial PRIMARY KEY
active boolean
actor.org_unit.id owner integer NOT NULL
name text
service_code text
api_key text

Index - Schema config


Table: config.global_flag

config.global_flag Structure
F-Key Name Type Description
name text PRIMARY KEY
value text
enabled boolean NOT NULL DEFAULT false
label text NOT NULL

Table config.global_flag Inherits internal_flag,

Index - Schema config


Table: config.hard_due_date

config.hard_due_date Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
ceiling_date timestamp with time zone NOT NULL
forceto boolean NOT NULL
owner integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.hard_due_date_values

config.hard_due_date_values Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.hard_due_date.id hard_due_date integer NOT NULL
ceiling_date timestamp with time zone NOT NULL
active_date timestamp with time zone NOT NULL

Index - Schema config


Table: config.hold_matrix_matchpoint

config.hold_matrix_matchpoint Structure
F-Key Name Type Description
id serial PRIMARY KEY
active boolean NOT NULL DEFAULT true
strict_ou_match boolean NOT NULL DEFAULT false
actor.org_unit.id user_home_ou integer
actor.org_unit.id request_ou integer
actor.org_unit.id pickup_ou integer
actor.org_unit.id item_owning_ou integer
actor.org_unit.id item_circ_ou integer
permission.grp_tree.id usr_grp integer
permission.grp_tree.id requestor_grp integer NOT NULL
config.circ_modifier.code circ_modifier text
marc_type text
marc_form text
marc_bib_level text
marc_vr_format text
juvenile_flag boolean
ref_flag boolean
item_age interval
holdable boolean NOT NULL DEFAULT true
distance_is_from_owner boolean NOT NULL DEFAULT false
actor.org_unit_type.id transit_range integer
max_holds integer
include_frozen_holds boolean NOT NULL DEFAULT true
stop_blocked_user boolean NOT NULL DEFAULT false
config.rule_age_hold_protect.id age_hold_protect_rule integer
description text

Index - Schema config


Table: config.hold_matrix_weights

config.hold_matrix_weights Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
user_home_ou numeric(6,2) NOT NULL
request_ou numeric(6,2) NOT NULL
pickup_ou numeric(6,2) NOT NULL
item_owning_ou numeric(6,2) NOT NULL
item_circ_ou numeric(6,2) NOT NULL
usr_grp numeric(6,2) NOT NULL
requestor_grp numeric(6,2) NOT NULL
circ_modifier numeric(6,2) NOT NULL
marc_type numeric(6,2) NOT NULL
marc_form numeric(6,2) NOT NULL
marc_bib_level numeric(6,2) NOT NULL
marc_vr_format numeric(6,2) NOT NULL
juvenile_flag numeric(6,2) NOT NULL
ref_flag numeric(6,2) NOT NULL
item_age numeric(6,2) NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.hold_type

config.hold_type Structure
F-Key Name Type Description
id serial NOT NULL
hold_type text UNIQUE
description text

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.i18n_core

config.i18n_core Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
fq_field text NOT NULL
identity_value text NOT NULL
config.i18n_locale.code translation text NOT NULL
string text NOT NULL

Index - Schema config


Table: config.i18n_locale

config.i18n_locale Structure
F-Key Name Type Description
code text PRIMARY KEY
marc_code text NOT NULL
name text UNIQUE NOT NULL
description text
rtl boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.identification_type

Types of valid patron identification. Each patron must display at least one valid form of identification in order to get a library card. This table lists those forms.

config.identification_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.idl_field_doc

config.idl_field_doc Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
fm_class text NOT NULL
field text NOT NULL
actor.org_unit.id owner integer NOT NULL
string text NOT NULL

Index - Schema config


Table: config.index_normalizer

config.index_normalizer Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
description text
func text NOT NULL
param_count integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.internal_flag

config.internal_flag Structure
F-Key Name Type Description
name text PRIMARY KEY
value text
enabled boolean NOT NULL DEFAULT false

Index - Schema config


View: config.item_form_map

config.item_form_map Structure
F-Key Name Type Description
code text
value text
SELECT coded_value_map.code
,
    coded_value_map.value
   
FROM config.coded_value_map
  
WHERE (coded_value_map.ctype = 'item_form'::text);

Index - Schema config


View: config.item_type_map

config.item_type_map Structure
F-Key Name Type Description
code text
value text
SELECT coded_value_map.code
,
    coded_value_map.value
   
FROM config.coded_value_map
  
WHERE (coded_value_map.ctype = 'item_type'::text);

Index - Schema config


View: config.language_map

config.language_map Structure
F-Key Name Type Description
code text
value text
SELECT coded_value_map.code
,
    coded_value_map.value
   
FROM config.coded_value_map
  
WHERE (coded_value_map.ctype = 'item_lang'::text);

Index - Schema config


View: config.lit_form_map

config.lit_form_map Structure
F-Key Name Type Description
code text
value text
description text
SELECT coded_value_map.code
,
    coded_value_map.value
,
    coded_value_map.description
   
FROM config.coded_value_map
  
WHERE (coded_value_map.ctype = 'lit_form'::text);

Index - Schema config


Table: config.marc21_ff_pos_map

config.marc21_ff_pos_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
fixed_field text NOT NULL
tag text NOT NULL
rec_type text NOT NULL
start_pos integer NOT NULL
length integer NOT NULL
default_val text NOT NULL DEFAULT ' '::text

Index - Schema config


Table: config.marc21_physical_characteristic_subfield_map

config.marc21_physical_characteristic_subfield_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.marc21_physical_characteristic_type_map.ptype_key ptype_key text NOT NULL
subfield text NOT NULL
start_pos integer NOT NULL
length integer NOT NULL
label text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.marc21_physical_characteristic_type_map

config.marc21_physical_characteristic_type_map Structure
F-Key Name Type Description
ptype_key text PRIMARY KEY
label text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.marc21_physical_characteristic_value_map

config.marc21_physical_characteristic_value_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
value text NOT NULL
config.marc21_physical_characteristic_subfield_map.id ptype_subfield integer NOT NULL
label text NOT NULL

Index - Schema config


Table: config.marc21_rec_type_map

config.marc21_rec_type_map Structure
F-Key Name Type Description
code text PRIMARY KEY
type_val text NOT NULL
blvl_val text NOT NULL

Index - Schema config


Table: config.marc_field

This table stores a list of MARC fields recognized by the Evergreen instance. Note that we're not aiming for completely generic ISO2709 support: we're assuming things like three characters for a tag, one-character subfield labels, two indicators per variable data field, and the like, all of which are technically specializations of ISO2709. Of particular significance is the owner column; if it's set to a null value, the field definition is assumed to come from a national standards body; if it's set to a non-null value, the field definition is an OU-level addition to or override of the standard.

config.marc_field Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.marc_format.id marc_format integer NOT NULL
marc_record_type config.marc_record_type NOT NULL
tag character(3) NOT NULL
name text
description text
fixed_field boolean
repeatable boolean
mandatory boolean
hidden boolean
actor.org_unit.id owner integer

 

config.marc_field Constraints
Name Constraint
config_standard_marc_tags_are_fully_specified CHECK (((owner IS NOT NULL) OR ((owner IS NULL) AND (repeatable IS NOT NULL) AND (mandatory IS NOT NULL) AND (hidden IS NOT NULL))))
config_marc_field_owner_idx owner config_marc_field_tag_idx tag

Index - Schema config


View: config.marc_field_for_ou

config.marc_field_for_ou Structure
F-Key Name Type Description
id integer
marc_format integer
marc_record_type config.marc_record_type
tag character(3)
name text
description text
fixed_field boolean
repeatable boolean
mandatory boolean
hidden boolean
owner integer
depth integer
 WITH RECURSIVE ou_marc_fields
(id
     , marc_format
     , marc_record_type
     , tag
     , name
     , description
     , fixed_field
     , repeatable
     , mandatory
     , hidden
     , owner
     , depth
) AS 
(
         
SELECT marc_field.id
     ,
            marc_field.marc_format
     ,
            marc_field.marc_record_type
     ,
            marc_field.tag
     ,
            marc_field.name
     ,
            marc_field.description
     ,
            marc_field.fixed_field
     ,
            marc_field.repeatable
     ,
            marc_field.mandatory
     ,
            marc_field.hidden
     ,
            marc_field.owner
     ,
            0 AS "?column?"
           
  FROM config.marc_field
          
 WHERE (marc_field.owner IS NULL)
        
 UNION
         
SELECT marc_field.id
     ,
            marc_field.marc_format
     ,
            marc_field.marc_record_type
     ,
            marc_field.tag
     ,
            marc_field.name
     ,
            marc_field.description
     ,
            marc_field.fixed_field
     ,
            marc_field.repeatable
     ,
            marc_field.mandatory
     ,
            marc_field.hidden
     ,
            marc_field.owner
     ,
            0
           
  FROM config.marc_field
          
 WHERE (NOT 
           (ARRAY[
                 (marc_field.marc_format)::text
                 , (marc_field.marc_record_type)::text
                 , (marc_field.tag)::text] IN 
                 (
                  SELECT ARRAY[
                       (marc_field_1.marc_format)::text
                       , (marc_field_1.marc_record_type)::text
                       , (marc_field_1.tag)::text] AS "array"
                   
                    FROM config.marc_field marc_field_1
                  
                   WHERE (marc_field_1.owner IS NULL)
                 )
           )
     )
        
 UNION
         
SELECT c.id
     ,
            c.marc_format
     ,
            c.marc_record_type
     ,
            c.tag
     ,
            COALESCE
     (c.name
           , p.name
     ) AS "coalesce"
     ,
            COALESCE
     (c.description
           , p.description
     ) AS "coalesce"
     ,
            COALESCE
     (c.fixed_field
           , p.fixed_field
     ) AS "coalesce"
     ,
            COALESCE
     (c.repeatable
           , p.repeatable
     ) AS "coalesce"
     ,
            COALESCE
     (c.mandatory
           , p.mandatory
     ) AS "coalesce"
     ,
            COALESCE
     (c.hidden
           , p.hidden
     ) AS "coalesce"
     ,
            c.owner
     ,
            
     (p.depth + 1)
           
  FROM (
           (config.marc_field c
             
              JOIN ou_marc_fields p 
             USING (marc_format
                       , marc_record_type
                       , tag
                 )
           )
             
        JOIN actor.org_unit aou 
          ON (
                 (c.owner = aou.id)
           )
     )
          
 WHERE (
           (aou.parent_ou = p.owner)
          OR (
                 (aou.parent_ou IS NULL)
               AND (p.owner IS NULL)
           )
     )
        
)
 
SELECT ou_marc_fields.id
,
    ou_marc_fields.marc_format
,
    ou_marc_fields.marc_record_type
,
    ou_marc_fields.tag
,
    ou_marc_fields.name
,
    ou_marc_fields.description
,
    ou_marc_fields.fixed_field
,
    ou_marc_fields.repeatable
,
    ou_marc_fields.mandatory
,
    ou_marc_fields.hidden
,
    ou_marc_fields.owner
,
    ou_marc_fields.depth
   
FROM ou_marc_fields;

Index - Schema config


Table: config.marc_format

List of MARC formats supported by this Evergreen database. This exists primarily as a hook for future support of UNIMARC, though whether that will ever happen remains to be seen.

config.marc_format Structure
F-Key Name Type Description
id serial PRIMARY KEY
code text NOT NULL
name text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.marc_subfield

This table stores the list of subfields recognized by this Evergreen instance. As with config.marc_field, of particular significance is the owner column; if it's set to a null value, the subfield definition is assumed to come from a national standards body; if it's set to a non-null value, the subfield definition is an OU-level addition to or override of the standard.

config.marc_subfield Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.marc_format.id marc_format integer NOT NULL
marc_record_type config.marc_record_type NOT NULL
tag character(3) NOT NULL
code character(1) NOT NULL
description text
repeatable boolean
mandatory boolean
hidden boolean
config.record_attr_definition.name value_ctype text
actor.org_unit.id owner integer

 

config.marc_subfield Constraints
Name Constraint
config_standard_marc_subfields_are_fully_specified CHECK (((owner IS NOT NULL) OR ((owner IS NULL) AND (repeatable IS NOT NULL) AND (mandatory IS NOT NULL) AND (hidden IS NOT NULL))))
config_marc_subfield_tag_code_idx tag, code

Index - Schema config


View: config.marc_subfield_for_ou

config.marc_subfield_for_ou Structure
F-Key Name Type Description
id integer
marc_format integer
marc_record_type config.marc_record_type
tag character(3)
code character(1)
description text
repeatable boolean
mandatory boolean
hidden boolean
value_ctype text
owner integer
depth integer
 WITH RECURSIVE ou_marc_subfields
(id
     , marc_format
     , marc_record_type
     , tag
     , code
     , description
     , repeatable
     , mandatory
     , hidden
     , value_ctype
     , owner
     , depth
) AS 
(
         
SELECT marc_subfield.id
     ,
            marc_subfield.marc_format
     ,
            marc_subfield.marc_record_type
     ,
            marc_subfield.tag
     ,
            marc_subfield.code
     ,
            marc_subfield.description
     ,
            marc_subfield.repeatable
     ,
            marc_subfield.mandatory
     ,
            marc_subfield.hidden
     ,
            marc_subfield.value_ctype
     ,
            marc_subfield.owner
     ,
            0 AS "?column?"
           
  FROM config.marc_subfield
          
 WHERE (marc_subfield.owner IS NULL)
        
 UNION
         
SELECT marc_subfield.id
     ,
            marc_subfield.marc_format
     ,
            marc_subfield.marc_record_type
     ,
            marc_subfield.tag
     ,
            marc_subfield.code
     ,
            marc_subfield.description
     ,
            marc_subfield.repeatable
     ,
            marc_subfield.mandatory
     ,
            marc_subfield.hidden
     ,
            marc_subfield.value_ctype
     ,
            marc_subfield.owner
     ,
            0
           
  FROM config.marc_subfield
          
 WHERE (NOT 
           (ARRAY[
                 (marc_subfield.marc_format)::text
                 , (marc_subfield.marc_record_type)::text
                 , (marc_subfield.tag)::text
                 , (marc_subfield.code)::text] IN 
                 (
                  SELECT ARRAY[
                       (marc_subfield_1.marc_format)::text
                       , (marc_subfield_1.marc_record_type)::text
                       , (marc_subfield_1.tag)::text
                       , (marc_subfield_1.code)::text] AS "array"
                   
                    FROM config.marc_subfield marc_subfield_1
                  
                   WHERE (marc_subfield_1.owner IS NULL)
                 )
           )
     )
        
 UNION
         
SELECT c.id
     ,
            c.marc_format
     ,
            c.marc_record_type
     ,
            c.tag
     ,
            c.code
     ,
            COALESCE
     (c.description
           , p.description
     ) AS "coalesce"
     ,
            COALESCE
     (c.repeatable
           , p.repeatable
     ) AS "coalesce"
     ,
            COALESCE
     (c.mandatory
           , p.mandatory
     ) AS "coalesce"
     ,
            COALESCE
     (c.hidden
           , p.hidden
     ) AS "coalesce"
     ,
            COALESCE
     (c.value_ctype
           , p.value_ctype
     ) AS "coalesce"
     ,
            c.owner
     ,
            
     (p.depth + 1)
           
  FROM (
           (config.marc_subfield c
             
              JOIN ou_marc_subfields p 
             USING (marc_format
                       , marc_record_type
                       , tag
                       , code
                 )
           )
             
        JOIN actor.org_unit aou 
          ON (
                 (c.owner = aou.id)
           )
     )
          
 WHERE (
           (aou.parent_ou = p.owner)
          OR (
                 (aou.parent_ou IS NULL)
               AND (p.owner IS NULL)
           )
     )
        
)
 
SELECT ou_marc_subfields.id
,
    ou_marc_subfields.marc_format
,
    ou_marc_subfields.marc_record_type
,
    ou_marc_subfields.tag
,
    ou_marc_subfields.code
,
    ou_marc_subfields.description
,
    ou_marc_subfields.repeatable
,
    ou_marc_subfields.mandatory
,
    ou_marc_subfields.hidden
,
    ou_marc_subfields.value_ctype
,
    ou_marc_subfields.owner
,
    ou_marc_subfields.depth
   
FROM ou_marc_subfields;

Index - Schema config


Table: config.metabib_class

config.metabib_class Structure
F-Key Name Type Description
name text PRIMARY KEY
label text UNIQUE NOT NULL
buoyant boolean NOT NULL DEFAULT false
restrict boolean NOT NULL DEFAULT false
combined boolean NOT NULL DEFAULT false
a_weight numeric NOT NULL DEFAULT 1.0
b_weight numeric NOT NULL DEFAULT 0.4
c_weight numeric NOT NULL DEFAULT 0.2
d_weight numeric NOT NULL DEFAULT 0.1

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.metabib_class_ts_map

Text Search Configs for metabib class indexing This table contains text search config definitions for storing index_vector values.

config.metabib_class_ts_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.metabib_class.name field_class text NOT NULL
config.ts_config_list.id ts_config text NOT NULL
active boolean NOT NULL DEFAULT true
index_weight character(1) NOT NULL DEFAULT 'C'::bpchar
index_lang text
search_lang text
always boolean NOT NULL DEFAULT true

 

config.metabib_class_ts_map Constraints
Name Constraint
metabib_class_ts_map_index_weight_check CHECK ((index_weight = ANY (ARRAY['A'::bpchar, 'B'::bpchar, 'C'::bpchar, 'D'::bpchar])))

Index - Schema config


Table: config.metabib_field

XPath used for record indexing ingest This table contains the XPath used to chop up MODS into its indexable parts. Each XPath entry is named and assigned to a "class" of either title, subject, author, keyword, series or identifier.

config.metabib_field Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.metabib_class.name field_class text NOT NULL
name text NOT NULL
label text NOT NULL
xpath text
weight integer NOT NULL DEFAULT 1
config.xml_transform.name format text NOT NULL DEFAULT 'mods33'::text
search_field boolean NOT NULL DEFAULT true
facet_field boolean NOT NULL DEFAULT false
browse_field boolean NOT NULL DEFAULT true
browse_nocase boolean NOT NULL DEFAULT false
browse_xpath text
browse_sort_xpath text
facet_xpath text
display_xpath text
authority_xpath text
joiner text
restrict boolean NOT NULL DEFAULT false
display_field boolean NOT NULL DEFAULT true

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.metabib_field_index_norm_map

config.metabib_field_index_norm_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.metabib_field.id field integer NOT NULL
config.index_normalizer.id norm integer NOT NULL
params text
pos integer NOT NULL

Index - Schema config


Table: config.metabib_field_ts_map

Text Search Configs for metabib field indexing This table contains text search config definitions for storing index_vector values.

config.metabib_field_ts_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.metabib_field.id metabib_field integer NOT NULL
config.ts_config_list.id ts_config text NOT NULL
active boolean NOT NULL DEFAULT true
index_weight character(1) NOT NULL DEFAULT 'C'::bpchar
index_lang text
search_lang text

 

config.metabib_field_ts_map Constraints
Name Constraint
metabib_field_ts_map_index_weight_check CHECK ((index_weight = ANY (ARRAY['A'::bpchar, 'B'::bpchar, 'C'::bpchar, 'D'::bpchar])))

Index - Schema config


Table: config.metabib_field_virtual_map

Maps between real (physically extracted) index definitions and virtual (target sync, no required extraction of its own) index definitions. The virtual side may not extract any data of its own, but will collect data from all of the real fields. This reduces extraction (ingest) overhead by eliminating duplcated extraction, and allows for searching across novel combinations of fields, such as names used as either subjects or authors. By preserving this mapping rather than defining duplicate extractions, information about the originating, "real" index definitions can be used in interesting ways, such as highlighting in search results.

config.metabib_field_virtual_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.metabib_field.id real integer NOT NULL
config.metabib_field.id virtual integer NOT NULL
weight integer NOT NULL DEFAULT 1

Index - Schema config


Table: config.metabib_search_alias

config.metabib_search_alias Structure
F-Key Name Type Description
alias text PRIMARY KEY
config.metabib_class.name field_class text NOT NULL
config.metabib_field.id field integer

Index - Schema config


Table: config.net_access_level

Patron Network Access level This will be used to inform the in-library firewall of how much internet access the using patron should be allowed.

config.net_access_level Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.non_cataloged_type

Types of valid non-cataloged items.

config.non_cataloged_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
owning_lib integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
circ_duration interval NOT NULL DEFAULT '14 days'::interval
in_house boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.openathens_identity

config.openathens_identity Structure
F-Key Name Type Description
id serial PRIMARY KEY
active boolean NOT NULL DEFAULT true
actor.org_unit.id org_unit integer NOT NULL
api_key text NOT NULL
connection_id text NOT NULL
connection_uri text NOT NULL
auto_signon_enabled boolean NOT NULL DEFAULT true
auto_signout_enabled boolean NOT NULL DEFAULT false
config.openathens_uid_field.id unique_identifier integer NOT NULL DEFAULT 1
config.openathens_name_field.id display_name integer NOT NULL DEFAULT 1
release_prefix boolean NOT NULL DEFAULT false
release_first_given_name boolean NOT NULL DEFAULT false
release_second_given_name boolean NOT NULL DEFAULT false
release_family_name boolean NOT NULL DEFAULT false
release_suffix boolean NOT NULL DEFAULT false
release_email boolean NOT NULL DEFAULT false
release_home_ou boolean NOT NULL DEFAULT false
release_barcode boolean NOT NULL DEFAULT false

Index - Schema config


Table: config.openathens_name_field

config.openathens_name_field Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.openathens_uid_field

config.openathens_uid_field Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.org_unit_setting_type

config.org_unit_setting_type Structure
F-Key Name Type Description
name text PRIMARY KEY
label text UNIQUE NOT NULL
config.settings_group.name grp text
description text
datatype text NOT NULL DEFAULT 'string'::text
fm_class text
permission.perm_list.id view_perm integer
permission.perm_list.id update_perm integer

 

config.org_unit_setting_type Constraints
Name Constraint
coust_no_empty_link CHECK ((((datatype = 'link'::text) AND (fm_class IS NOT NULL)) OR ((datatype <> 'link'::text) AND (fm_class IS NULL))))
coust_valid_datatype CHECK ((datatype = ANY (ARRAY['bool'::text, 'integer'::text, 'float'::text, 'currency'::text, 'interval'::text, 'date'::text, 'string'::text, 'object'::text, 'array'::text, 'link'::text])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.org_unit_setting_type_log

Org Unit setting Logs This table contains the most recent changes to each setting in actor.org_unit_setting, allowing for mistakes to be undone. This is NOT meant to be an auditor, but rather an undo/redo.

config.org_unit_setting_type_log Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
date_applied timestamp with time zone NOT NULL DEFAULT now()
actor.org_unit.id org integer
original_value text
new_value text
config.org_unit_setting_type.name field_name text

Index - Schema config


Table: config.print_template

config.print_template Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
label text UNIQUE#2 NOT NULL
actor.org_unit.id owner integer UNIQUE#2 UNIQUE#1 NOT NULL
active boolean NOT NULL DEFAULT false
config.i18n_locale.code locale text
content_type text NOT NULL DEFAULT 'text/html'::text
template text NOT NULL

Index - Schema config


Table: config.record_attr_definition

config.record_attr_definition Structure
F-Key Name Type Description
name text PRIMARY KEY
label text NOT NULL
description text
multi boolean NOT NULL DEFAULT true
filter boolean NOT NULL DEFAULT true
sorter boolean NOT NULL DEFAULT false
composite boolean NOT NULL DEFAULT false
tag text
sf_list text
joiner text
xpath text
config.xml_transform.name format text
start_pos integer
string_len integer
fixed_field text
config.marc21_physical_characteristic_subfield_map.id phys_char_sf integer
vocabulary text

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.record_attr_index_norm_map

config.record_attr_index_norm_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.record_attr_definition.name attr text NOT NULL
config.index_normalizer.id norm integer NOT NULL
params text
pos integer NOT NULL

Index - Schema config


Table: config.remote_account

config.remote_account Structure
F-Key Name Type Description
id serial PRIMARY KEY
label text NOT NULL
host text NOT NULL
username text
password text
account text
path text
actor.org_unit.id owner integer NOT NULL
last_activity timestamp with time zone

Index - Schema config


Table: config.remoteauth_profile

config.remoteauth_profile Structure
F-Key Name Type Description
name text PRIMARY KEY
description text
actor.org_unit.id context_org integer NOT NULL
enabled boolean NOT NULL DEFAULT false
permission.perm_list.id perm integer NOT NULL
restrict_to_org boolean NOT NULL DEFAULT true
allow_inactive boolean NOT NULL DEFAULT false
allow_expired boolean NOT NULL DEFAULT false
block_list text
config.usr_activity_type.id usr_activity_type integer

Index - Schema config


Table: config.rule_age_hold_protect

Hold Item Age Protection rules A hold request can only capture new(ish) items when they are within a particular proximity of the pickup_lib of the request. The proximity ('prox' column) is calculated by counting the number of tree edges between the pickup_lib and either the owning_lib or circ_lib of the copy that could fulfill the hold, as determined by the distance_is_from_owner value of the hold matrix rule controlling the hold request.

config.rule_age_hold_protect Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
age interval NOT NULL
prox integer NOT NULL

 

config.rule_age_hold_protect Constraints
Name Constraint
rule_age_hold_protect_name_check CHECK ((name ~ '^\w+$'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.rule_circ_duration

Circulation Duration rules Each circulation is given a duration based on one of these rules.

config.rule_circ_duration Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
extended interval NOT NULL
normal interval NOT NULL
shrt interval NOT NULL
max_renewals integer NOT NULL
max_auto_renewals integer

 

config.rule_circ_duration Constraints
Name Constraint
rule_circ_duration_name_check CHECK ((name ~ '^\w+$'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.rule_max_fine

Circulation Max Fine rules Each circulation is given a maximum fine based on one of these rules.

config.rule_max_fine Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
amount numeric(6,2) NOT NULL
is_percent boolean NOT NULL DEFAULT false

 

config.rule_max_fine Constraints
Name Constraint
rule_max_fine_name_check CHECK ((name ~ '^\w+$'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.rule_recurring_fine

Circulation Recurring Fine rules Each circulation is given a recurring fine amount based on one of these rules. Note that it is recommended to run the fine generator (from cron) at least as frequently as the lowest recurrence interval used by your circulation rules so that accrued fines will be up to date.

config.rule_recurring_fine Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
high numeric(6,2) NOT NULL
normal numeric(6,2) NOT NULL
low numeric(6,2) NOT NULL
recurrence_interval interval NOT NULL DEFAULT '1 day'::interval
grace_period interval NOT NULL DEFAULT '1 day'::interval

 

config.rule_recurring_fine Constraints
Name Constraint
rule_recurring_fine_name_check CHECK ((name ~ '^\w+$'::text))

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.settings_group

config.settings_group Structure
F-Key Name Type Description
name text PRIMARY KEY
label text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.sms_carrier

config.sms_carrier Structure
F-Key Name Type Description
id serial PRIMARY KEY
region text
name text
email_gateway text
active boolean DEFAULT true

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.standing

Patron Standings This table contains the values that can be applied to a patron by a staff member. These values should not be changed, other than for translation, as the ID column is currently a "magic number" in the source. :(

config.standing Structure
F-Key Name Type Description
id serial PRIMARY KEY
value text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.standing_penalty

config.standing_penalty Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
label text NOT NULL
block_list text
staff_alert boolean NOT NULL DEFAULT false
org_depth integer
ignore_proximity integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.ts_config_list

Full Text Configs A list of full text configs with names and descriptions.

config.ts_config_list Structure
F-Key Name Type Description
id text PRIMARY KEY
name text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.ui_staff_portal_page_entry

config.ui_staff_portal_page_entry Structure
F-Key Name Type Description
id serial PRIMARY KEY
page_col integer NOT NULL
col_pos integer NOT NULL
config.ui_staff_portal_page_entry_type.code entry_type text NOT NULL
label text
image_url text
target_url text
entry_text text
actor.org_unit.id owner integer NOT NULL

Index - Schema config


Table: config.ui_staff_portal_page_entry_type

config.ui_staff_portal_page_entry_type Structure
F-Key Name Type Description
code text PRIMARY KEY
label text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.upgrade_log

config.upgrade_log Structure
F-Key Name Type Description
version text PRIMARY KEY
install_date timestamp with time zone NOT NULL DEFAULT now()
applied_to text

Index - Schema config


Table: config.usr_activity_type

config.usr_activity_type Structure
F-Key Name Type Description
id serial PRIMARY KEY
ewho text
ewhat text
ehow text
label text NOT NULL
egroup config.usr_activity_group NOT NULL
enabled boolean NOT NULL DEFAULT true
transient boolean NOT NULL DEFAULT true

 

config.usr_activity_type Constraints
Name Constraint
one_of_wwh CHECK ((COALESCE(ewho, ewhat, ehow) IS NOT NULL))

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.usr_setting_type

config.usr_setting_type Structure
F-Key Name Type Description
name text PRIMARY KEY
opac_visible boolean NOT NULL DEFAULT false
label text UNIQUE NOT NULL
description text
config.settings_group.name grp text
datatype text NOT NULL DEFAULT 'string'::text
fm_class text
reg_default text

 

config.usr_setting_type Constraints
Name Constraint
coust_no_empty_link CHECK ((((datatype = 'link'::text) AND (fm_class IS NOT NULL)) OR ((datatype <> 'link'::text) AND (fm_class IS NULL))))
coust_valid_datatype CHECK ((datatype = ANY (ARRAY['bool'::text, 'integer'::text, 'float'::text, 'currency'::text, 'interval'::text, 'date'::text, 'string'::text, 'object'::text, 'array'::text, 'link'::text])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


View: config.videorecording_format_map

config.videorecording_format_map Structure
F-Key Name Type Description
code text
value text
SELECT coded_value_map.code
,
    coded_value_map.value
   
FROM config.coded_value_map
  
WHERE (coded_value_map.ctype = 'vr_format'::text);

Index - Schema config


Table: config.weight_assoc

config.weight_assoc Structure
F-Key Name Type Description
id serial PRIMARY KEY
active boolean NOT NULL
actor.org_unit.id org_unit integer NOT NULL
config.circ_matrix_weights.id circ_weights integer
config.hold_matrix_weights.id hold_weights integer

Index - Schema config


Table: config.workstation_setting_type

config.workstation_setting_type Structure
F-Key Name Type Description
name text PRIMARY KEY
label text UNIQUE NOT NULL
config.settings_group.name grp text
description text
datatype text NOT NULL DEFAULT 'string'::text
fm_class text

 

config.workstation_setting_type Constraints
Name Constraint
cwst_no_empty_link CHECK ((((datatype = 'link'::text) AND (fm_class IS NOT NULL)) OR ((datatype <> 'link'::text) AND (fm_class IS NULL))))
cwst_valid_datatype CHECK ((datatype = ANY (ARRAY['bool'::text, 'integer'::text, 'float'::text, 'currency'::text, 'interval'::text, 'date'::text, 'string'::text, 'object'::text, 'array'::text, 'link'::text])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.xml_transform

config.xml_transform Structure
F-Key Name Type Description
name text PRIMARY KEY
namespace_uri text NOT NULL
prefix text NOT NULL
xslt text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.z3950_attr

config.z3950_attr Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.z3950_source.name source text UNIQUE#1 NOT NULL
name text NOT NULL
label text NOT NULL
code integer UNIQUE#1 NOT NULL
format integer UNIQUE#1 NOT NULL
truncation integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.z3950_index_field_map

config.z3950_index_field_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
label text NOT NULL
config.metabib_field.id metabib_field integer
config.record_attr_definition.name record_attr text
config.z3950_attr.id z3950_attr integer
z3950_attr_type text

 

config.z3950_index_field_map Constraints
Name Constraint
attr_or_attr_type CHECK (((z3950_attr IS NOT NULL) OR (z3950_attr_type IS NOT NULL)))
metabib_field_or_record_attr CHECK (((metabib_field IS NOT NULL) OR (record_attr IS NOT NULL)))

Index - Schema config


Table: config.z3950_source

Z39.50 Sources Each row in this table represents a database searchable via Z39.50.

config.z3950_source Structure
F-Key Name Type Description
name text PRIMARY KEY
label text UNIQUE NOT NULL
host text NOT NULL
port integer NOT NULL
db text NOT NULL
record_format text NOT NULL DEFAULT 'FI'::text

Z39.50 element set.
transmission_format text NOT NULL DEFAULT 'usmarc'::text

Z39.50 preferred record syntax..
auth boolean NOT NULL DEFAULT true
permission.perm_list.id use_perm integer

If set, this permission is required for the source to be listed in the staff client Z39.50 interface. Similar to permission.grp_tree.application_perm.

Tables referencing this one via Foreign Key Constraints:

Index - Schema config


Table: config.z3950_source_credentials

config.z3950_source_credentials Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
config.z3950_source.name source text UNIQUE#1 NOT NULL
username text
password text

Index - Schema config


Function: config.interval_to_seconds(interval_string text)

Returns: integer

Language: PLPGSQL

BEGIN
	RETURN config.interval_to_seconds( interval_string::INTERVAL );
END;

Function: config.interval_to_seconds(interval_val interval)

Returns: integer

Language: PLPGSQL

BEGIN
	RETURN EXTRACT( EPOCH FROM interval_val );
END;

Function: config.ou_marc_fields(ou integer, marc_record_type config.marc_record_type, marc_format integer)

Returns: SET OF marc_field

Language: SQL

    SELECT id, marc_format, marc_record_type, tag, name, description, fixed_field, repeatable, mandatory, hidden, owner
    FROM (
        SELECT id, marc_format, marc_record_type, tag, name, description,
              fixed_field, repeatable, mandatory, hidden, owner, depth,
              MAX(depth) OVER (PARTITION BY marc_format, marc_record_type, tag) AS winner
        FROM config.marc_field_for_ou
        WHERE (owner IS NULL
               OR owner IN (SELECT id FROM actor.org_unit_ancestors($3)))
        AND   marc_format = $1
        AND   marc_record_type = $2
    ) AS s
    WHERE depth = winner
    AND not hidden;

Function: config.ou_marc_subfields(ou integer, marc_record_type config.marc_record_type, marc_format integer)

Returns: SET OF marc_subfield

Language: SQL

    SELECT id, marc_format, marc_record_type, tag, code, description, repeatable, mandatory,
           hidden, value_ctype, owner
    FROM (
        SELECT id, marc_format, marc_record_type, tag, code, description,
              repeatable, mandatory, hidden, value_ctype, owner, depth,
              MAX(depth) OVER (PARTITION BY marc_format, marc_record_type, tag, code) AS winner
        FROM config.marc_subfield_for_ou
        WHERE (owner IS NULL
               OR owner IN (SELECT id FROM actor.org_unit_ancestors($3)))
        AND   marc_format = $1
        AND   marc_record_type = $2
    ) AS s
    WHERE depth = winner
    AND not hidden;

Function: config.setting_is_user_or_ws()

Returns: trigger

Language: PLPGSQL

BEGIN

    IF TG_TABLE_NAME = 'usr_setting_type' THEN
        PERFORM TRUE FROM config.workstation_setting_type cwst
            WHERE cwst.name = NEW.name;
        IF NOT FOUND THEN
            RETURN NULL;
        END IF;
    END IF;

    IF TG_TABLE_NAME = 'workstation_setting_type' THEN
        PERFORM TRUE FROM config.usr_setting_type cust
            WHERE cust.name = NEW.name;
        IF NOT FOUND THEN
            RETURN NULL;
        END IF;
    END IF;

    RAISE EXCEPTION 
        '% Cannot be used as both a user setting and a workstation setting.', 
        NEW.name;
END;

Function: config.update_coded_value_map(add_only text, in_is_simple text, in_search_label text, in_opac_visible text, in_description boolean, in_value text, in_code boolean, in_ctype boolean)

Returns: void

Language: PLPGSQL

DECLARE
    current_row config.coded_value_map%ROWTYPE;
BEGIN
    -- Look for a current value
    SELECT INTO current_row * FROM config.coded_value_map WHERE ctype = in_ctype AND code = in_code;
    -- If we have one..
    IF FOUND AND NOT add_only THEN
        -- Update anything we were handed
        current_row.value := COALESCE(current_row.value, in_value);
        current_row.description := COALESCE(current_row.description, in_description);
        current_row.opac_visible := COALESCE(current_row.opac_visible, in_opac_visible);
        current_row.search_label := COALESCE(current_row.search_label, in_search_label);
        current_row.is_simple := COALESCE(current_row.is_simple, in_is_simple);
        UPDATE config.coded_value_map
            SET
                value = current_row.value,
                description = current_row.description,
                opac_visible = current_row.opac_visible,
                search_label = current_row.search_label,
                is_simple = current_row.is_simple
            WHERE id = current_row.id;
    ELSE
        INSERT INTO config.coded_value_map(ctype, code, value, description, opac_visible, search_label, is_simple) VALUES
            (in_ctype, in_code, in_value, in_description, COALESCE(in_opac_visible, TRUE), in_search_label, COALESCE(in_is_simple, FALSE));
    END IF;
END;

Function: config.update_hard_due_dates()

Returns: integer

Language: PLPGSQL

DECLARE
    temp_value  config.hard_due_date_values%ROWTYPE;
    updated     INT := 0;
BEGIN
    FOR temp_value IN
      SELECT  DISTINCT ON (hard_due_date) *
        FROM  config.hard_due_date_values
        WHERE active_date <= NOW() -- We've passed (or are at) the rollover time
        ORDER BY hard_due_date, active_date DESC -- Latest (nearest to us) active time
   LOOP
        UPDATE  config.hard_due_date
          SET   ceiling_date = temp_value.ceiling_date
          WHERE id = temp_value.hard_due_date
                AND ceiling_date <> temp_value.ceiling_date -- Time is equal if we've already updated the chdd
                AND temp_value.ceiling_date >= NOW(); -- Don't update ceiling dates to the past

        IF FOUND THEN
            updated := updated + 1;
        END IF;
    END LOOP;

    RETURN updated;
END;

Function: config.z3950_source_credentials_apply(passwd text, uname integer, org text, src text)

Returns: void

Language: PLPGSQL

BEGIN
    PERFORM 1 FROM config.z3950_source_credentials
        WHERE owner = org AND source = src;

    IF FOUND THEN
        IF COALESCE(uname, '') = '' AND COALESCE(passwd, '') = '' THEN
            DELETE FROM config.z3950_source_credentials 
                WHERE owner = org AND source = src;
        ELSE 
            UPDATE config.z3950_source_credentials 
                SET username = uname, password = passwd
                WHERE owner = org AND source = src;
        END IF;
    ELSE
        IF COALESCE(uname, '') <> '' OR COALESCE(passwd, '') <> '' THEN
            INSERT INTO config.z3950_source_credentials
                (source, owner, username, password) 
                VALUES (src, org, uname, passwd);
        END IF;
    END IF;
END;

Function: config.z3950_source_credentials_lookup(owner text, source integer)

Returns: z3950_source_credentials

Language: SQL


    SELECT creds.* 
    FROM config.z3950_source_credentials creds
        JOIN actor.org_unit aou ON (aou.id = creds.owner)
        JOIN actor.org_unit_type aout ON (aout.id = aou.ou_type)
    WHERE creds.source = $1 AND creds.owner IN ( 
        SELECT id FROM actor.org_unit_ancestors($2) 
    )
    ORDER BY aout.depth DESC LIMIT 1;


Schema container


Table: container.biblio_record_entry_bucket

container.biblio_record_entry_bucket Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
container.biblio_record_entry_bucket_type.code btype text UNIQUE#1 NOT NULL DEFAULT 'misc'::text
description text
pub boolean NOT NULL DEFAULT false
actor.org_unit.id owning_lib integer
create_time timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.biblio_record_entry_bucket_item

container.biblio_record_entry_bucket_item Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.biblio_record_entry_bucket.id bucket integer NOT NULL
biblio.record_entry.id target_biblio_record_entry bigint NOT NULL
pos integer
create_time timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.biblio_record_entry_bucket_item_note

container.biblio_record_entry_bucket_item_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.biblio_record_entry_bucket_item.id item integer NOT NULL
note text NOT NULL

Index - Schema container


Table: container.biblio_record_entry_bucket_note

container.biblio_record_entry_bucket_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.biblio_record_entry_bucket.id bucket integer NOT NULL
note text NOT NULL

Index - Schema container


Table: container.biblio_record_entry_bucket_type

container.biblio_record_entry_bucket_type Structure
F-Key Name Type Description
code text PRIMARY KEY
label text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.call_number_bucket

container.call_number_bucket Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
container.call_number_bucket_type.code btype text UNIQUE#1 NOT NULL DEFAULT 'misc'::text
description text
pub boolean NOT NULL DEFAULT false
actor.org_unit.id owning_lib integer
create_time timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.call_number_bucket_item

container.call_number_bucket_item Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.call_number_bucket.id bucket integer NOT NULL
asset.call_number.id target_call_number integer NOT NULL
pos integer
create_time timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.call_number_bucket_item_note

container.call_number_bucket_item_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.call_number_bucket_item.id item integer NOT NULL
note text NOT NULL

Index - Schema container


Table: container.call_number_bucket_note

container.call_number_bucket_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.call_number_bucket.id bucket integer NOT NULL
note text NOT NULL

Index - Schema container


Table: container.call_number_bucket_type

container.call_number_bucket_type Structure
F-Key Name Type Description
code text PRIMARY KEY
label text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.carousel

container.carousel Structure
F-Key Name Type Description
id serial PRIMARY KEY
config.carousel_type.id type integer NOT NULL
actor.org_unit.id owner integer NOT NULL
name text NOT NULL
container.biblio_record_entry_bucket.id bucket integer
actor.usr.id creator integer NOT NULL
actor.usr.id editor integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
edit_time timestamp with time zone NOT NULL DEFAULT now()
age_filter interval
owning_lib_filter integer[]
copy_location_filter integer[]
last_refresh_time timestamp with time zone
active boolean NOT NULL DEFAULT true
max_items integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.carousel_org_unit

container.carousel_org_unit Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.carousel.id carousel integer NOT NULL
override_name text
actor.org_unit.id org_unit integer NOT NULL
seq integer NOT NULL

Index - Schema container


Table: container.copy_bucket

container.copy_bucket Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
container.copy_bucket_type.code btype text UNIQUE#1 NOT NULL DEFAULT 'misc'::text
description text
pub boolean NOT NULL DEFAULT false
actor.org_unit.id owning_lib integer
create_time timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.copy_bucket_item

container.copy_bucket_item Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.copy_bucket.id bucket integer NOT NULL
target_copy integer NOT NULL
pos integer
create_time timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

copy_bucket_item_bucket_idx bucket

Index - Schema container


Table: container.copy_bucket_item_note

container.copy_bucket_item_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.copy_bucket_item.id item integer NOT NULL
note text NOT NULL

Index - Schema container


Table: container.copy_bucket_note

container.copy_bucket_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.copy_bucket.id bucket integer NOT NULL
note text NOT NULL

Index - Schema container


Table: container.copy_bucket_type

container.copy_bucket_type Structure
F-Key Name Type Description
code text PRIMARY KEY
label text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.user_bucket

container.user_bucket Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
container.user_bucket_type.code btype text UNIQUE#1 NOT NULL DEFAULT 'misc'::text
description text
pub boolean NOT NULL DEFAULT false
actor.org_unit.id owning_lib integer
create_time timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Table: container.user_bucket_item

container.user_bucket_item Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.user_bucket.id bucket integer NOT NULL
actor.usr.id target_user integer NOT NULL
pos integer
create_time timestamp with time zone NOT NULL DEFAULT now()

Tables referencing this one via Foreign Key Constraints:

user_bucket_item_target_user_idx target_user

Index - Schema container


Table: container.user_bucket_item_note

container.user_bucket_item_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.user_bucket_item.id item integer NOT NULL
note text NOT NULL

Index - Schema container


Table: container.user_bucket_note

container.user_bucket_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
container.user_bucket.id bucket integer NOT NULL
note text NOT NULL

Index - Schema container


Table: container.user_bucket_type

container.user_bucket_type Structure
F-Key Name Type Description
code text PRIMARY KEY
label text UNIQUE NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema container


Function: container.clear_all_expired_circ_history_items()

Returns: void

Language: PLPGSQL

Delete expired circulation bucket items for all users that have a setting for patron.max_reading_list_interval.

--
-- Delete expired circulation bucket items for all users that have
-- a setting for patron.max_reading_list_interval.
--
DECLARE
    today        TIMESTAMP WITH TIME ZONE;
    threshold    TIMESTAMP WITH TIME ZONE;
	usr_setting  RECORD;
BEGIN
	SELECT date_trunc( 'day', now() ) INTO today;
	--
	FOR usr_setting in
		SELECT
			usr,
			value
		FROM
			actor.usr_setting
		WHERE
			name = 'patron.max_reading_list_interval'
	LOOP
		--
		-- Make sure the setting is a valid interval
		--
		BEGIN
			threshold := today - CAST( translate( usr_setting.value, '"', '' ) AS INTERVAL );
		EXCEPTION
			WHEN OTHERS THEN
				RAISE NOTICE 'Invalid setting patron.max_reading_list_interval for user %: ''%''',
					usr_setting.usr, usr_setting.value;
				CONTINUE;
		END;
		--
		--RAISE NOTICE 'User % threshold %', usr_setting.usr, threshold;
		--
    	DELETE FROM container.copy_bucket_item
    	WHERE
        	bucket IN
        	(
        	    SELECT
        	        id
        	    FROM
        	        container.copy_bucket
        	    WHERE
        	        owner = usr_setting.usr
        	        AND btype = 'circ_history'
        	)
        	AND create_time < threshold;
	END LOOP;
	--
END;

Function: container.clear_expired_circ_history_items(ac_usr integer)

Returns: void

Language: PLPGSQL

Delete old circulation bucket items for a specified user. "Old" means older than the interval specified by a user-level setting, if it is so specified.

--
-- Delete old circulation bucket items for a specified user.
-- "Old" means older than the interval specified by a
-- user-level setting, if it is so specified.
--
DECLARE
    threshold TIMESTAMP WITH TIME ZONE;
BEGIN
	-- Sanity check
	IF ac_usr IS NULL THEN
		RETURN;
	END IF;
	-- Determine the threshold date that defines "old".  Subtract the
	-- interval from the system date, then truncate to midnight.
	SELECT
		date_trunc( 
			'day',
			now() - CAST( translate( value, '"', '' ) AS INTERVAL )
		)
	INTO
		threshold
	FROM
		actor.usr_setting
	WHERE
		usr = ac_usr
		AND name = 'patron.max_reading_list_interval';
	--
	IF threshold is null THEN
		-- No interval defined; don't delete anything
		-- RAISE NOTICE 'No interval defined for user %', ac_usr;
		return;
	END IF;
	--
	-- RAISE NOTICE 'Date threshold: %', threshold;
	--
	-- Threshold found; do the delete
	delete from container.copy_bucket_item
	where
		bucket in
		(
			select
				id
			from
				container.copy_bucket
			where
				owner = ac_usr
				and btype = 'circ_history'
		)
		and create_time < threshold;
	--
	RETURN;
END;

Schema evergreen


Function: evergreen.array_overlap_check()

Returns: trigger

Language: PLPGSQL

DECLARE
    fld     TEXT;
    cnt     INT;
BEGIN
    fld := TG_ARGV[0];
    EXECUTE 'SELECT COUNT(*) FROM '|| TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME ||' WHERE '|| fld ||' && ($1).'|| fld INTO cnt USING NEW;
    IF cnt > 0 THEN
        RAISE EXCEPTION 'Cannot insert duplicate array into field % of table %', fld, TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME;
    END IF;
    RETURN NEW;
END;

Function: evergreen.asset_copy_alert_copy_inh_fkey()

Returns: trigger

Language: PLPGSQL

BEGIN
        PERFORM 1 FROM asset.copy WHERE id = NEW.copy;
        IF NOT FOUND THEN
                RAISE foreign_key_violation USING MESSAGE = FORMAT(
                        $$Referenced asset.copy id not found, copy:%s$$, NEW.copy
                );
        END IF;
        RETURN NEW;
END;

Function: evergreen.asset_copy_inventory_copy_inh_fkey()

Returns: trigger

Language: PLPGSQL

BEGIN
        PERFORM 1 FROM asset.copy WHERE id = NEW.copy;
        IF NOT FOUND THEN
                RAISE foreign_key_violation USING MESSAGE = FORMAT(
                        $$Referenced asset.copy id not found, copy:%s$$, NEW.copy
                );
        END IF;
        RETURN NEW;
END;

Function: evergreen.asset_copy_note_owning_copy_inh_fkey()

Returns: trigger

Language: PLPGSQL

BEGIN
        PERFORM 1 FROM asset.copy WHERE id = NEW.owning_copy;
        IF NOT FOUND THEN
                RAISE foreign_key_violation USING MESSAGE = FORMAT(
                        $$Referenced asset.copy id not found, owning_copy:%s$$, NEW.owning_copy
                );
        END IF;
        RETURN NEW;
END;

Function: evergreen.asset_copy_tag_copy_map_copy_inh_fkey()

Returns: trigger

Language: PLPGSQL

BEGIN
        PERFORM 1 FROM asset.copy WHERE id = NEW.copy;
        IF NOT FOUND THEN
                RAISE foreign_key_violation USING MESSAGE = FORMAT(
                        $$Referenced asset.copy id not found, copy:%s$$, NEW.copy
                );
        END IF;
        RETURN NEW;
END;

Function: evergreen.can_float(to_ou integer, from_ou integer, copy_floating_group integer)

Returns: boolean

Language: PLPGSQL

DECLARE
    float_member config.floating_group_member%ROWTYPE;
    shared_ou_depth INT;
    to_ou_depth INT;
BEGIN
    -- Grab the shared OU depth. If this is less than the stop depth later we ignore the entry.
    SELECT INTO shared_ou_depth max(depth) FROM actor.org_unit_common_ancestors( from_ou, to_ou ) aou JOIN actor.org_unit_type aout ON aou.ou_type = aout.id;
    -- Grab the to ou depth. If this is greater than max depth we ignore the entry.
    SELECT INTO to_ou_depth depth FROM actor.org_unit aou JOIN actor.org_unit_type aout ON aou.ou_type = aout.id WHERE aou.id = to_ou;
    -- Grab float members that apply. We don't care what we get beyond wanting excluded ones first.
    SELECT INTO float_member *
        FROM
            config.floating_group_member cfgm
            JOIN actor.org_unit aou ON cfgm.org_unit = aou.id
            JOIN actor.org_unit_type aout ON aou.ou_type = aout.id
        WHERE
            cfgm.floating_group = copy_floating_group
            AND to_ou IN (SELECT id FROM actor.org_unit_descendants(aou.id))
            AND cfgm.stop_depth <= shared_ou_depth
            AND (cfgm.max_depth IS NULL OR to_ou_depth <= max_depth)
        ORDER BY
            exclude DESC;
    -- If we found something then we want to return the opposite of the exclude flag
    IF FOUND THEN
        RETURN NOT float_member.exclude;
    END IF;
    -- Otherwise no floating.
    RETURN false;
END;

Function: evergreen.change_db_setting(settings text, setting_name text[])

Returns: void

Language: PLPGSQL

BEGIN
    EXECUTE 'ALTER DATABASE ' || quote_ident(current_database()) || ' SET ' || quote_ident(setting_name) || ' = ' || array_to_string(settings, ',');
END;

Function: evergreen.coded_value_map_normalizer(ctype text, input text)

Returns: text

Language: SQL

        SELECT COALESCE(value,$1) 
            FROM config.coded_value_map 
            WHERE ctype = $2 AND code = $1;

Function: evergreen.container_copy_bucket_item_target_copy_inh_fkey()

Returns: trigger

Language: PLPGSQL

BEGIN
        PERFORM 1 FROM asset.copy WHERE id = NEW.target_copy;
        IF NOT FOUND THEN
                RAISE foreign_key_violation USING MESSAGE = FORMAT(
                        $$Referenced asset.copy id not found, target_copy:%s$$, NEW.target_copy
                );
        END IF;
        RETURN NEW;
END;

Function: evergreen.could_be_serial_holding_code(text)

Returns: boolean

Language: PLPERLU

Return true if parameter is valid JSON representing an array that at minimum doesn't make MARC::Field balk and only has subfield labels exactly one character long. Otherwise false.

    use JSON::XS;
    use MARC::Field;

    eval {
        my $holding_code = (new JSON::XS)->decode(shift);
        new MARC::Field('999', @$holding_code);
    };
    return 0 if $@;
    # verify that subfield labels are exactly one character long
    foreach (keys %{ { @$holding_code } }) {
        return 0 if length($_) != 1;
    }
    return 1;

Function: evergreen.display_field_force_nfc()

Returns: trigger

Language: PLPGSQL

BEGIN
    NEW.value := force_unicode_normal_form(NEW.value,'NFC');
    RETURN NEW;
END;

Function: evergreen.escape_for_html(text)

Returns: text

Language: SQL

    SELECT  regexp_replace(
                regexp_replace(
                    regexp_replace(
                        $1,
                        '&',
                        '&amp;',
                        'g'
                    ),
                    '<',
                    '&lt;',
                    'g'
                ),
                '>',
                '&gt;',
                'g'
            );

Function: evergreen.extract_marc_field(text, bigint, text)

Returns: text

Language: SQL

    SELECT extract_marc_field($1,$2,$3,'');

Function: evergreen.extract_marc_field(text, bigint, text, text)

Returns: text

Language: PLPGSQL

DECLARE
    query TEXT;
    output TEXT;
BEGIN
    query := $q$
        SELECT  regexp_replace(
                    oils_xpath_string(
                        $q$ || quote_literal($3) || $q$,
                        marc,
                        ' '
                    ),
                    $q$ || quote_literal($4) || $q$,
                    '',
                    'g')
          FROM  $q$ || $1 || $q$
          WHERE id = $q$ || $2;

    EXECUTE query INTO output;

    -- RAISE NOTICE 'query: %, output; %', query, output;

    RETURN output;
END;

Function: evergreen.extract_marc_field_set(text, bigint, text, text)

Returns: SET OF text

Language: PLPGSQL

DECLARE
    query TEXT;
    output TEXT;
BEGIN
    FOR output IN
        SELECT x.t FROM (
            SELECT id,t
                FROM  oils_xpath_table(
                    'id', 'marc', $1, $3, 'id = ' || $2)
                AS t(id int, t text))x
        LOOP
        IF $4 IS NOT NULL THEN
            SELECT INTO output (SELECT regexp_replace(output, $4, '', 'g'));
        END IF;
        RETURN NEXT output;
    END LOOP;
    RETURN;
END;

Function: evergreen.facet_force_nfc()

Returns: trigger

Language: PLPGSQL

BEGIN
    NEW.value := force_unicode_normal_form(NEW.value,'NFC');
    RETURN NEW;
END;

Function: evergreen.fake_fkey_tgr()

Returns: trigger

Language: PLPGSQL

DECLARE
    copy_id BIGINT;
BEGIN
    EXECUTE 'SELECT ($1).' || quote_ident(TG_ARGV[0]) INTO copy_id USING NEW;
    IF copy_id IS NOT NULL THEN
        PERFORM * FROM asset.copy WHERE id = copy_id;
        IF NOT FOUND THEN
            RAISE EXCEPTION 'Key (%.%=%) does not exist in asset.copy', TG_TABLE_SCHEMA, TG_TABLE_NAME, copy_id;
        END IF;
    END IF;
    RETURN NULL;
END;

Function: evergreen.find_next_open_time(dow_count integer, initial_time timestamp with time zone, hourly boolean, initial time without time zone, circ_lib integer)

Returns: timestamp with time zone

Language: PLPGSQL

DECLARE
    day_number      INT;
    plus_days       INT;
    final_time      TEXT;
    time_adjusted   BOOL;
    hoo_open        TIME WITHOUT TIME ZONE;
    hoo_close       TIME WITHOUT TIME ZONE;
    adjacent        actor.org_unit_closed%ROWTYPE;
    breakout        INT := 0;
BEGIN

    IF dow_count > 6 THEN
        RETURN initial;
    END IF;

    IF initial_time IS NULL THEN
        initial_time := initial::TIME;
    END IF;

    final_time := (initial + '1 second'::INTERVAL)::TEXT;
    LOOP
        breakout := breakout + 1;

        time_adjusted := FALSE;

        IF dow_count > 0 THEN -- we're recursing, so check for HOO closing
            day_number := EXTRACT(ISODOW FROM final_time::TIMESTAMPTZ) - 1;
            plus_days := 0;
            FOR i IN 1..7 LOOP
                EXECUTE 'SELECT dow_' || day_number || '_open, dow_' || day_number || '_close FROM actor.hours_of_operation WHERE id = $1'
                    INTO hoo_open, hoo_close
                    USING circ_lib;

                -- RAISE NOTICE 'initial time: %; dow: %; close: %',initial_time,day_number,hoo_close;

                IF hoo_close = '00:00:00' THEN -- bah ... I guess we'll check the next day
                    day_number := (day_number + 1) % 7;
                    plus_days := plus_days + 1;
                    time_adjusted := TRUE;
                    CONTINUE;
                END IF;

                IF hoo_close IS NULL THEN -- no hours of operation ... assume no closing?
                    hoo_close := '23:59:59';
                END IF;

                EXIT;
            END LOOP;

            final_time := DATE(final_time::TIMESTAMPTZ + (plus_days || ' days')::INTERVAL)::TEXT;
            IF hoo_close <> '00:00:00' AND hourly THEN -- Not a day-granular circ
                final_time := final_time||' '|| hoo_close;
            ELSE
                final_time := final_time||' 23:59:59';
            END IF;
        END IF;

        -- Loop through other closings
        LOOP
            SELECT * INTO adjacent FROM actor.org_unit_closed WHERE org_unit = circ_lib AND final_time::TIMESTAMPTZ between close_start AND close_end;
            EXIT WHEN adjacent.id IS NULL;
            time_adjusted := TRUE;
            -- RAISE NOTICE 'recursing for closings with final_time: %',final_time;
            final_time := evergreen.find_next_open_time(circ_lib, adjacent.close_end::TIMESTAMPTZ, hourly, initial_time, dow_count + 1)::TEXT;
        END LOOP;

        EXIT WHEN breakout > 100;
        EXIT WHEN NOT time_adjusted;

    END LOOP;

    RETURN final_time;
END;

Function: evergreen.force_unicode_normal_form(form text, string text)

Returns: text

Language: PLPERLU

use Unicode::Normalize 'normalize';
return normalize($_[1],$_[0]); # reverse the params

Function: evergreen.generic_map_normalizer(text, text)

Returns: text

Language: PLPERLU

my $string = shift;
my %map;

my $default = $string;

$_ = shift;
while (/^\s*?(.*?)\s*?=>\s*?(\S+)\s*/) {
    if ($1 eq '') {
        $default = $2;
    } else {
        $map{$2} = [split(/\s*,\s*/, $1)];
    }
    $_ = $';
}

for my $key ( keys %map ) {
    return $key if (grep { $_ eq $string } @{ $map{$key} });
}

return $default;


Function: evergreen.get_barcodes(in_barcode integer, type text, select_ou text)

Returns: SET OF barcode_set

Language: PLPGSQL

Given user input, find an appropriate barcode in the proper class. Will add prefix/suffix information to do so, and return all results.

DECLARE
    cur_barcode TEXT;
    barcode_len INT;
    completion_len  INT;
    asset_barcodes  TEXT[];
    actor_barcodes  TEXT[];
    do_asset    BOOL = false;
    do_serial   BOOL = false;
    do_booking  BOOL = false;
    do_actor    BOOL = false;
    completion_set  config.barcode_completion%ROWTYPE;
BEGIN

    IF position('asset' in type) > 0 THEN
        do_asset = true;
    END IF;
    IF position('serial' in type) > 0 THEN
        do_serial = true;
    END IF;
    IF position('booking' in type) > 0 THEN
        do_booking = true;
    END IF;
    IF do_asset OR do_serial OR do_booking THEN
        asset_barcodes = asset_barcodes || in_barcode;
    END IF;
    IF position('actor' in type) > 0 THEN
        do_actor = true;
        actor_barcodes = actor_barcodes || in_barcode;
    END IF;

    barcode_len := length(in_barcode);

    FOR completion_set IN
      SELECT * FROM config.barcode_completion
        WHERE active
        AND org_unit IN (SELECT aou.id FROM actor.org_unit_ancestors(select_ou) aou)
        LOOP
        IF completion_set.prefix IS NULL THEN
            completion_set.prefix := '';
        END IF;
        IF completion_set.suffix IS NULL THEN
            completion_set.suffix := '';
        END IF;
        IF completion_set.length = 0 OR completion_set.padding IS NULL OR length(completion_set.padding) = 0 THEN
            cur_barcode = completion_set.prefix || in_barcode || completion_set.suffix;
        ELSE
            completion_len = completion_set.length - length(completion_set.prefix) - length(completion_set.suffix);
            IF completion_len >= barcode_len THEN
                IF completion_set.padding_end THEN
                    cur_barcode = rpad(in_barcode, completion_len, completion_set.padding);
                ELSE
                    cur_barcode = lpad(in_barcode, completion_len, completion_set.padding);
                END IF;
                cur_barcode = completion_set.prefix || cur_barcode || completion_set.suffix;
            END IF;
        END IF;
        IF completion_set.actor THEN
            actor_barcodes = actor_barcodes || cur_barcode;
        END IF;
        IF completion_set.asset THEN
            asset_barcodes = asset_barcodes || cur_barcode;
        END IF;
    END LOOP;

    IF do_asset AND do_serial THEN
        RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM ONLY asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false;
        RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false;
    ELSIF do_asset THEN
        RETURN QUERY SELECT 'asset'::TEXT, id, barcode FROM asset.copy WHERE barcode = ANY(asset_barcodes) AND deleted = false;
    ELSIF do_serial THEN
        RETURN QUERY SELECT 'serial'::TEXT, id, barcode FROM serial.unit WHERE barcode = ANY(asset_barcodes) AND deleted = false;
    END IF;
    IF do_booking THEN
        RETURN QUERY SELECT 'booking'::TEXT, id::BIGINT, barcode FROM booking.resource WHERE barcode = ANY(asset_barcodes);
    END IF;
    IF do_actor THEN
        RETURN QUERY SELECT 'actor'::TEXT, c.usr::BIGINT, c.barcode FROM actor.card c JOIN actor.usr u ON c.usr = u.id WHERE
            ((c.barcode = ANY(actor_barcodes) AND c.active) OR c.barcode = in_barcode) AND NOT u.deleted ORDER BY usr;
    END IF;
    RETURN;
END;

Function: evergreen.get_locale_name(description text)

Returns: record

Language: PLPGSQL

DECLARE
    eg_locale TEXT;
BEGIN
    eg_locale := LOWER(SUBSTRING(locale FROM 1 FOR 2)) || '-' || UPPER(SUBSTRING(locale FROM 4 FOR 2));
        
    SELECT i18nc.string INTO name
    FROM config.i18n_locale i18nl
       INNER JOIN config.i18n_core i18nc ON i18nl.code = i18nc.translation
    WHERE i18nc.identity_value = eg_locale
       AND code = eg_locale
       AND i18nc.fq_field = 'i18n_l.name';

    IF name IS NULL THEN
       SELECT i18nl.name INTO name
       FROM config.i18n_locale i18nl
       WHERE code = eg_locale;
    END IF;

    SELECT i18nc.string INTO description
    FROM config.i18n_locale i18nl
       INNER JOIN config.i18n_core i18nc ON i18nl.code = i18nc.translation
    WHERE i18nc.identity_value = eg_locale
       AND code = eg_locale
       AND i18nc.fq_field = 'i18n_l.description';

    IF description IS NULL THEN
       SELECT i18nl.description INTO description
       FROM config.i18n_locale i18nl
       WHERE code = eg_locale;
    END IF;
END;

Function: evergreen.is_json(text)

Returns: boolean

Language: PLPERLU

    use JSON::XS;
    my $json = shift();
    eval { JSON::XS->new->allow_nonref->decode( $json ) };
    return $@ ? 0 : 1;

Function: evergreen.levenshtein_damerau_edistance( text, b text, a integer)

Returns: numeric

Language: PLPERLU

use Text::Levenshtein::Damerau::XS qw/xs_edistance/;
return xs_edistance(@_);

Function: evergreen.limit_oustl()

Returns: trigger

Language: PLPGSQL

    BEGIN
        -- Only keeps the most recent five settings changes.
        DELETE FROM config.org_unit_setting_type_log WHERE field_name = NEW.field_name AND org = NEW.org AND date_applied NOT IN 
        (SELECT date_applied FROM config.org_unit_setting_type_log WHERE field_name = NEW.field_name AND org = NEW.org ORDER BY date_applied DESC LIMIT 4);
        
        IF (TG_OP = 'UPDATE') THEN
            RETURN NEW;
        ELSIF (TG_OP = 'INSERT') THEN
            RETURN NEW;
        END IF;
        RETURN NULL;
    END;

Function: evergreen.located_uris(rank bigint, label_sortkey integer, name integer)

Returns: SET OF record

Language: SQL

 SELECT * FROM evergreen.located_uris(ARRAY[$1],$2,$3) 

Function: evergreen.located_uris(rank bigint[], label_sortkey integer, name integer)

Returns: SET OF record

Language: SQL

    WITH all_orgs AS (SELECT COALESCE( enabled, FALSE ) AS flag FROM config.global_flag WHERE name = 'opac.located_uri.act_as_copy')
    SELECT DISTINCT ON (id) * FROM (
    SELECT acn.id, COALESCE(aou.name,aoud.name), acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
      FROM asset.call_number acn
           INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number
           INNER JOIN asset.uri auri ON auri.id = auricnm.uri
           LEFT JOIN actor.org_unit_ancestors( COALESCE($3, $2) ) aou ON (acn.owning_lib = aou.id)
           LEFT JOIN actor.org_unit_descendants( COALESCE($3, $2) ) aoud ON (acn.owning_lib = aoud.id),
           all_orgs
      WHERE acn.record = ANY ($1)
          AND acn.deleted IS FALSE
          AND auri.active IS TRUE
          AND ((NOT all_orgs.flag AND aou.id IS NOT NULL) OR (all_orgs.flag AND COALESCE(aou.id,aoud.id) IS NOT NULL))
    UNION
    SELECT acn.id, COALESCE(aou.name,aoud.name) AS name, acn.label_sortkey, evergreen.rank_ou(aou.id, $2, $3) AS pref_ou
      FROM asset.call_number acn
           INNER JOIN asset.uri_call_number_map auricnm ON acn.id = auricnm.call_number
           INNER JOIN asset.uri auri ON auri.id = auricnm.uri
           LEFT JOIN actor.org_unit_ancestors( $2 ) aou ON (acn.owning_lib = aou.id)
           LEFT JOIN actor.org_unit_descendants( $2 ) aoud ON (acn.owning_lib = aoud.id),
           all_orgs
      WHERE acn.record = ANY ($1)
          AND acn.deleted IS FALSE
          AND auri.active IS TRUE
          AND ((NOT all_orgs.flag AND aou.id IS NOT NULL) OR (all_orgs.flag AND COALESCE(aou.id,aoud.id) IS NOT NULL)))x
    ORDER BY id, pref_ou DESC;

Function: evergreen.located_uris_as_uris(pref_lib bigint, ouid integer, bibid integer)

Returns: SET OF uri

Language: SQL

    /* Maps a bib directly to its scoped asset.uri's */

    SELECT uri.* 
    FROM evergreen.located_uris($1, $2, $3) located_uri
    JOIN asset.uri_call_number_map map ON (map.call_number = located_uri.id)
    JOIN asset.uri uri ON (uri.id = map.uri)

Function: evergreen.lowercase(text)

Returns: text

Language: PLPERLU

    return lc(shift);

Function: evergreen.lpad_number_substrings(text, text, integer)

Returns: text

Language: PLPERLU

    my $string = shift;            # Source string
    my $pad = shift;               # string to fill. Typically '0'. This should be a single character.
    my $len = shift;               # length of resultant padded field

    $string =~ s/([0-9]+)/$pad x ($len - length($1)) . $1/eg;

    return $string;

Function: evergreen.maintain_901()

Returns: trigger

Language: PLPERLU

use strict;
use MARC::Record;
use MARC::File::XML (BinaryEncoding => 'UTF-8');
use MARC::Charset;
use Encode;
use Unicode::Normalize;

MARC::Charset->assume_unicode(1);

my $schema = $_TD->{table_schema};
my $marc = MARC::Record->new_from_xml($_TD->{new}{marc});

my @old901s = $marc->field('901');
$marc->delete_fields(@old901s);

if ($schema eq 'biblio') {
    my $tcn_value = $_TD->{new}{tcn_value};

    # Set TCN value to record ID?
    my $id_as_tcn = spi_exec_query("
        SELECT enabled
        FROM config.global_flag
        WHERE name = 'cat.bib.use_id_for_tcn'
    ");
    if (($id_as_tcn->{processed}) && $id_as_tcn->{rows}[0]->{enabled} eq 't') {
        $tcn_value = $_TD->{new}{id}; 
        $_TD->{new}{tcn_value} = $tcn_value;
    }

    my $new_901 = MARC::Field->new("901", " ", " ",
        "a" => $tcn_value,
        "b" => $_TD->{new}{tcn_source},
        "c" => $_TD->{new}{id},
        "t" => $schema
    );

    if ($_TD->{new}{owner}) {
        $new_901->add_subfields("o" => $_TD->{new}{owner});
    }

    if ($_TD->{new}{share_depth}) {
        $new_901->add_subfields("d" => $_TD->{new}{share_depth});
    }

    if ($_TD->{new}{source}) {
        my $plan = spi_prepare('
            SELECT source
            FROM config.bib_source
            WHERE id = $1
        ', 'INTEGER');
        my $source_name =
            spi_exec_prepared($plan, {limit => 1}, $_TD->{new}{source})->{rows}[0]{source};
        spi_freeplan($plan);
        $new_901->add_subfields("s" => $source_name) if $source_name;
    }

    $marc->append_fields($new_901);
} elsif ($schema eq 'authority') {
    my $new_901 = MARC::Field->new("901", " ", " ",
        "c" => $_TD->{new}{id},
        "t" => $schema,
    );
    $marc->append_fields($new_901);
} elsif ($schema eq 'serial') {
    my $new_901 = MARC::Field->new("901", " ", " ",
        "c" => $_TD->{new}{id},
        "t" => $schema,
        "o" => $_TD->{new}{owning_lib},
    );

    if ($_TD->{new}{record}) {
        $new_901->add_subfields("r" => $_TD->{new}{record});
    }

    $marc->append_fields($new_901);
} else {
    my $new_901 = MARC::Field->new("901", " ", " ",
        "c" => $_TD->{new}{id},
        "t" => $schema,
    );
    $marc->append_fields($new_901);
}

my $xml = $marc->as_xml_record();
$xml =~ s/\n//sgo;
$xml =~ s/^<\?xml.+\?\s*>//go;
$xml =~ s/>\s+</></go;
$xml =~ s/\p{Cc}//go;

# Embed a version of OpenILS::Application::AppUtils->entityize()
# to avoid having to set PERL5LIB for PostgreSQL as well

$xml = NFC($xml);

# Convert raw ampersands to entities
$xml =~ s/&(?!\S+;)/&amp;/gso;

# Convert Unicode characters to entities
$xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;

$xml =~ s/[\x00-\x1f]//go;
$_TD->{new}{marc} = $xml;

return "MODIFY";

Function: evergreen.maintain_control_numbers()

Returns: trigger

Language: PLPERLU

use strict;
use MARC::Record;
use MARC::File::XML (BinaryEncoding => 'UTF-8');
use MARC::Charset;
use Encode;
use Unicode::Normalize;

MARC::Charset->assume_unicode(1);

my $record = MARC::Record->new_from_xml($_TD->{new}{marc});
my $schema = $_TD->{table_schema};
my $rec_id = $_TD->{new}{id};

# Short-circuit if maintaining control numbers per MARC21 spec is not enabled
my $enable = spi_exec_query("SELECT enabled FROM config.global_flag WHERE name = 'cat.maintain_control_numbers'");
if (!($enable->{processed}) or $enable->{rows}[0]->{enabled} eq 'f') {
    return;
}

# Get the control number identifier from an OU setting based on $_TD->{new}{owner}
my $ou_cni = 'EVRGRN';

my $owner;
if ($schema eq 'serial') {
    $owner = $_TD->{new}{owning_lib};
} else {
    # are.owner and bre.owner can be null, so fall back to the consortial setting
    $owner = $_TD->{new}{owner} || 1;
}

my $ous_rv = spi_exec_query("SELECT value FROM actor.org_unit_ancestor_setting('cat.marc_control_number_identifier', $owner)");
if ($ous_rv->{processed}) {
    $ou_cni = $ous_rv->{rows}[0]->{value};
    $ou_cni =~ s/"//g; # Stupid VIM syntax highlighting"
} else {
    # Fall back to the shortname of the OU if there was no OU setting
    $ous_rv = spi_exec_query("SELECT shortname FROM actor.org_unit WHERE id = $owner");
    if ($ous_rv->{processed}) {
        $ou_cni = $ous_rv->{rows}[0]->{shortname};
    }
}

my ($create, $munge) = (0, 0);

my @scns = $record->field('035');

foreach my $id_field ('001', '003') {
    my $spec_value;
    my @controls = $record->field($id_field);

    if ($id_field eq '001') {
        $spec_value = $rec_id;
    } else {
        $spec_value = $ou_cni;
    }

    # Create the 001/003 if none exist
    if (scalar(@controls) == 1) {
        # Only one field; check to see if we need to munge it
        unless (grep $_->data() eq $spec_value, @controls) {
            $munge = 1;
        }
    } else {
        # Delete the other fields, as with more than 1 001/003 we do not know which 003/001 to match
        foreach my $control (@controls) {
            $record->delete_field($control);
        }
        $record->insert_fields_ordered(MARC::Field->new($id_field, $spec_value));
        $create = 1;
    }
}

my $cn = $record->field('001')->data();
# Special handling of OCLC numbers, often found in records that lack 003
if ($cn =~ /^o(c[nm]|n)\d/) {
    $cn =~ s/^o(c[nm]|n)0*(\d+)/$2/;
    $record->field('003')->data('OCoLC');
    $create = 0;
}

# Now, if we need to munge the 001, we will first push the existing 001/003
# into the 035; but if the record did not have one (and one only) 001 and 003
# to begin with, skip this process
if ($munge and not $create) {

    my $scn = "(" . $record->field('003')->data() . ")" . $cn;

    # Do not create duplicate 035 fields
    unless (grep $_->subfield('a') eq $scn, @scns) {
        $record->insert_fields_ordered(MARC::Field->new('035', '', '', 'a' => $scn));
    }
}

# Set the 001/003 and update the MARC
if ($create or $munge) {
    $record->field('001')->data($rec_id);
    $record->field('003')->data($ou_cni);

    my $xml = $record->as_xml_record();
    $xml =~ s/\n//sgo;
    $xml =~ s/^<\?xml.+\?\s*>//go;
    $xml =~ s/>\s+</></go;
    $xml =~ s/\p{Cc}//go;

    # Embed a version of OpenILS::Application::AppUtils->entityize()
    # to avoid having to set PERL5LIB for PostgreSQL as well

    $xml = NFC($xml);

    # Convert raw ampersands to entities
    $xml =~ s/&(?!\S+;)/&amp;/gso;

    # Convert Unicode characters to entities
    $xml =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;

    $xml =~ s/[\x00-\x1f]//go;
    $_TD->{new}{marc} = $xml;

    return "MODIFY";
}

return;

Function: evergreen.marc_to(xfrm text, marc text)

Returns: text

Language: SQL

    SELECT evergreen.xml_pretty_print(xslt_process($1,xslt)::XML)::TEXT FROM config.xml_transform WHERE name = $2;

Function: evergreen.oils_i18n_code_tracking()

Returns: trigger

Language: PLPGSQL

BEGIN
    PERFORM oils_i18n_update_apply( OLD.code::TEXT, NEW.code::TEXT, TG_ARGV[0]::TEXT );
    RETURN NEW;
END;

Function: evergreen.oils_i18n_gettext(integer, text, text, text)

Returns: text

Language: SQL

    SELECT $2;

Function: evergreen.oils_i18n_gettext(text, text, text, text)

Returns: text

Language: SQL

    SELECT $2;

Function: evergreen.oils_i18n_id_tracking()

Returns: trigger

Language: PLPGSQL

BEGIN
    PERFORM oils_i18n_update_apply( OLD.id::TEXT, NEW.id::TEXT, TG_ARGV[0]::TEXT );
    RETURN NEW;
END;

Function: evergreen.oils_i18n_update_apply(hint text, new_ident text, old_ident text)

Returns: void

Language: PLPGSQL

BEGIN

    EXECUTE $$
        UPDATE  config.i18n_core
          SET   identity_value = $$ || quote_literal(new_ident) || $$ 
          WHERE fq_field LIKE '$$ || hint || $$.%' 
                AND identity_value = $$ || quote_literal(old_ident) || $$::TEXT;$$;

    RETURN;

END;

Function: evergreen.oils_i18n_xlate(raw_locale text, keyvalue text, identcol text, keycol text, keyclass text, keytable text)

Returns: text

Language: PLPGSQL

DECLARE
    locale      TEXT := REGEXP_REPLACE( REGEXP_REPLACE( raw_locale, E'[;, ].+$', '' ), E'_', '-', 'g' );
    language    TEXT := REGEXP_REPLACE( locale, E'-.+$', '' );
    result      config.i18n_core%ROWTYPE;
    fallback    TEXT;
    keyfield    TEXT := keyclass || '.' || keycol;
BEGIN

    -- Try the full locale
    SELECT  * INTO result
      FROM  config.i18n_core
      WHERE fq_field = keyfield
            AND identity_value = keyvalue
            AND translation = locale;

    -- Try just the language
    IF NOT FOUND THEN
        SELECT  * INTO result
          FROM  config.i18n_core
          WHERE fq_field = keyfield
                AND identity_value = keyvalue
                AND translation = language;
    END IF;

    -- Fall back to the string we passed in in the first place
    IF NOT FOUND THEN
	EXECUTE
            'SELECT ' ||
                keycol ||
            ' FROM ' || keytable ||
            ' WHERE ' || identcol || ' = ' || quote_literal(keyvalue)
                INTO fallback;
        RETURN fallback;
    END IF;

    RETURN result.string;
END;

Function: evergreen.oils_json_to_text(text)

Returns: text

Language: PLPERLU

    use JSON::XS;
    my $json = shift();
    my $txt;
    eval { $txt = JSON::XS->new->allow_nonref->decode( $json ) };
    return undef if ($@);
    return $txt

Function: evergreen.oils_text_as_bytea(text)

Returns: bytea

Language: SQL

    SELECT CAST(REGEXP_REPLACE(UPPER($1), $$\\$$, $$\\\\$$, 'g') AS BYTEA);

Function: evergreen.oils_xpath(text, text)

Returns: text[]

Language: SQL

    SELECT  ARRAY_AGG(
                CASE WHEN strpos(x,'<') = 1 THEN -- It's an element node
                    x
                ELSE -- it's text-ish
                    evergreen.xml_famous5_to_text(x)
                END
            )
      FROM  UNNEST(XPATH( $1, $2::XML)::TEXT[]) x;

Function: evergreen.oils_xpath(text, text, text[])

Returns: text[]

Language: SQL

    SELECT  ARRAY_AGG(
                CASE WHEN strpos(x,'<') = 1 THEN -- It's an element node
                    x
                ELSE -- it's text-ish
                    evergreen.xml_famous5_to_text(x)
                END
            )
      FROM  UNNEST(XPATH( $1, $2::XML, $3 )::TEXT[]) x;

Function: evergreen.oils_xpath_string(text, text)

Returns: text

Language: SQL

    SELECT oils_xpath_string( $1, $2, '{}'::TEXT[] );

Function: evergreen.oils_xpath_string(text, text, anyarray)

Returns: text

Language: SQL

    SELECT oils_xpath_string( $1, $2, '', $3 );

Function: evergreen.oils_xpath_string(text, text, text)

Returns: text

Language: SQL

    SELECT oils_xpath_string( $1, $2, $3, '{}'::TEXT[] );

Function: evergreen.oils_xpath_string(text, text, text, anyarray)

Returns: text

Language: SQL

    SELECT  ARRAY_TO_STRING(
                oils_xpath(
                    $1 ||
                        CASE WHEN $1 ~ $re$/[^/[]*@[^]]+$$re$ OR $1 ~ $re$text\(\)$$re$ THEN '' ELSE '//text()' END,
                    $2,
                    $4
                ),
                $3
            );

Function: evergreen.oils_xpath_table(criteria text, xpaths text, relation_name text, document_field text, key text)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    xpath_list  TEXT[];
    select_list TEXT[];
    where_list  TEXT[];
    q           TEXT;
    out_record  RECORD;
    empty_test  RECORD;
BEGIN
    xpath_list := STRING_TO_ARRAY( xpaths, '|' );

    select_list := ARRAY_APPEND( select_list, key || '::INT AS key' );

    FOR i IN 1 .. ARRAY_UPPER(xpath_list,1) LOOP
        IF xpath_list[i] = 'null()' THEN
            select_list := ARRAY_APPEND( select_list, 'NULL::TEXT AS c_' || i );
        ELSE
            select_list := ARRAY_APPEND(
                select_list,
                $sel$
                unnest(
                    COALESCE(
                        NULLIF(
                            oils_xpath(
                                $sel$ ||
                                    quote_literal(
                                        CASE
                                            WHEN xpath_list[i] ~ $re$/[^/[]*@[^/]+$$re$ OR xpath_list[i] ~ $re$text\(\)$$re$ THEN xpath_list[i]
                                            ELSE xpath_list[i] || '//text()'
                                        END
                                    ) ||
                                $sel$,
                                $sel$ || document_field || $sel$
                            ),
                           '{}'::TEXT[]
                        ),
                        '{NULL}'::TEXT[]
                    )
                ) AS c_$sel$ || i
            );
            where_list := ARRAY_APPEND(
                where_list,
                'c_' || i || ' IS NOT NULL'
            );
        END IF;
    END LOOP;

    q := $q$
SELECT * FROM (
    SELECT $q$ || ARRAY_TO_STRING( select_list, ', ' ) || $q$ FROM $q$ || relation_name || $q$ WHERE ($q$ || criteria || $q$)
)x WHERE $q$ || ARRAY_TO_STRING( where_list, ' OR ' );
    -- RAISE NOTICE 'query: %', q;

    FOR out_record IN EXECUTE q LOOP
        RETURN NEXT out_record;
    END LOOP;

    RETURN;
END;

Function: evergreen.oils_xpath_tag_to_table(xpaths text, tag text, marc text[])

Returns: SET OF record

Language: PLPGSQL


-- This function currently populates columns with the FIRST matching value
-- of each XPATH.  It would be reasonable to add a 'return_arrays' option
-- where each column is an array of all matching values for each path, but
-- that remains as a TODO

DECLARE
    field RECORD;
    output RECORD;
    select_list TEXT[];
    from_list TEXT[];
    q TEXT;
BEGIN
    -- setup query select
    FOR i IN 1 .. ARRAY_UPPER(xpaths,1) LOOP
        IF xpaths[i] = 'null()' THEN
            select_list := ARRAY_APPEND(select_list, 'NULL::TEXT AS c_' || i );
        ELSE
            select_list := ARRAY_APPEND(select_list, '(oils_xpath(' ||
                quote_literal(
                    CASE
                        WHEN xpaths[i] ~ $re$/[^/[]*@[^/]+$$re$ -- attribute
                            OR xpaths[i] ~ $re$text\(\)$$re$
                        THEN xpaths[i]
                        ELSE xpaths[i] || '//text()'
                    END
                ) || ', field_marc))[1] AS cl_' || i);
                -- hardcoded to first value for each path
        END IF;
    END LOOP;

    -- run query over tag set
    q := 'SELECT ' || ARRAY_TO_STRING(select_list, ',')
        || ' FROM UNNEST(oils_xpath(' || quote_literal('//*[@tag="' || tag
        || '"]') || ', ' || quote_literal(marc) || ')) AS field_marc;';
    --RAISE NOTICE '%', q;

    RETURN QUERY EXECUTE q;
END;


Function: evergreen.oils_xslt_process(text, text)

Returns: text

Language: PLPERLU

  use strict;

  use XML::LibXSLT;
  use XML::LibXML;

  my $doc = shift;
  my $xslt = shift;

  # The following approach uses the older XML::LibXML 1.69 / XML::LibXSLT 1.68
  # methods of parsing XML documents and stylesheets, in the hopes of broader
  # compatibility with distributions
  my $parser = $_SHARED{'_xslt_process'}{parsers}{xml} || XML::LibXML->new();

  # Cache the XML parser, if we do not already have one
  $_SHARED{'_xslt_process'}{parsers}{xml} = $parser
    unless ($_SHARED{'_xslt_process'}{parsers}{xml});

  my $xslt_parser = $_SHARED{'_xslt_process'}{parsers}{xslt} || XML::LibXSLT->new();

  # Cache the XSLT processor, if we do not already have one
  $_SHARED{'_xslt_process'}{parsers}{xslt} = $xslt_parser
    unless ($_SHARED{'_xslt_process'}{parsers}{xslt});

  my $stylesheet = $_SHARED{'_xslt_process'}{stylesheets}{$xslt} ||
    $xslt_parser->parse_stylesheet( $parser->parse_string($xslt) );

  $_SHARED{'_xslt_process'}{stylesheets}{$xslt} = $stylesheet
    unless ($_SHARED{'_xslt_process'}{stylesheets}{$xslt});

  return $stylesheet->output_as_chars(
    $stylesheet->transform(
      $parser->parse_string($doc)
    )
  );


Function: evergreen.org_top()

Returns: org_unit

Language: SQL

    SELECT * FROM actor.org_unit WHERE parent_ou IS NULL LIMIT 1;

Function: evergreen.ous_change_log()

Returns: trigger

Language: PLPGSQL

    DECLARE
    original TEXT;
    BEGIN
        -- Check for which setting is being updated, and log it.
        SELECT INTO original value FROM actor.org_unit_setting WHERE name = NEW.name AND org_unit = NEW.org_unit;
                
        INSERT INTO config.org_unit_setting_type_log (org,original_value,new_value,field_name) VALUES (NEW.org_unit, original, NEW.value, NEW.name);
        
        RETURN NEW;
    END;

Function: evergreen.ous_delete_log()

Returns: trigger

Language: PLPGSQL

    DECLARE
    original TEXT;
    BEGIN
        -- Check for which setting is being updated, and log it.
        SELECT INTO original value FROM actor.org_unit_setting WHERE name = OLD.name AND org_unit = OLD.org_unit;
                
        INSERT INTO config.org_unit_setting_type_log (org,original_value,new_value,field_name) VALUES (OLD.org_unit, original, 'null', OLD.name);
        
        RETURN OLD;
    END;

Function: evergreen.pg_statistics(frequency text, element text)

Returns: SET OF record

Language: PLPGSQL

BEGIN
    -- This query will die on PG < 9.2, but the function can be created. We just won't use it where we can't.
    RETURN QUERY
        SELECT  e,
                f
          FROM  (SELECT ROW_NUMBER() OVER (),
                        (f * 100)::INT AS f
                  FROM  (SELECT UNNEST(most_common_elem_freqs) AS f
                          FROM  pg_stats
                          WHERE tablename = tab
                                AND attname = col
                        )x
                ) AS f
                JOIN (SELECT ROW_NUMBER() OVER (),
                             e
                       FROM (SELECT UNNEST(most_common_elems::text::text[]) AS e
                              FROM  pg_stats
                              WHERE tablename = tab
                                    AND attname = col
                            )y
                ) AS elems USING (row_number);
END;

Function: evergreen.protect_reserved_rows_from_delete()

Returns: trigger

Language: PLPGSQL

BEGIN
IF OLD.id < TG_ARGV[0]::INT THEN
    RAISE EXCEPTION 'Cannot delete row with reserved ID %', OLD.id;
END IF;
RETURN OLD;
END

Function: evergreen.query_int_wrapper(integer[], text)

Returns: boolean

Language: PLPGSQL

BEGIN
    RETURN $1 @@ $2::query_int;
END;

Function: evergreen.qwerty_keyboard_distance(b text, a text)

Returns: numeric

Language: PLPERLU

use String::KeyboardDistance qw(:all);
return qwerty_keyboard_distance(@_);

Function: evergreen.qwerty_keyboard_distance_match(b text, a text)

Returns: numeric

Language: PLPERLU

use String::KeyboardDistance qw(:all);
return qwerty_keyboard_distance_match(@_);

Function: evergreen.rank_cp(copy asset.copy)

Returns: integer

Language: PLPGSQL

DECLARE
    rank INT;
BEGIN
    WITH totally_available AS (
        SELECT id, 0 AS avail_rank
        FROM config.copy_status
        WHERE opac_visible IS TRUE
            AND copy_active IS TRUE
            AND id != 1 -- "Checked out"
    ), almost_available AS (
        SELECT id, 10 AS avail_rank
        FROM config.copy_status
        WHERE holdable IS TRUE
            AND opac_visible IS TRUE
            AND copy_active IS FALSE
            OR id = 1 -- "Checked out"
    )
    SELECT COALESCE(
        CASE WHEN NOT copy.opac_visible THEN 100 END,
        (SELECT avail_rank FROM totally_available WHERE copy.status IN (id)),
        CASE WHEN copy.holdable THEN
            (SELECT avail_rank FROM almost_available WHERE copy.status IN (id))
        END,
        100
    ) INTO rank;

    RETURN rank;
END;

Function: evergreen.rank_cp(copy_id bigint)

Returns: integer

Language: PLPGSQL

DECLARE
    copy asset.copy%ROWTYPE;
BEGIN
    SELECT * INTO copy FROM asset.copy WHERE id = copy_id;
    RETURN evergreen.rank_cp(copy);
END;

Function: evergreen.rank_ou(plon integer, plat integer, pref_lib integer, search_lib double precision, lib double precision)

Returns: integer

Language: SQL

    SELECT COALESCE(

        -- lib matches search_lib
        (SELECT CASE WHEN $1 = $2 THEN -20000 END),

        -- lib matches pref_lib
        (SELECT CASE WHEN $1 = $3 THEN -10000 END),


        -- pref_lib is a child of search_lib and lib is a child of pref lib.  
        -- For example, searching CONS, pref lib is SYS1, 
        -- copies at BR1 and BR2 sort to the front.
        (SELECT distance - 5000
            FROM actor.org_unit_descendants_distance($3) 
            WHERE id = $1 AND $3 IN (
                SELECT id FROM actor.org_unit_descendants($2))),

        -- lib is a child of search_lib
        (SELECT distance FROM actor.org_unit_descendants_distance($2) WHERE id = $1),

        -- all others pay cash
        1000
    ) + ((SELECT CASE WHEN addr.latitude IS NULL THEN 0 ELSE -20038 END) + (earth_distance( -- shortest GC distance is returned, only half the circumfrence is needed
            ll_to_earth(
                COALESCE(addr.latitude,plat), -- if the org has no coords, we just
                COALESCE(addr.longitude,plon) -- force 0 distance and let the above tie-break
            ),ll_to_earth(plat,plon)
        ) / 1000)::INT ) -- earth_distance is in meters, convert to kilometers and subtract from largest distance
    FROM actor.org_unit org
		 LEFT JOIN actor.org_address addr ON (org.billing_address = addr.id)
	WHERE org.id = $1;

Function: evergreen.rank_ou(pref_lib integer, search_lib integer, lib integer)

Returns: integer

Language: SQL

    SELECT COALESCE(

        -- lib matches search_lib
        (SELECT CASE WHEN $1 = $2 THEN -20000 END),

        -- lib matches pref_lib
        (SELECT CASE WHEN $1 = $3 THEN -10000 END),


        -- pref_lib is a child of search_lib and lib is a child of pref lib.  
        -- For example, searching CONS, pref lib is SYS1, 
        -- copies at BR1 and BR2 sort to the front.
        (SELECT distance - 5000
            FROM actor.org_unit_descendants_distance($3) 
            WHERE id = $1 AND $3 IN (
                SELECT id FROM actor.org_unit_descendants($2))),

        -- lib is a child of search_lib
        (SELECT distance FROM actor.org_unit_descendants_distance($2) WHERE id = $1),

        -- all others pay cash
        1000
    );

Function: evergreen.ranked_volumes(rank bigint, label_sortkey integer, name integer, id public.hstore, includes public.hstore, pref_lib integer, soffset text[])

Returns: SET OF record

Language: SQL

 SELECT * FROM evergreen.ranked_volumes(ARRAY[$1],$2,$3,$4,$5,$6,$7) 

Function: evergreen.ranked_volumes(rank bigint[], label_sortkey integer, name integer, id public.hstore, includes public.hstore, pref_lib integer, soffset text[])

Returns: SET OF record

Language: SQL

    WITH RECURSIVE ou_depth AS (
        SELECT COALESCE(
            $3,
            (
                SELECT depth
                FROM actor.org_unit_type aout
                    INNER JOIN actor.org_unit ou ON ou_type = aout.id
                WHERE ou.id = $2
            )
        ) AS depth
    ), descendant_depth AS (
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
        FROM  actor.org_unit ou
                JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
                JOIN anscestor_depth ad ON (ad.id = ou.id),
                ou_depth
        WHERE ad.depth = ou_depth.depth
            UNION ALL
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
        FROM  actor.org_unit ou
                JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
                JOIN descendant_depth ot ON (ot.id = ou.parent_ou)
    ), anscestor_depth AS (
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
        FROM  actor.org_unit ou
                JOIN actor.org_unit_type out ON (out.id = ou.ou_type)
        WHERE ou.id = $2
            UNION ALL
        SELECT  ou.id,
                ou.parent_ou,
                out.depth
        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)
    ), descendants as (
        SELECT ou.* FROM actor.org_unit ou JOIN descendant_depth USING (id)
    )

    SELECT ua.id, ua.name, ua.label_sortkey, MIN(ua.rank) AS rank FROM (
        SELECT acn.id, owning_lib.name, acn.label_sortkey,
            evergreen.rank_cp(acp),
            RANK() OVER w
        FROM asset.call_number acn
            JOIN asset.copy acp ON (acn.id = acp.call_number)
            JOIN descendants AS aou ON (acp.circ_lib = aou.id)
            JOIN actor.org_unit AS owning_lib ON (acn.owning_lib = owning_lib.id)
        WHERE acn.record = ANY ($1)
            AND acn.deleted IS FALSE
            AND acp.deleted IS FALSE
            AND CASE WHEN ('exclude_invisible_acn' = ANY($7)) THEN 
                EXISTS (
                    WITH basevm AS (SELECT c_attrs FROM  asset.patron_default_visibility_mask()),
                         circvm AS (SELECT search.calculate_visibility_attribute_test('circ_lib', ARRAY[acp.circ_lib]) AS mask)
                    SELECT  1 
                      FROM  basevm, circvm, asset.copy_vis_attr_cache acvac
                      WHERE acvac.vis_attr_vector @@ (basevm.c_attrs || '&' || circvm.mask)::query_int
                            AND acvac.target_copy = acp.id
                            AND acvac.record = acn.record
                ) ELSE TRUE END
        GROUP BY acn.id, evergreen.rank_cp(acp), owning_lib.name, acn.label_sortkey, aou.id
        WINDOW w AS (
            ORDER BY 
                COALESCE(
                    CASE WHEN aou.id = $2 THEN -20000 END,
                    CASE WHEN aou.id = $6 THEN -10000 END,
                    (SELECT distance - 5000
                        FROM actor.org_unit_descendants_distance($6) as x
                        WHERE x.id = aou.id AND $6 IN (
                            SELECT q.id FROM actor.org_unit_descendants($2) as q)),
                    (SELECT e.distance FROM actor.org_unit_descendants_distance($2) as e WHERE e.id = aou.id),
                    1000
                ),
                evergreen.rank_cp(acp)
        )
    ) AS ua
    GROUP BY ua.id, ua.name, ua.label_sortkey
    ORDER BY rank, ua.name, ua.label_sortkey
    LIMIT ($4 -> 'acn')::INT
    OFFSET ($5 -> 'acn')::INT;

Function: evergreen.regexp_split_to_array(text, text)

Returns: text[]

Language: PLPERLU

    return encode_array_literal([split $_[1], $_[0]]);

Function: evergreen.rel_bump(mults text[], bumps text, value text[], terms numeric[])

Returns: numeric

Language: PLPERLU

use strict;
my ($terms,$value,$bumps,$mults) = @_;

my $retval = 1;

for (my $id = 0; $id < @$bumps; $id++) {
        if ($bumps->[$id] eq 'first_word') {
                $retval *= $mults->[$id] if ($value =~ /^$terms->[0]/);
        } elsif ($bumps->[$id] eq 'full_match') {
                my $fullmatch = join(' ', @$terms);
                $retval *= $mults->[$id] if ($value =~ /^$fullmatch$/);
        } elsif ($bumps->[$id] eq 'word_order') {
                my $wordorder = join('.*', @$terms);
                $retval *= $mults->[$id] if ($value =~ /$wordorder/);
        }
}
return $retval;

Function: evergreen.tableoid2name(oid)

Returns: text

Language: PLPGSQL

	BEGIN
		RETURN $1::regclass;
	END;

Function: evergreen.text_array_merge_unique(text[], text[])

Returns: text[]

Language: SQL

    SELECT NULLIF(ARRAY(
        SELECT * FROM UNNEST($1) x
            UNION
        SELECT * FROM UNNEST($2) y
    ),'{}');

Function: evergreen.unaccent_and_squash(arg text)

Returns: text

Language: PLPGSQL

	BEGIN
	RETURN evergreen.lowercase(public.unaccent('public.unaccent', regexp_replace(arg, '[\s[:punct:]]','','g')));
	END;

Function: evergreen.upgrade_deps_block_check(my_applied_to text, my_db_patch text)

Returns: boolean

Language: PLPGSQL

DECLARE 
    deprecates TEXT;
    supersedes TEXT;
BEGIN
    IF NOT evergreen.upgrade_verify_no_dep_conflicts( my_db_patch ) THEN
        SELECT  STRING_AGG(patch, ', ') INTO deprecates FROM evergreen.upgrade_list_applied_deprecates(my_db_patch);
        SELECT  STRING_AGG(patch, ', ') INTO supersedes FROM evergreen.upgrade_list_applied_supersedes(my_db_patch);
        RAISE EXCEPTION '
Upgrade script % can not be applied:
  applied deprecated scripts %
  applied superseded scripts %
  deprecated by %
  superseded by %',
            my_db_patch,
            (SELECT ARRAY_AGG(patch) FROM evergreen.upgrade_list_applied_deprecates(my_db_patch)),
            (SELECT ARRAY_AGG(patch) FROM evergreen.upgrade_list_applied_supersedes(my_db_patch)),
            evergreen.upgrade_list_applied_deprecated(my_db_patch),
            evergreen.upgrade_list_applied_superseded(my_db_patch);
    END IF;

    INSERT INTO config.upgrade_log (version, applied_to) VALUES (my_db_patch, my_applied_to);
    RETURN TRUE;
END;

Function: evergreen.upgrade_list_applied_deprecated(my_db_patch text)

Returns: SET OF text

Language: SQL

    SELECT  db_patch
      FROM  config.db_patch_dependencies
      WHERE ARRAY[$1]::TEXT[] && deprecates

Function: evergreen.upgrade_list_applied_deprecates(my_db_patch text)

Returns: SET OF patch

Language: SQL

    SELECT  DISTINCT l.version
      FROM  config.upgrade_log l
            JOIN config.db_patch_dependencies d ON (l.version = ANY(d.deprecates))
      WHERE d.db_patch = $1

Function: evergreen.upgrade_list_applied_superseded(my_db_patch text)

Returns: SET OF text

Language: SQL

    SELECT  db_patch
      FROM  config.db_patch_dependencies
      WHERE ARRAY[$1]::TEXT[] && supersedes

Function: evergreen.upgrade_list_applied_supersedes(my_db_patch text)

Returns: SET OF patch

Language: SQL

    SELECT  DISTINCT l.version
      FROM  config.upgrade_log l
            JOIN config.db_patch_dependencies d ON (l.version = ANY(d.supersedes))
      WHERE d.db_patch = $1

Function: evergreen.upgrade_verify_no_dep_conflicts(my_db_patch text)

Returns: boolean

Language: SQL

    SELECT  COUNT(*) = 0
      FROM  (SELECT * FROM evergreen.upgrade_list_applied_deprecates( $1 )
                UNION
             SELECT * FROM evergreen.upgrade_list_applied_supersedes( $1 )
                UNION
             SELECT * FROM evergreen.upgrade_list_applied_deprecated( $1 )
                UNION
             SELECT * FROM evergreen.upgrade_list_applied_superseded( $1 ))x

Function: evergreen.uppercase(text)

Returns: text

Language: PLPERLU

    return uc(shift);

Function: evergreen.vandelay_import_item_imported_as_inh_fkey()

Returns: trigger

Language: PLPGSQL

BEGIN
        IF NEW.imported_as IS NULL THEN
                RETURN NEW;
        END IF;
        PERFORM 1 FROM asset.copy WHERE id = NEW.imported_as;
        IF NOT FOUND THEN
                RAISE foreign_key_violation USING MESSAGE = FORMAT(
                        $$Referenced asset.copy id not found, imported_as:%s$$, NEW.imported_as
                );
        END IF;
        RETURN NEW;
END;

Function: evergreen.xml_escape(str text)

Returns: text

Language: SQL

    SELECT REPLACE(REPLACE(REPLACE($1,
       '&', '&amp;'),
       '<', '&lt;'),
       '>', '&gt;');

Function: evergreen.xml_famous5_to_text(text)

Returns: text

Language: SQL

 SELECT REPLACE(
            REPLACE(
                REPLACE(
                    REPLACE(
                        REPLACE( $1, '&lt;', '<'),
                        '&gt;',
                        '>'
                    ),
                    '&apos;',
                    $$'$$
                ), -- ' ... vim
                '&quot;',
                '"'
            ),
            '&amp;',
            '&'
        );

Function: evergreen.xml_pretty_print(input xml)

Returns: xml

Language: SQL

Simple pretty printer for XML, as written by Andrew Dunstan at http://goo.gl/zBHIk

SELECT xslt_process($1::text,
$$<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="1.0">
   <xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
   <xsl:strip-space elements="*"/>
   <xsl:template match="@*|node()">
     <xsl:copy>
       <xsl:apply-templates select="@*|node()"/>
     </xsl:copy>
   </xsl:template>
 </xsl:stylesheet>
$$::text)::XML

Function: evergreen.z3950_attr_name_is_valid()

Returns: trigger

Language: PLPGSQL

Used by a config.z3950_index_field_map constraint trigger to verify z3950_attr_type maps.

    BEGIN

      PERFORM * FROM config.z3950_attr WHERE name = NEW.z3950_attr_type;

      IF FOUND THEN
        RETURN NULL;
      END IF;

      RAISE EXCEPTION '% is not a valid Z39.50 attribute type', NEW.z3950_attr_type;

    END;

Schema extend_reporter


View: extend_reporter.copy_count_per_org

extend_reporter.copy_count_per_org Structure
F-Key Name Type Description
bibid bigint
circ_lib integer
owning_lib integer
last_edit_time timestamp with time zone
has_only_deleted_copies integer
deleted_count bigint
visible_count bigint
total_count bigint
SELECT acn.record AS bibid
,
    ac.circ_lib
,
    acn.owning_lib
,
    max
(ac.edit_date) AS last_edit_time
,
    min
(
     (ac.deleted)::integer
) AS has_only_deleted_copies
,
    count
(
        CASE
            WHEN ac.deleted THEN ac.id
            ELSE NULL::bigint
        END) AS deleted_count
,
    count
(
        CASE
            WHEN 
     (NOT ac.deleted) THEN ac.id
            ELSE NULL::bigint
        END
) AS visible_count
,
    count
(*) AS total_count
   
FROM asset.call_number acn
,
    asset.copy ac
  
WHERE (ac.call_number = acn.id)
  
GROUP BY acn.record
, acn.owning_lib
, ac.circ_lib;

Index - Schema extend_reporter


View: extend_reporter.full_circ_count

extend_reporter.full_circ_count Structure
F-Key Name Type Description
id bigint
circ_count bigint
SELECT cp.id
,
    
(
     (COALESCE
           (
                 (
                  SELECT legacy_circ_count.circ_count
           
                    FROM extend_reporter.legacy_circ_count
          
                   WHERE (legacy_circ_count.id = cp.id)
                 )
                 , 0
           ) + 
           (
            SELECT count
                 (*) AS count
           
              FROM action.circulation
          
             WHERE (circulation.target_copy = cp.id)
           )
     ) + 
     (
      SELECT count
           (*) AS count
           
        FROM action.aged_circulation
          
       WHERE (aged_circulation.target_copy = cp.id)
     )
) AS circ_count
   
FROM asset.copy cp;

Index - Schema extend_reporter


View: extend_reporter.global_bibs_by_holding_update

extend_reporter.global_bibs_by_holding_update Structure
F-Key Name Type Description
id bigint
holding_update timestamp with time zone
update_type text
SELECT DISTINCT 
ON (x.id) x.id
,
    x.holding_update
,
    x.update_type
   
FROM (
SELECT b.id
     ,
            last
     (cp.create_date) AS holding_update
     ,
            'add'::text AS update_type
           
  FROM (
           (biblio.record_entry b
             
              JOIN asset.call_number cn 
                ON (
                       (cn.record = b.id)
                 )
           )
             
        JOIN asset.copy cp 
          ON (
                 (cp.call_number = cn.id)
           )
     )
          
 WHERE (
           (NOT cp.deleted)
         AND (b.id > 0)
     )
          
GROUP BY b.id
        
 UNION
         
SELECT b.id
     ,
            last
     (cp.edit_date) AS holding_update
     ,
            'delete'::text AS update_type
           
  FROM (
           (biblio.record_entry b
             
              JOIN asset.call_number cn 
                ON (
                       (cn.record = b.id)
                 )
           )
             
        JOIN asset.copy cp 
          ON (
                 (cp.call_number = cn.id)
           )
     )
          
 WHERE (cp.deleted 
         AND (b.id > 0)
     )
          
GROUP BY b.id
) x
  
ORDER BY x.id
, x.holding_update;

Index - Schema extend_reporter


Table: extend_reporter.legacy_circ_count

extend_reporter.legacy_circ_count Structure
F-Key Name Type Description
id bigint PRIMARY KEY
circ_count integer NOT NULL

Index - Schema extend_reporter


Schema metabib


Table: metabib.author_field_entry

metabib.author_field_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id source bigint NOT NULL
config.metabib_field.id field integer NOT NULL
value text NOT NULL
index_vector tsvector NOT NULL
metabib_author_field_entry_index_vector_idx index_vector metabib_author_field_entry_source_idx source metabib_author_field_entry_value_idx "substring"(value, 1, 1024)) WHERE (index_vector = ''::tsvector

Index - Schema metabib


Table: metabib.browse_entry

metabib.browse_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
value text UNIQUE#1
index_vector tsvector
sort_value text UNIQUE#1 NOT NULL

Tables referencing this one via Foreign Key Constraints:

browse_entry_sort_value_idx sort_value metabib_browse_entry_index_vector_idx index_vector

Index - Schema metabib


Table: metabib.browse_entry_def_map

metabib.browse_entry_def_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
metabib.browse_entry.id entry bigint
config.metabib_field.id def integer
biblio.record_entry.id source bigint
authority.record_entry.id authority bigint
browse_entry_def_map_def_idx def browse_entry_def_map_entry_idx entry browse_entry_def_map_source_idx source

Index - Schema metabib


Table: metabib.browse_entry_simple_heading_map

metabib.browse_entry_simple_heading_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
metabib.browse_entry.id entry bigint
authority.simple_heading.id simple_heading bigint
browse_entry_sh_map_entry_idx entry browse_entry_sh_map_sh_idx simple_heading

Index - Schema metabib


View: metabib.combined_all_field_entry

metabib.combined_all_field_entry Structure
F-Key Name Type Description
record bigint
metabib_field integer
index_vector tsvector
SELECT combined_title_field_entry.record
,
    combined_title_field_entry.metabib_field
,
    combined_title_field_entry.index_vector
   
FROM metabib.combined_title_field_entry

UNION ALL
 
SELECT combined_author_field_entry.record
,
    combined_author_field_entry.metabib_field
,
    combined_author_field_entry.index_vector
   
FROM metabib.combined_author_field_entry

UNION ALL
 
SELECT combined_subject_field_entry.record
,
    combined_subject_field_entry.metabib_field
,
    combined_subject_field_entry.index_vector
   
FROM metabib.combined_subject_field_entry

UNION ALL
 
SELECT combined_keyword_field_entry.record
,
    combined_keyword_field_entry.metabib_field
,
    combined_keyword_field_entry.index_vector
   
FROM metabib.combined_keyword_field_entry

UNION ALL
 
SELECT combined_identifier_field_entry.record
,
    combined_identifier_field_entry.metabib_field
,
    combined_identifier_field_entry.index_vector
   
FROM metabib.combined_identifier_field_entry

UNION ALL
 
SELECT combined_series_field_entry.record
,
    combined_series_field_entry.metabib_field
,
    combined_series_field_entry.index_vector
   
FROM metabib.combined_series_field_entry;

Index - Schema metabib


Table: metabib.combined_author_field_entry

metabib.combined_author_field_entry Structure
F-Key Name Type Description
record bigint NOT NULL
metabib_field integer
index_vector tsvector NOT NULL
metabib_combined_author_field_entry_index_vector_idx index_vector metabib_combined_author_field_source_idx metabib_field

Index - Schema metabib


Table: metabib.combined_identifier_field_entry

metabib.combined_identifier_field_entry Structure
F-Key Name Type Description
record bigint NOT NULL
metabib_field integer
index_vector tsvector NOT NULL
metabib_combined_identifier_field_entry_index_vector_idx index_vector metabib_combined_identifier_field_source_idx metabib_field

Index - Schema metabib


Table: metabib.combined_keyword_field_entry

metabib.combined_keyword_field_entry Structure
F-Key Name Type Description
record bigint NOT NULL
metabib_field integer
index_vector tsvector NOT NULL
metabib_combined_keyword_field_entry_index_vector_idx index_vector metabib_combined_keyword_field_source_idx metabib_field

Index - Schema metabib


Table: metabib.combined_series_field_entry

metabib.combined_series_field_entry Structure
F-Key Name Type Description
record bigint NOT NULL
metabib_field integer
index_vector tsvector NOT NULL
metabib_combined_series_field_entry_index_vector_idx index_vector metabib_combined_series_field_source_idx metabib_field

Index - Schema metabib


Table: metabib.combined_subject_field_entry

metabib.combined_subject_field_entry Structure
F-Key Name Type Description
record bigint NOT NULL
metabib_field integer
index_vector tsvector NOT NULL
metabib_combined_subject_field_entry_index_vector_idx index_vector metabib_combined_subject_field_source_idx metabib_field

Index - Schema metabib


Table: metabib.combined_title_field_entry

metabib.combined_title_field_entry Structure
F-Key Name Type Description
record bigint NOT NULL
metabib_field integer
index_vector tsvector NOT NULL
metabib_combined_title_field_entry_index_vector_idx index_vector metabib_combined_title_field_source_idx metabib_field

Index - Schema metabib


View: metabib.composite_attr_id_map

metabib.composite_attr_id_map Structure
F-Key Name Type Description
id integer
attr text
value text
SELECT c.id
,
    c.ctype AS attr
,
    c.code AS value
   
FROM (config.coded_value_map c
     
  JOIN config.record_attr_definition d 
    ON (
           (
                 (d.name = c.ctype)
               AND d.composite
           )
     )
);

Index - Schema metabib


View: metabib.compressed_display_entry

metabib.compressed_display_entry Structure
F-Key Name Type Description
source bigint
name text
multi boolean
label text
field integer
value json
SELECT flat_display_entry.source
,
    flat_display_entry.name
,
    flat_display_entry.multi
,
    flat_display_entry.label
,
    flat_display_entry.field
,
        CASE
            WHEN flat_display_entry.multi THEN to_json
(array_agg
     (flat_display_entry.value)
)
            ELSE to_json
(min
     (flat_display_entry.value)
)
        END AS value
   
FROM metabib.flat_display_entry
  
GROUP BY flat_display_entry.source
, flat_display_entry.name
, flat_display_entry.multi
, flat_display_entry.label
, flat_display_entry.field;

Index - Schema metabib


Table: metabib.display_entry

metabib.display_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
source bigint NOT NULL
field integer NOT NULL
value text NOT NULL
metabib_display_entry_field_idx field metabib_display_entry_source_idx source

Index - Schema metabib


Table: metabib.facet_entry

metabib.facet_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
source bigint NOT NULL
field integer NOT NULL
value text NOT NULL
metabib_facet_entry_field_idx field metabib_facet_entry_source_idx source metabib_facet_entry_value_idx "substring"(value, 1, 1024)

Index - Schema metabib


View: metabib.flat_display_entry

metabib.flat_display_entry Structure
F-Key Name Type Description
source bigint
name text
multi boolean
label text
field integer
value text
SELECT mde.source
,
    cdfm.name
,
    cdfm.multi
,
    cmf.label
,
    cmf.id AS field
,
    mde.value
   
FROM (
     (metabib.display_entry mde
     
        JOIN config.metabib_field cmf 
          ON (
                 (cmf.id = mde.field)
           )
     )
     
  JOIN config.display_field_map cdfm 
    ON (
           (cdfm.field = mde.field)
     )
);

Index - Schema metabib


View: metabib.full_attr_id_map

metabib.full_attr_id_map Structure
F-Key Name Type Description
id bigint
attr text
value text
SELECT record_attr_id_map.id
,
    record_attr_id_map.attr
,
    record_attr_id_map.value
   
FROM metabib.record_attr_id_map

UNION
 
SELECT composite_attr_id_map.id
,
    composite_attr_id_map.attr
,
    composite_attr_id_map.value
   
FROM metabib.composite_attr_id_map;

Index - Schema metabib


View: metabib.full_rec

metabib.full_rec Structure
F-Key Name Type Description
id bigint
record bigint
tag character(3)
ind1 text
ind2 text
subfield text
value text
index_vector tsvector
SELECT real_full_rec.id
,
    real_full_rec.record
,
    real_full_rec.tag
,
    real_full_rec.ind1
,
    real_full_rec.ind2
,
    real_full_rec.subfield
,
    "substring"
(real_full_rec.value
     , 1
     , 1024
) AS value
,
    real_full_rec.index_vector
   
FROM metabib.real_full_rec;

Index - Schema metabib


Table: metabib.identifier_field_entry

metabib.identifier_field_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id source bigint NOT NULL
config.metabib_field.id field integer NOT NULL
value text NOT NULL
index_vector tsvector NOT NULL
metabib_identifier_field_entry_index_vector_idx index_vector metabib_identifier_field_entry_source_idx source metabib_identifier_field_entry_value_idx "substring"(value, 1, 1024)) WHERE (index_vector = ''::tsvector

Index - Schema metabib


Table: metabib.keyword_field_entry

metabib.keyword_field_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id source bigint NOT NULL
config.metabib_field.id field integer NOT NULL
value text NOT NULL
index_vector tsvector NOT NULL
metabib_keyword_field_entry_index_vector_idx index_vector metabib_keyword_field_entry_source_idx source metabib_keyword_field_entry_value_idx "substring"(value, 1, 1024)) WHERE (index_vector = ''::tsvector

Index - Schema metabib


Table: metabib.metarecord

metabib.metarecord Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
fingerprint text NOT NULL
biblio.record_entry.id master_record bigint
mods text

Tables referencing this one via Foreign Key Constraints:

metabib_metarecord_fingerprint_idx fingerprint metabib_metarecord_master_record_idx master_record

Index - Schema metabib


Table: metabib.metarecord_source_map

metabib.metarecord_source_map Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
metabib.metarecord.id metarecord bigint NOT NULL
biblio.record_entry.id source bigint NOT NULL
metabib_metarecord_source_map_metarecord_idx metarecord metabib_metarecord_source_map_source_record_idx source

Index - Schema metabib


Table: metabib.real_full_rec

metabib.real_full_rec Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('metabib.full_rec_id_seq'::regclass)
biblio.record_entry.id record bigint NOT NULL
tag character(3) NOT NULL
ind1 text
ind2 text
subfield text
value text NOT NULL
index_vector tsvector NOT NULL
metabib_full_rec_02x_tag_subfield_lower_substring tag, subfield, lower("substring"(value, 1, 1024))) WHERE (tag = ANY (ARRAY['020'::bpchar, '022'::bpchar, '024'::bpchar]) metabib_full_rec_index_vector_idx index_vector metabib_full_rec_isxn_caseless_idx lower(value)) WHERE (tag = ANY (ARRAY['020'::bpchar, '022'::bpchar, '024'::bpchar]) metabib_full_rec_record_idx record metabib_full_rec_tag_subfield_idx tag, subfield metabib_full_rec_value_idx "substring"(value, 1, 1024) metabib_full_rec_value_tpo_index "substring"(value, 1, 1024) text_pattern_ops

Index - Schema metabib


View: metabib.rec_descriptor

metabib.rec_descriptor Structure
F-Key Name Type Description
id bigint
record bigint
item_type text
item_form text
bib_level text
control_type text
char_encoding text
enc_level text
audience text
lit_form text
type_mat text
cat_form text
pub_status text
item_lang text
vr_format text
date1 text
date2 text
SELECT record_attr.id
,
    record_attr.id AS record
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).item_type AS item_type
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).item_form AS item_form
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).bib_level AS bib_level
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).control_type AS control_type
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).char_encoding AS char_encoding
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).enc_level AS enc_level
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).audience AS audience
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).lit_form AS lit_form
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).type_mat AS type_mat
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).cat_form AS cat_form
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).pub_status AS pub_status
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).item_lang AS item_lang
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).vr_format AS vr_format
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).date1 AS date1
,
    
(populate_record
     (NULL::metabib.rec_desc_type
           , record_attr.attrs
     )
).date2 AS date2
   
FROM metabib.record_attr;

Index - Schema metabib


View: metabib.record_attr

metabib.record_attr Structure
F-Key Name Type Description
id bigint
attrs hstore
SELECT record_attr_flat.id
,
    hstore
(array_agg
     (record_attr_flat.attr)
     , array_agg
     (record_attr_flat.value)
) AS attrs
   
FROM metabib.record_attr_flat
  
WHERE (record_attr_flat.attr IS NOT NULL)
  
GROUP BY record_attr_flat.id;

Index - Schema metabib


View: metabib.record_attr_flat

metabib.record_attr_flat Structure
F-Key Name Type Description
id bigint
attr text
value text
SELECT v.source AS id
,
    m.attr
,
    m.value
   
FROM (metabib.record_attr_vector_list v
     
LEFT JOIN metabib.uncontrolled_record_attr_value m 
    ON (
           (m.id = ANY 
                 (v.vlist)
           )
     )
)
UNION
 
SELECT v.source AS id
,
    c.ctype AS attr
,
    c.code AS value
   
FROM (metabib.record_attr_vector_list v
     
LEFT JOIN config.coded_value_map c 
    ON (
           (c.id = ANY 
                 (v.vlist)
           )
     )
);

Index - Schema metabib


View: metabib.record_attr_id_map

metabib.record_attr_id_map Structure
F-Key Name Type Description
id bigint
attr text
value text
SELECT uncontrolled_record_attr_value.id
,
    uncontrolled_record_attr_value.attr
,
    uncontrolled_record_attr_value.value
   
FROM metabib.uncontrolled_record_attr_value

UNION
 
SELECT c.id
,
    c.ctype AS attr
,
    c.code AS value
   
FROM (config.coded_value_map c
     
  JOIN config.record_attr_definition d 
    ON (
           (
                 (d.name = c.ctype)
               AND (NOT d.composite)
           )
     )
);

Index - Schema metabib


Table: metabib.record_attr_vector_list

metabib.record_attr_vector_list Structure
F-Key Name Type Description
biblio.record_entry.id source bigint PRIMARY KEY
vlist integer[] NOT NULL
mrca_vlist_idx vlist gin__int_ops

Index - Schema metabib


Table: metabib.record_sorter

metabib.record_sorter Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id source bigint NOT NULL
config.record_attr_definition.name attr text NOT NULL
value text NOT NULL
metabib_sorter_a_v_idx attr, value metabib_sorter_s_a_idx source, attr metabib_sorter_source_idx source

Index - Schema metabib


Table: metabib.series_field_entry

metabib.series_field_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id source bigint NOT NULL
config.metabib_field.id field integer NOT NULL
value text NOT NULL
index_vector tsvector NOT NULL
metabib_series_field_entry_index_vector_idx index_vector metabib_series_field_entry_source_idx source metabib_series_field_entry_value_idx "substring"(value, 1, 1024)) WHERE (index_vector = ''::tsvector

Index - Schema metabib


Table: metabib.subject_field_entry

metabib.subject_field_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id source bigint NOT NULL
config.metabib_field.id field integer NOT NULL
value text NOT NULL
index_vector tsvector NOT NULL
metabib_subject_field_entry_index_vector_idx index_vector metabib_subject_field_entry_source_idx source metabib_subject_field_entry_value_idx "substring"(value, 1, 1024)) WHERE (index_vector = ''::tsvector

Index - Schema metabib


Table: metabib.title_field_entry

metabib.title_field_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id source bigint NOT NULL
config.metabib_field.id field integer NOT NULL
value text NOT NULL
index_vector tsvector NOT NULL
metabib_title_field_entry_index_vector_idx index_vector metabib_title_field_entry_source_idx source metabib_title_field_entry_value_idx "substring"(value, 1, 1024)) WHERE (index_vector = ''::tsvector

Index - Schema metabib


Table: metabib.uncontrolled_record_attr_value

metabib.uncontrolled_record_attr_value Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('metabib.uncontrolled_record_attr_value_id_seq'::regclass)
config.record_attr_definition.name attr text NOT NULL
value text NOT NULL

Index - Schema metabib


View: metabib.wide_display_entry

metabib.wide_display_entry Structure
F-Key Name Type Description
source bigint
title text
author text
subject_geographic text
subject_name text
subject_temporal text
subject_topic text
creators text
isbn text
issn text
upc text
tcn text
edition text
physical_description text
publisher text
series_title text
abstract text
toc text
pubdate text
type_of_resource text
SELECT bre.id AS source
,
    
(COALESCE
     (mcde_title.value
           ,'null'::json
     )
)::text AS title
,
    
(COALESCE
     (mcde_author.value
           ,'null'::json
     )
)::text AS author
,
    
(COALESCE
     (mcde_subject_geographic.value
           ,'null'::json
     )
)::text AS subject_geographic
,
    
(COALESCE
     (mcde_subject_name.value
           ,'null'::json
     )
)::text AS subject_name
,
    
(COALESCE
     (mcde_subject_temporal.value
           ,'null'::json
     )
)::text AS subject_temporal
,
    
(COALESCE
     (mcde_subject_topic.value
           ,'null'::json
     )
)::text AS subject_topic
,
    
(COALESCE
     (mcde_creators.value
           ,'null'::json
     )
)::text AS creators
,
    
(COALESCE
     (mcde_isbn.value
           ,'null'::json
     )
)::text AS isbn
,
    
(COALESCE
     (mcde_issn.value
           ,'null'::json
     )
)::text AS issn
,
    
(COALESCE
     (mcde_upc.value
           ,'null'::json
     )
)::text AS upc
,
    
(COALESCE
     (mcde_tcn.value
           ,'null'::json
     )
)::text AS tcn
,
    
(COALESCE
     (mcde_edition.value
           ,'null'::json
     )
)::text AS edition
,
    
(COALESCE
     (mcde_physical_description.value
           ,'null'::json
     )
)::text AS physical_description
,
    
(COALESCE
     (mcde_publisher.value
           ,'null'::json
     )
)::text AS publisher
,
    
(COALESCE
     (mcde_series_title.value
           ,'null'::json
     )
)::text AS series_title
,
    
(COALESCE
     (mcde_abstract.value
           ,'null'::json
     )
)::text AS abstract
,
    
(COALESCE
     (mcde_toc.value
           ,'null'::json
     )
)::text AS toc
,
    
(COALESCE
     (mcde_pubdate.value
           ,'null'::json
     )
)::text AS pubdate
,
    
(COALESCE
     (mcde_type_of_resource.value
           ,'null'::json
     )
)::text AS type_of_resource
   
FROM (
     (
           (
                 (
                       (
                             (
                                   (
                                         (
                                               (
                                                     (
                                                           (
                                                                 (
                                                                       (
                                                                             (
                                                                                   (
                                                                                         (
                                                                                               (
                                                                                                     (
                                                                                                           (
                                                                                                                 (biblio.record_entry bre
     
                                                                                                               LEFT JOIN metabib.compressed_display_entry mcde_title 
                                                                                                                      ON (
                                                                                                                             (
                                                                                                                                   (bre.id = mcde_title.source)
                                                                                                                                 AND (mcde_title.name = 'title'::text)
                                                                                                                             )
                                                                                                                       )
                                                                                                                 )
     
                                                                                                         LEFT JOIN metabib.compressed_display_entry mcde_author 
                                                                                                                ON (
                                                                                                                       (
                                                                                                                             (bre.id = mcde_author.source)
                                                                                                                           AND (mcde_author.name = 'author'::text)
                                                                                                                       )
                                                                                                                 )
                                                                                                           )
     
                                                                                                   LEFT JOIN metabib.compressed_display_entry mcde_subject 
                                                                                                          ON (
                                                                                                                 (
                                                                                                                       (bre.id = mcde_subject.source)
                                                                                                                     AND (mcde_subject.name = 'subject'::text)
                                                                                                                 )
                                                                                                           )
                                                                                                     )
     
                                                                                             LEFT JOIN metabib.compressed_display_entry mcde_subject_geographic 
                                                                                                    ON (
                                                                                                           (
                                                                                                                 (bre.id = mcde_subject_geographic.source)
                                                                                                               AND (mcde_subject_geographic.name = 'subject_geographic'::text)
                                                                                                           )
                                                                                                     )
                                                                                               )
     
                                                                                       LEFT JOIN metabib.compressed_display_entry mcde_subject_name 
                                                                                              ON (
                                                                                                     (
                                                                                                           (bre.id = mcde_subject_name.source)
                                                                                                         AND (mcde_subject_name.name = 'subject_name'::text)
                                                                                                     )
                                                                                               )
                                                                                         )
     
                                                                                 LEFT JOIN metabib.compressed_display_entry mcde_subject_temporal 
                                                                                        ON (
                                                                                               (
                                                                                                     (bre.id = mcde_subject_temporal.source)
                                                                                                   AND (mcde_subject_temporal.name = 'subject_temporal'::text)
                                                                                               )
                                                                                         )
                                                                                   )
     
                                                                           LEFT JOIN metabib.compressed_display_entry mcde_subject_topic 
                                                                                  ON (
                                                                                         (
                                                                                               (bre.id = mcde_subject_topic.source)
                                                                                             AND (mcde_subject_topic.name = 'subject_topic'::text)
                                                                                         )
                                                                                   )
                                                                             )
     
                                                                     LEFT JOIN metabib.compressed_display_entry mcde_creators 
                                                                            ON (
                                                                                   (
                                                                                         (bre.id = mcde_creators.source)
                                                                                       AND (mcde_creators.name = 'creators'::text)
                                                                                   )
                                                                             )
                                                                       )
     
                                                               LEFT JOIN metabib.compressed_display_entry mcde_isbn 
                                                                      ON (
                                                                             (
                                                                                   (bre.id = mcde_isbn.source)
                                                                                 AND (mcde_isbn.name = 'isbn'::text)
                                                                             )
                                                                       )
                                                                 )
     
                                                         LEFT JOIN metabib.compressed_display_entry mcde_issn 
                                                                ON (
                                                                       (
                                                                             (bre.id = mcde_issn.source)
                                                                           AND (mcde_issn.name = 'issn'::text)
                                                                       )
                                                                 )
                                                           )
     
                                                   LEFT JOIN metabib.compressed_display_entry mcde_upc 
                                                          ON (
                                                                 (
                                                                       (bre.id = mcde_upc.source)
                                                                     AND (mcde_upc.name = 'upc'::text)
                                                                 )
                                                           )
                                                     )
     
                                             LEFT JOIN metabib.compressed_display_entry mcde_tcn 
                                                    ON (
                                                           (
                                                                 (bre.id = mcde_tcn.source)
                                                               AND (mcde_tcn.name = 'tcn'::text)
                                                           )
                                                     )
                                               )
     
                                       LEFT JOIN metabib.compressed_display_entry mcde_edition 
                                              ON (
                                                     (
                                                           (bre.id = mcde_edition.source)
                                                         AND (mcde_edition.name = 'edition'::text)
                                                     )
                                               )
                                         )
     
                                 LEFT JOIN metabib.compressed_display_entry mcde_physical_description 
                                        ON (
                                               (
                                                     (bre.id = mcde_physical_description.source)
                                                   AND (mcde_physical_description.name = 'physical_description'::text)
                                               )
                                         )
                                   )
     
                           LEFT JOIN metabib.compressed_display_entry mcde_publisher 
                                  ON (
                                         (
                                               (bre.id = mcde_publisher.source)
                                             AND (mcde_publisher.name = 'publisher'::text)
                                         )
                                   )
                             )
     
                     LEFT JOIN metabib.compressed_display_entry mcde_series_title 
                            ON (
                                   (
                                         (bre.id = mcde_series_title.source)
                                       AND (mcde_series_title.name = 'series_title'::text)
                                   )
                             )
                       )
     
               LEFT JOIN metabib.compressed_display_entry mcde_abstract 
                      ON (
                             (
                                   (bre.id = mcde_abstract.source)
                                 AND (mcde_abstract.name = 'abstract'::text)
                             )
                       )
                 )
     
         LEFT JOIN metabib.compressed_display_entry mcde_toc 
                ON (
                       (
                             (bre.id = mcde_toc.source)
                           AND (mcde_toc.name = 'toc'::text)
                       )
                 )
           )
     
   LEFT JOIN metabib.compressed_display_entry mcde_pubdate 
          ON (
                 (
                       (bre.id = mcde_pubdate.source)
                     AND (mcde_pubdate.name = 'pubdate'::text)
                 )
           )
     )
     
LEFT JOIN metabib.compressed_display_entry mcde_type_of_resource 
    ON (
           (
                 (bre.id = mcde_type_of_resource.source)
               AND (mcde_type_of_resource.name = 'type_of_resource'::text)
           )
     )
);

Index - Schema metabib


Function: metabib.autosuggest_prepare_tsquery(orig text)

Returns: text[]

Language: PLPGSQL

DECLARE
    orig_ended_in_space     BOOLEAN;
    result                  RECORD;
    plain                   TEXT;
    normalized              TEXT;
BEGIN
    orig_ended_in_space := orig ~ E'\\s$';

    orig := ARRAY_TO_STRING(
        evergreen.regexp_split_to_array(orig, E'\\W+'), ' '
    );

    normalized := public.naco_normalize(orig); -- also trim()s
    plain := trim(orig);

    IF NOT orig_ended_in_space THEN
        plain := plain || ':*';
        normalized := normalized || ':*';
    END IF;

    plain := ARRAY_TO_STRING(
        evergreen.regexp_split_to_array(plain, E'\\s+'), ' & '
    );
    normalized := ARRAY_TO_STRING(
        evergreen.regexp_split_to_array(normalized, E'\\s+'), ' & '
    );

    RETURN ARRAY[normalized, plain];
END;

Function: metabib.browse(result_limit integer[], pivot_id text, staff integer, context_loc_group integer, context_org boolean, browse_term bigint, search_field integer)

Returns: SET OF flat_browse_entry_appearance

Language: PLPGSQL

DECLARE
    core_query              TEXT;
    back_query              TEXT;
    forward_query           TEXT;
    pivot_sort_value        TEXT;
    pivot_sort_fallback     TEXT;
    context_locations       INT[];
    browse_superpage_size   INT;
    results_skipped         INT := 0;
    back_limit              INT;
    back_to_pivot           INT;
    forward_limit           INT;
    forward_to_pivot        INT;
BEGIN
    -- First, find the pivot if we were given a browse term but not a pivot.
    IF pivot_id IS NULL THEN
        pivot_id := metabib.browse_pivot(search_field, browse_term);
    END IF;

    SELECT INTO pivot_sort_value, pivot_sort_fallback
        sort_value, value FROM metabib.browse_entry WHERE id = pivot_id;

    -- Bail if we couldn't find a pivot.
    IF pivot_sort_value IS NULL THEN
        RETURN;
    END IF;

    -- Transform the context_loc_group argument (if any) (logc at the
    -- TPAC layer) into a form we'll be able to use.
    IF context_loc_group IS NOT NULL THEN
        SELECT INTO context_locations ARRAY_AGG(location)
            FROM asset.copy_location_group_map
            WHERE lgroup = context_loc_group;
    END IF;

    -- Get the configured size of browse superpages.
    SELECT INTO browse_superpage_size COALESCE(value::INT,100)     -- NULL ok
        FROM config.global_flag
        WHERE enabled AND name = 'opac.browse.holdings_visibility_test_limit';

    -- First we're going to search backward from the pivot, then we're going
    -- to search forward.  In each direction, we need two limits.  At the
    -- lesser of the two limits, we delineate the edge of the result set
    -- we're going to return.  At the greater of the two limits, we find the
    -- pivot value that would represent an offset from the current pivot
    -- at a distance of one "page" in either direction, where a "page" is a
    -- result set of the size specified in the "result_limit" argument.
    --
    -- The two limits in each direction make four derived values in total,
    -- and we calculate them now.
    back_limit := CEIL(result_limit::FLOAT / 2);
    back_to_pivot := result_limit;
    forward_limit := result_limit / 2;
    forward_to_pivot := result_limit - 1;

    -- This is the meat of the SQL query that finds browse entries.  We'll
    -- pass this to a function which uses it with a cursor, so that individual
    -- rows may be fetched in a loop until some condition is satisfied, without
    -- waiting for a result set of fixed size to be collected all at once.
    core_query := '
SELECT  mbe.id,
        mbe.value,
        mbe.sort_value
  FROM  metabib.browse_entry mbe
  WHERE (
            EXISTS ( -- are there any bibs using this mbe via the requested fields?
                SELECT  1
                  FROM  metabib.browse_entry_def_map mbedm
                  WHERE mbedm.entry = mbe.id AND mbedm.def = ANY(' || quote_literal(search_field) || ')
            ) OR EXISTS ( -- are there any authorities using this mbe via the requested fields?
                SELECT  1
                  FROM  metabib.browse_entry_simple_heading_map mbeshm
                        JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
                        JOIN authority.control_set_auth_field_metabib_field_map_refs map ON (
                            ash.atag = map.authority_field
                            AND map.metabib_field = ANY(' || quote_literal(search_field) || ')
                        )
                        JOIN authority.control_set_authority_field acsaf ON (
                            map.authority_field = acsaf.id
                        )
                        JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field)
                  WHERE mbeshm.entry = mbe.id
                    AND ahf.heading_purpose IN (' || $$'variant'$$ || ')
                    -- and authority that variant is coming from is linked to a bib
                    AND EXISTS (
                        SELECT  1
                        FROM  metabib.browse_entry_def_map mbedm2
                        WHERE mbedm2.authority = ash.record AND mbedm2.def = ANY(' || quote_literal(search_field) || ')
                    )

            )
        ) AND ';

    -- This is the variant of the query for browsing backward.
    back_query := core_query ||
        ' mbe.sort_value <= ' || quote_literal(pivot_sort_value) ||
    ' ORDER BY mbe.sort_value DESC, mbe.value DESC LIMIT 1000';

    -- This variant browses forward.
    forward_query := core_query ||
        ' mbe.sort_value > ' || quote_literal(pivot_sort_value) ||
    ' ORDER BY mbe.sort_value, mbe.value LIMIT 1000';

    -- We now call the function which applies a cursor to the provided
    -- queries, stopping at the appropriate limits and also giving us
    -- the next page's pivot.
    RETURN QUERY
        SELECT * FROM metabib.staged_browse(
            back_query, search_field, context_org, context_locations,
            staff, browse_superpage_size, TRUE, back_limit, back_to_pivot
        ) UNION
        SELECT * FROM metabib.staged_browse(
            forward_query, search_field, context_org, context_locations,
            staff, browse_superpage_size, FALSE, forward_limit, forward_to_pivot
        ) ORDER BY row_number DESC;

END;

Function: metabib.browse(result_limit text, pivot_id text, staff integer, context_loc_group integer, context_org boolean, browse_term bigint, search_class integer)

Returns: SET OF flat_browse_entry_appearance

Language: PLPGSQL

BEGIN
    RETURN QUERY SELECT * FROM metabib.browse(
        (SELECT COALESCE(ARRAY_AGG(id), ARRAY[]::INT[])
            FROM config.metabib_field WHERE field_class = search_class),
        browse_term,
        context_org,
        context_loc_group,
        staff,
        pivot_id,
        result_limit
    );
END;

Function: metabib.browse_authority_pivot(integer[], text)

Returns: bigint

Language: SQL

    SELECT  mbe.id
      FROM  metabib.browse_entry mbe
            JOIN metabib.browse_entry_simple_heading_map mbeshm ON ( mbeshm.entry = mbe.id )
            JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
            JOIN authority.control_set_auth_field_metabib_field_map_refs map ON (
                ash.atag = map.authority_field
                AND map.metabib_field = ANY($1)
            )
      WHERE mbe.sort_value >= public.naco_normalize($2)
      ORDER BY mbe.sort_value, mbe.value LIMIT 1;

Function: metabib.browse_authority_refs_pivot(integer[], text)

Returns: bigint

Language: SQL

    SELECT  mbe.id
      FROM  metabib.browse_entry mbe
            JOIN metabib.browse_entry_simple_heading_map mbeshm ON ( mbeshm.entry = mbe.id )
            JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
            JOIN authority.control_set_auth_field_metabib_field_map_refs_only map ON (
                ash.atag = map.authority_field
                AND map.metabib_field = ANY($1)
            )
      WHERE mbe.sort_value >= public.naco_normalize($2)
      ORDER BY mbe.sort_value, mbe.value LIMIT 1;

Function: metabib.browse_bib_pivot(integer[], text)

Returns: bigint

Language: SQL

    SELECT  mbe.id
      FROM  metabib.browse_entry mbe
            JOIN metabib.browse_entry_def_map mbedm ON (
                mbedm.entry = mbe.id
                AND mbedm.def = ANY($1)
            )
      WHERE mbe.sort_value >= public.naco_normalize($2)
      ORDER BY mbe.sort_value, mbe.value LIMIT 1;

Function: metabib.browse_normalize(mapped_field text, facet_text integer)

Returns: text

Language: PLPGSQL

DECLARE
    normalizer  RECORD;
BEGIN

    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.metabib_field_index_norm_map m ON (m.norm = n.id)
          WHERE m.field = mapped_field AND m.pos < 0
          ORDER BY m.pos LOOP

            EXECUTE 'SELECT ' || normalizer.func || '(' ||
                quote_literal( facet_text ) ||
                CASE
                    WHEN normalizer.param_count > 0
                        THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
                        ELSE ''
                    END ||
                ')' INTO facet_text;

    END LOOP;

    RETURN facet_text;
END;


Function: metabib.browse_pivot(integer[], text)

Returns: bigint

Language: SQL

    SELECT  id FROM metabib.browse_entry
      WHERE id IN (
                metabib.browse_bib_pivot($1, $2),
                metabib.browse_authority_refs_pivot($1,$2) -- only look in 4xx, 5xx, 7xx of authority
            )
      ORDER BY sort_value, value LIMIT 1;

Function: metabib.compile_composite_attr(cattr_def text)

Returns: query_int

Language: PLPERLU


    use JSON::XS;

    my $json = shift;
    my $def = decode_json($json);

    die("Composite attribute definition not supplied") unless $def;

    my $_cache = (exists $_SHARED{metabib_compile_composite_attr_cache}) ? 1 : 0;

    return $_SHARED{metabib_compile_composite_attr_cache}{$json}
        if ($_cache && $_SHARED{metabib_compile_composite_attr_cache}{$json});

    sub recurse {
        my $d = shift;
        my $j = '&';
        my @list;

        if (ref $d eq 'HASH') { # node or AND
            if (exists $d->{_attr}) { # it is a node
                my $plan = spi_prepare('SELECT * FROM metabib.full_attr_id_map WHERE attr = $1 AND value = $2', qw/TEXT TEXT/);
                my $id = spi_exec_prepared(
                    $plan, {limit => 1}, $d->{_attr}, $d->{_val}
                )->{rows}[0]{id};
                spi_freeplan($plan);
                return $id;
            } elsif (exists $d->{_not} && scalar(keys(%$d)) == 1) { # it is a NOT
                return '!' . recurse($$d{_not});
            } else { # an AND list
                @list = map { recurse($$d{$_}) } sort keys %$d;
            }
        } elsif (ref $d eq 'ARRAY') {
            $j = '|';
            @list = map { recurse($_) } @$d;
        }

        @list = grep { defined && $_ ne '' } @list;

        return '(' . join($j,@list) . ')' if @list;
        return '';
    }

    my $val = recurse($def) || undef;
    $_SHARED{metabib_compile_composite_attr_cache}{$json} = $val if $_cache;
    return $val;


Function: metabib.compile_composite_attr(cattr_id integer)

Returns: query_int

Language: SQL

    SELECT metabib.compile_composite_attr(definition) FROM config.composite_attr_entry_definition WHERE coded_value = $1;

Function: metabib.compile_composite_attr_cache_disable()

Returns: boolean

Language: PLPERLU

    delete $_SHARED{metabib_compile_composite_attr_cache};
    return ! exists $_SHARED{metabib_compile_composite_attr_cache};

Function: metabib.compile_composite_attr_cache_init()

Returns: boolean

Language: PLPERLU

    $_SHARED{metabib_compile_composite_attr_cache} = {}
        if ! exists $_SHARED{metabib_compile_composite_attr_cache};
    return exists $_SHARED{metabib_compile_composite_attr_cache};

Function: metabib.compile_composite_attr_cache_invalidate()

Returns: boolean

Language: SQL

    SELECT metabib.compile_composite_attr_cache_disable() AND metabib.compile_composite_attr_cache_init();

Function: metabib.composite_attr_def_cache_inval_tgr()

Returns: trigger

Language: PLPGSQL

BEGIN
    PERFORM metabib.compile_composite_attr_cache_invalidate();
    RETURN NULL;
END;

Function: metabib.display_field_normalize_trigger()

Returns: trigger

Language: PLPGSQL

DECLARE
    normalizer  RECORD;
    display_field_text  TEXT;
BEGIN
    display_field_text := NEW.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.metabib_field_index_norm_map m ON (m.norm = n.id)
          WHERE m.field = NEW.field AND m.pos < 0
          ORDER BY m.pos LOOP

            EXECUTE 'SELECT ' || normalizer.func || '(' ||
                quote_literal( display_field_text ) ||
                CASE
                    WHEN normalizer.param_count > 0
                        THEN ',' || REPLACE(REPLACE(BTRIM(
                            normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
                        ELSE ''
                    END ||
                ')' INTO display_field_text;

    END LOOP;

    NEW.value = display_field_text;

    RETURN NEW;
END;

Function: metabib.facet_normalize_trigger()

Returns: trigger

Language: PLPGSQL

DECLARE
    normalizer  RECORD;
    facet_text  TEXT;
BEGIN
    facet_text := NEW.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.metabib_field_index_norm_map m ON (m.norm = n.id)
          WHERE m.field = NEW.field AND m.pos < 0
          ORDER BY m.pos LOOP

            EXECUTE 'SELECT ' || normalizer.func || '(' ||
                quote_literal( facet_text ) ||
                CASE
                    WHEN normalizer.param_count > 0
                        THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
                        ELSE ''
                    END ||
                ')' INTO facet_text;

    END LOOP;

    NEW.value = facet_text;

    RETURN NEW;
END;

Function: metabib.reingest_metabib_field_entries(only_fields bigint, skip_search boolean, skip_browse boolean, skip_display boolean, skip_facet boolean, bib_id integer[])

Returns: void

Language: PLPGSQL

DECLARE
    fclass          RECORD;
    ind_data        metabib.field_entry_template%ROWTYPE;
    mbe_row         metabib.browse_entry%ROWTYPE;
    mbe_id          BIGINT;
    b_skip_facet    BOOL;
    b_skip_display    BOOL;
    b_skip_browse   BOOL;
    b_skip_search   BOOL;
    value_prepped   TEXT;
    field_list      INT[] := only_fields;
    field_types     TEXT[] := '{}'::TEXT[];
BEGIN

    IF field_list = '{}'::INT[] THEN
        SELECT ARRAY_AGG(id) INTO field_list FROM config.metabib_field;
    END IF;

    SELECT COALESCE(NULLIF(skip_facet, FALSE), EXISTS (SELECT enabled FROM config.internal_flag WHERE name =  'ingest.skip_facet_indexing' AND enabled)) INTO b_skip_facet;
    SELECT COALESCE(NULLIF(skip_display, FALSE), EXISTS (SELECT enabled FROM config.internal_flag WHERE name =  'ingest.skip_display_indexing' AND enabled)) INTO b_skip_display;
    SELECT COALESCE(NULLIF(skip_browse, FALSE), EXISTS (SELECT enabled FROM config.internal_flag WHERE name =  'ingest.skip_browse_indexing' AND enabled)) INTO b_skip_browse;
    SELECT COALESCE(NULLIF(skip_search, FALSE), EXISTS (SELECT enabled FROM config.internal_flag WHERE name =  'ingest.skip_search_indexing' AND enabled)) INTO b_skip_search;

    IF NOT b_skip_facet THEN field_types := field_types || '{facet}'; END IF;
    IF NOT b_skip_display THEN field_types := field_types || '{display}'; END IF;
    IF NOT b_skip_browse THEN field_types := field_types || '{browse}'; END IF;
    IF NOT b_skip_search THEN field_types := field_types || '{search}'; END IF;

    PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
    IF NOT FOUND THEN
        IF NOT b_skip_search THEN
            FOR fclass IN SELECT * FROM config.metabib_class LOOP
                -- RAISE NOTICE 'Emptying out %', fclass.name;
                EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
            END LOOP;
        END IF;
        IF NOT b_skip_facet THEN
            DELETE FROM metabib.facet_entry WHERE source = bib_id;
        END IF;
        IF NOT b_skip_display THEN
            DELETE FROM metabib.display_entry WHERE source = bib_id;
        END IF;
        IF NOT b_skip_browse THEN
            DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
        END IF;
    END IF;

    FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id, ' ', field_types, field_list ) LOOP

	-- don't store what has been normalized away
        CONTINUE WHEN ind_data.value IS NULL;

        IF ind_data.field < 0 THEN
            ind_data.field = -1 * ind_data.field;
        END IF;

        IF ind_data.facet_field AND NOT b_skip_facet THEN
            INSERT INTO metabib.facet_entry (field, source, value)
                VALUES (ind_data.field, ind_data.source, ind_data.value);
        END IF;

        IF ind_data.display_field AND NOT b_skip_display THEN
            INSERT INTO metabib.display_entry (field, source, value)
                VALUES (ind_data.field, ind_data.source, ind_data.value);
        END IF;


        IF ind_data.browse_field AND NOT b_skip_browse THEN
            -- A caveat about this SELECT: this should take care of replacing
            -- old mbe rows when data changes, but not if normalization (by
            -- which I mean specifically the output of
            -- evergreen.oils_tsearch2()) changes.  It may or may not be
            -- expensive to add a comparison of index_vector to index_vector
            -- to the WHERE clause below.

            CONTINUE WHEN ind_data.sort_value IS NULL;

            value_prepped := metabib.browse_normalize(ind_data.value, ind_data.field);
            IF ind_data.browse_nocase THEN
                SELECT INTO mbe_row * FROM metabib.browse_entry
                    WHERE evergreen.lowercase(value) = evergreen.lowercase(value_prepped) AND sort_value = ind_data.sort_value
                    ORDER BY sort_value, value LIMIT 1; -- gotta pick something, I guess
            ELSE
                SELECT INTO mbe_row * FROM metabib.browse_entry
                    WHERE value = value_prepped AND sort_value = ind_data.sort_value;
            END IF;

            IF FOUND THEN
                mbe_id := mbe_row.id;
            ELSE
                INSERT INTO metabib.browse_entry
                    ( value, sort_value ) VALUES
                    ( value_prepped, ind_data.sort_value );

                mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
            END IF;

            INSERT INTO metabib.browse_entry_def_map (entry, def, source, authority)
                VALUES (mbe_id, ind_data.field, ind_data.source, ind_data.authority);
        END IF;

        IF ind_data.search_field AND NOT b_skip_search THEN
            -- Avoid inserting duplicate rows
            EXECUTE 'SELECT 1 FROM metabib.' || ind_data.field_class ||
                '_field_entry WHERE field = $1 AND source = $2 AND value = $3'
                INTO mbe_id USING ind_data.field, ind_data.source, ind_data.value;
                -- RAISE NOTICE 'Search for an already matching row returned %', mbe_id;
            IF mbe_id IS NULL THEN
                EXECUTE $$
                INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
                    VALUES ($$ ||
                        quote_literal(ind_data.field) || $$, $$ ||
                        quote_literal(ind_data.source) || $$, $$ ||
                        quote_literal(ind_data.value) ||
                    $$);$$;
            END IF;
        END IF;

    END LOOP;

    IF NOT b_skip_search THEN
        PERFORM metabib.update_combined_index_vectors(bib_id);
        PERFORM * FROM config.internal_flag WHERE name = 'ingest.disable_symspell_reification' AND enabled;
        IF NOT FOUND THEN
            PERFORM search.symspell_dictionary_reify();
        END IF;
    END IF;

    RETURN;
END;

Function: metabib.reingest_metabib_full_rec(bib_id bigint)

Returns: void

Language: PLPGSQL

BEGIN
    PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
    IF NOT FOUND THEN
        DELETE FROM metabib.real_full_rec WHERE record = bib_id;
    END IF;
    INSERT INTO metabib.real_full_rec (record, tag, ind1, ind2, subfield, value)
        SELECT record, tag, ind1, ind2, subfield, value FROM biblio.flatten_marc( bib_id );

    RETURN;
END;

Function: metabib.reingest_record_attributes(rdeleted bigint, prmarc text[], pattr_list text, rid boolean)

Returns: void

Language: PLPGSQL

DECLARE
    transformed_xml TEXT;
    rmarc           TEXT := prmarc;
    tmp_val         TEXT;
    prev_xfrm       TEXT;
    normalizer      RECORD;
    xfrm            config.xml_transform%ROWTYPE;
    attr_vector     INT[] := '{}'::INT[];
    attr_vector_tmp INT[];
    attr_list       TEXT[] := pattr_list;
    attr_value      TEXT[];
    norm_attr_value TEXT[];
    tmp_xml         TEXT;
    tmp_array       TEXT[];
    attr_def        config.record_attr_definition%ROWTYPE;
    ccvm_row        config.coded_value_map%ROWTYPE;
    jump_past       BOOL;
BEGIN

    IF attr_list IS NULL OR rdeleted THEN -- need to do the full dance on INSERT or undelete
        SELECT ARRAY_AGG(name) INTO attr_list FROM config.record_attr_definition
        WHERE (
            tag IS NOT NULL OR
            fixed_field IS NOT NULL OR
            xpath IS NOT NULL OR
            phys_char_sf IS NOT NULL OR
            composite
        ) AND (
            filter OR sorter
        );
    END IF;

    IF rmarc IS NULL THEN
        SELECT marc INTO rmarc FROM biblio.record_entry WHERE id = rid;
    END IF;

    FOR attr_def IN SELECT * FROM config.record_attr_definition WHERE NOT composite AND name = ANY( attr_list ) ORDER BY format LOOP

        jump_past := FALSE; -- This gets set when we are non-multi and have found something
        attr_value := '{}'::TEXT[];
        norm_attr_value := '{}'::TEXT[];
        attr_vector_tmp := '{}'::INT[];

        SELECT * INTO ccvm_row FROM config.coded_value_map c WHERE c.ctype = attr_def.name LIMIT 1; 

        IF attr_def.tag IS NOT NULL THEN -- tag (and optional subfield list) selection
            SELECT  ARRAY_AGG(value) INTO attr_value
              FROM  (SELECT * FROM metabib.full_rec ORDER BY tag, subfield) AS x
              WHERE record = rid
                    AND tag LIKE attr_def.tag
                    AND CASE
                        WHEN attr_def.sf_list IS NOT NULL 
                            THEN POSITION(subfield IN attr_def.sf_list) > 0
                        ELSE TRUE
                    END
              GROUP BY tag
              ORDER BY tag;

            IF NOT attr_def.multi THEN
                attr_value := ARRAY[ARRAY_TO_STRING(attr_value, COALESCE(attr_def.joiner,' '))];
                jump_past := TRUE;
            END IF;
        END IF;

        IF NOT jump_past AND attr_def.fixed_field IS NOT NULL THEN -- a named fixed field, see config.marc21_ff_pos_map.fixed_field
            attr_value := attr_value || vandelay.marc21_extract_fixed_field_list(rmarc, attr_def.fixed_field);

            IF NOT attr_def.multi THEN
                attr_value := ARRAY[attr_value[1]];
                jump_past := TRUE;
            END IF;
        END IF;

        IF NOT jump_past AND 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(rmarc,xfrm.xslt);
                ELSE
                    transformed_xml := rmarc;
                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;

            FOR tmp_xml IN SELECT UNNEST(oils_xpath(attr_def.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]])) LOOP
                tmp_val := oils_xpath_string(
                                '//*',
                                tmp_xml,
                                COALESCE(attr_def.joiner,' '),
                                ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
                            );
                IF tmp_val IS NOT NULL AND BTRIM(tmp_val) <> '' THEN
                    attr_value := attr_value || tmp_val;
                    EXIT WHEN NOT attr_def.multi;
                END IF;
            END LOOP;
        END IF;

        IF NOT jump_past AND attr_def.phys_char_sf IS NOT NULL THEN -- a named Physical Characteristic, see config.marc21_physical_characteristic_*_map
            SELECT  ARRAY_AGG(m.value) INTO tmp_array
              FROM  vandelay.marc21_physical_characteristics(rmarc) v
                    LEFT JOIN config.marc21_physical_characteristic_value_map m ON (m.id = v.value)
              WHERE v.subfield = attr_def.phys_char_sf AND (m.value IS NOT NULL AND BTRIM(m.value) <> '')
                    AND ( ccvm_row.id IS NULL OR ( ccvm_row.id IS NOT NULL AND v.id IS NOT NULL) );

            attr_value := attr_value || tmp_array;

            IF NOT attr_def.multi THEN
                attr_value := ARRAY[attr_value[1]];
            END IF;

        END IF;

                -- apply index normalizers to attr_value
        FOR tmp_val IN SELECT value FROM UNNEST(attr_value) x(value) LOOP
            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 || '(' ||
                    COALESCE( quote_literal( tmp_val ), 'NULL' ) ||
                        CASE
                            WHEN normalizer.param_count > 0
                                THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
                                ELSE ''
                            END ||
                    ')' INTO tmp_val;

            END LOOP;
            IF tmp_val IS NOT NULL AND tmp_val <> '' THEN
                -- note that a string that contains only blanks
                -- is a valid value for some attributes
                norm_attr_value := norm_attr_value || tmp_val;
            END IF;
        END LOOP;
        
        IF attr_def.filter THEN
            -- Create unknown uncontrolled values and find the IDs of the values
            IF ccvm_row.id IS NULL THEN
                FOR tmp_val IN SELECT value FROM UNNEST(norm_attr_value) x(value) LOOP
                    IF tmp_val IS NOT NULL AND BTRIM(tmp_val) <> '' THEN
                        BEGIN -- use subtransaction to isolate unique constraint violations
                            INSERT INTO metabib.uncontrolled_record_attr_value ( attr, value ) VALUES ( attr_def.name, tmp_val );
                        EXCEPTION WHEN unique_violation THEN END;
                    END IF;
                END LOOP;

                SELECT ARRAY_AGG(id) INTO attr_vector_tmp FROM metabib.uncontrolled_record_attr_value WHERE attr = attr_def.name AND value = ANY( norm_attr_value );
            ELSE
                SELECT ARRAY_AGG(id) INTO attr_vector_tmp FROM config.coded_value_map WHERE ctype = attr_def.name AND code = ANY( norm_attr_value );
            END IF;

            -- Add the new value to the vector
            attr_vector := attr_vector || attr_vector_tmp;
        END IF;

        IF attr_def.sorter THEN
            DELETE FROM metabib.record_sorter WHERE source = rid AND attr = attr_def.name;
            IF norm_attr_value[1] IS NOT NULL THEN
                INSERT INTO metabib.record_sorter (source, attr, value) VALUES (rid, attr_def.name, norm_attr_value[1]);
            END IF;
        END IF;

    END LOOP;

/* We may need to rewrite the vlist to contain
   the intersection of new values for requested
   attrs and old values for ignored attrs. To
   do this, we take the old attr vlist and
   subtract any values that are valid for the
   requested attrs, and then add back the new
   set of attr values. */

    IF ARRAY_LENGTH(pattr_list, 1) > 0 THEN 
        SELECT vlist INTO attr_vector_tmp FROM metabib.record_attr_vector_list WHERE source = rid;
        SELECT attr_vector_tmp - ARRAY_AGG(id::INT) INTO attr_vector_tmp FROM metabib.full_attr_id_map WHERE attr = ANY (pattr_list);
        attr_vector := attr_vector || attr_vector_tmp;
    END IF;

    -- On to composite attributes, now that the record attrs have been pulled.  Processed in name order, so later composite
    -- attributes can depend on earlier ones.
    PERFORM metabib.compile_composite_attr_cache_init();
    FOR attr_def IN SELECT * FROM config.record_attr_definition WHERE composite AND name = ANY( attr_list ) ORDER BY name LOOP

        FOR ccvm_row IN SELECT * FROM config.coded_value_map c WHERE c.ctype = attr_def.name ORDER BY value LOOP

            tmp_val := metabib.compile_composite_attr( ccvm_row.id );
            CONTINUE WHEN tmp_val IS NULL OR tmp_val = ''; -- nothing to do

            IF attr_def.filter THEN
                IF attr_vector @@ tmp_val::query_int THEN
                    attr_vector = attr_vector + intset(ccvm_row.id);
                    EXIT WHEN NOT attr_def.multi;
                END IF;
            END IF;

            IF attr_def.sorter THEN
                IF attr_vector @@ tmp_val THEN
                    DELETE FROM metabib.record_sorter WHERE source = rid AND attr = attr_def.name;
                    INSERT INTO metabib.record_sorter (source, attr, value) VALUES (rid, attr_def.name, ccvm_row.code);
                END IF;
            END IF;

        END LOOP;

    END LOOP;

    IF ARRAY_LENGTH(attr_vector, 1) > 0 THEN
        IF rdeleted THEN -- initial insert OR revivication
            DELETE FROM metabib.record_attr_vector_list WHERE source = rid;
            INSERT INTO metabib.record_attr_vector_list (source, vlist) VALUES (rid, attr_vector);
        ELSE
            UPDATE metabib.record_attr_vector_list SET vlist = attr_vector WHERE source = rid;
        END IF;
    END IF;

END;


Function: metabib.remap_metarecord_for_bib(retain_deleted bigint, bib_is_deleted text, fp boolean, bib_id boolean)

Returns: bigint

Language: PLPGSQL

DECLARE
    new_mapping     BOOL := TRUE;
    source_count    INT;
    old_mr          BIGINT;
    tmp_mr          metabib.metarecord%ROWTYPE;
    deleted_mrs     BIGINT[];
BEGIN

    -- We need to make sure we're not a deleted master record of an MR
    IF bib_is_deleted THEN
        IF NOT retain_deleted THEN -- Go away for any MR that we're master of, unless retained
            DELETE FROM metabib.metarecord_source_map WHERE source = bib_id;
        END IF;

        FOR old_mr IN SELECT id FROM metabib.metarecord WHERE master_record = bib_id LOOP

            -- Now, are there any more sources on this MR?
            SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = old_mr;

            IF source_count = 0 AND NOT retain_deleted THEN -- No other records
                deleted_mrs := ARRAY_APPEND(deleted_mrs, old_mr); -- Just in case...
                DELETE FROM metabib.metarecord WHERE id = old_mr;

            ELSE -- indeed there are. Update it with a null cache and recalcualated master record
                UPDATE  metabib.metarecord
                  SET   mods = NULL,
                        master_record = (SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC, id ASC LIMIT 1)
                  WHERE id = old_mr;
            END IF;
        END LOOP;

    ELSE -- insert or update

        FOR tmp_mr IN SELECT m.* FROM metabib.metarecord m JOIN metabib.metarecord_source_map s ON (s.metarecord = m.id) WHERE s.source = bib_id LOOP

            -- Find the first fingerprint-matching
            IF old_mr IS NULL AND fp = tmp_mr.fingerprint THEN
                old_mr := tmp_mr.id;
                new_mapping := FALSE;

            ELSE -- Our fingerprint changed ... maybe remove the old MR
                DELETE FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id AND source = bib_id; -- remove the old source mapping
                SELECT COUNT(*) INTO source_count FROM metabib.metarecord_source_map WHERE metarecord = tmp_mr.id;
                IF source_count = 0 THEN -- No other records
                    deleted_mrs := ARRAY_APPEND(deleted_mrs, tmp_mr.id);
                    DELETE FROM metabib.metarecord WHERE id = tmp_mr.id;
                END IF;
            END IF;

        END LOOP;

        -- we found no suitable, preexisting MR based on old source maps
        IF old_mr IS NULL THEN
            SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp; -- is there one for our current fingerprint?

            IF old_mr IS NULL THEN -- nope, create one and grab its id
                INSERT INTO metabib.metarecord ( fingerprint, master_record ) VALUES ( fp, bib_id );
                SELECT id INTO old_mr FROM metabib.metarecord WHERE fingerprint = fp;

            ELSE -- indeed there is. update it with a null cache and recalcualated master record
                UPDATE  metabib.metarecord
                  SET   mods = NULL,
                        master_record = (SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC, id ASC LIMIT 1)
                  WHERE id = old_mr;
            END IF;

        ELSE -- there was one we already attached to, update its mods cache and master_record
            UPDATE  metabib.metarecord
              SET   mods = NULL,
                    master_record = (SELECT id FROM biblio.record_entry WHERE fingerprint = fp AND NOT deleted ORDER BY quality DESC, id ASC LIMIT 1)
              WHERE id = old_mr;
        END IF;

        IF new_mapping THEN
            INSERT INTO metabib.metarecord_source_map (metarecord, source) VALUES (old_mr, bib_id); -- new source mapping
        END IF;

    END IF;

    IF ARRAY_UPPER(deleted_mrs,1) > 0 THEN
        UPDATE action.hold_request SET target = old_mr WHERE target IN ( SELECT unnest(deleted_mrs) ) AND hold_type = 'M'; -- if we had to delete any MRs above, make sure their holds are moved
    END IF;

    RETURN old_mr;

END;

Function: metabib.search_class_to_registered_components(search_class text)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    search_parts        TEXT[];
    field_name          TEXT;
    search_part_count   INTEGER;
    rec                 RECORD;
    registered_class    config.metabib_class%ROWTYPE;
    registered_alias    config.metabib_search_alias%ROWTYPE;
    registered_field    config.metabib_field%ROWTYPE;
BEGIN
    search_parts := REGEXP_SPLIT_TO_ARRAY(search_class, E'\\|');

    search_part_count := ARRAY_LENGTH(search_parts, 1);
    IF search_part_count = 0 THEN
        RETURN;
    ELSE
        SELECT INTO registered_class
            * FROM config.metabib_class WHERE name = search_parts[1];
        IF FOUND THEN
            IF search_part_count < 2 THEN   -- all fields
                rec := (registered_class.name, NULL::INTEGER);
                RETURN NEXT rec;
                RETURN; -- done
            END IF;
            FOR field_name IN SELECT *
                FROM UNNEST(search_parts[2:search_part_count]) LOOP
                SELECT INTO registered_field
                    * FROM config.metabib_field
                    WHERE name = field_name AND
                        field_class = registered_class.name;
                IF FOUND THEN
                    rec := (registered_class.name, registered_field.id);
                    RETURN NEXT rec;
                END IF;
            END LOOP;
        ELSE
            -- maybe we have an alias?
            SELECT INTO registered_alias
                * FROM config.metabib_search_alias WHERE alias=search_parts[1];
            IF NOT FOUND THEN
                RETURN;
            ELSE
                IF search_part_count < 2 THEN   -- return w/e the alias says
                    rec := (
                        registered_alias.field_class, registered_alias.field
                    );
                    RETURN NEXT rec;
                    RETURN; -- done
                ELSE
                    FOR field_name IN SELECT *
                        FROM UNNEST(search_parts[2:search_part_count]) LOOP
                        SELECT INTO registered_field
                            * FROM config.metabib_field
                            WHERE name = field_name AND
                                field_class = registered_alias.field_class;
                        IF FOUND THEN
                            rec := (
                                registered_alias.field_class,
                                registered_field.id
                            );
                            RETURN NEXT rec;
                        END IF;
                    END LOOP;
                END IF;
            END IF;
        END IF;
    END IF;
END;

Function: metabib.staged_browse(next_pivot_pos text, result_limit integer[], count_up_from_zero integer, browse_superpage_size integer[], staff boolean, context_locations integer, context_org boolean, fields integer, query integer)

Returns: SET OF flat_browse_entry_appearance

Language: PLPGSQL

DECLARE
    curs                    REFCURSOR;
    rec                     RECORD;
    qpfts_query             TEXT;
    aqpfts_query            TEXT;
    afields                 INT[];
    bfields                 INT[];
    result_row              metabib.flat_browse_entry_appearance%ROWTYPE;
    results_skipped         INT := 0;
    row_counter             INT := 0;
    row_number              INT;
    slice_start             INT;
    slice_end               INT;
    full_end                INT;
    all_records             BIGINT[];
    all_brecords             BIGINT[];
    all_arecords            BIGINT[];
    superpage_of_records    BIGINT[];
    superpage_size          INT;
    c_tests                 TEXT := '';
    b_tests                 TEXT := '';
    c_orgs                  INT[];
    unauthorized_entry      RECORD;
BEGIN
    IF count_up_from_zero THEN
        row_number := 0;
    ELSE
        row_number := -1;
    END IF;

    IF NOT staff THEN
        SELECT x.c_attrs, x.b_attrs INTO c_tests, b_tests FROM asset.patron_default_visibility_mask() x;
    END IF;

    -- b_tests supplies its own query_int operator, c_tests does not
    IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF;

    SELECT ARRAY_AGG(id) INTO c_orgs FROM actor.org_unit_descendants(context_org);

    c_tests := c_tests || search.calculate_visibility_attribute_test('circ_lib',c_orgs)
               || '&' || search.calculate_visibility_attribute_test('owning_lib',c_orgs);

    PERFORM 1 FROM config.internal_flag WHERE enabled AND name = 'opac.located_uri.act_as_copy';
    IF FOUND THEN
        b_tests := b_tests || search.calculate_visibility_attribute_test(
            'luri_org',
            (SELECT ARRAY_AGG(id) FROM actor.org_unit_full_path(context_org) x)
        );
    ELSE
        b_tests := b_tests || search.calculate_visibility_attribute_test(
            'luri_org',
            (SELECT ARRAY_AGG(id) FROM actor.org_unit_ancestors(context_org) x)
        );
    END IF;

    IF context_locations THEN
        IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF;
        c_tests := c_tests || search.calculate_visibility_attribute_test('location',context_locations);
    END IF;

    OPEN curs NO SCROLL FOR EXECUTE query;

    LOOP
        FETCH curs INTO rec;
        IF NOT FOUND THEN
            IF result_row.pivot_point IS NOT NULL THEN
                RETURN NEXT result_row;
            END IF;
            RETURN;
        END IF;

        --Is unauthorized?
        SELECT INTO unauthorized_entry *
        FROM metabib.browse_entry_simple_heading_map mbeshm
        INNER JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
        INNER JOIN authority.control_set_authority_field acsaf ON ( acsaf.id = ash.atag )
        JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field)
        WHERE mbeshm.entry = rec.id
        AND   ahf.heading_purpose = 'variant';

        -- Gather aggregate data based on the MBE row we're looking at now, authority axis
        IF (unauthorized_entry.record IS NOT NULL) THEN
            --unauthorized term belongs to an auth linked to a bib?
            SELECT INTO all_arecords, result_row.sees, afields
                    ARRAY_AGG(DISTINCT abl.bib),
                    STRING_AGG(DISTINCT abl.authority::TEXT, $$,$$),
                    ARRAY_AGG(DISTINCT map.metabib_field)
            FROM authority.bib_linking abl
            INNER JOIN authority.control_set_auth_field_metabib_field_map_refs map ON (
                    map.authority_field = unauthorized_entry.atag
                    AND map.metabib_field = ANY(fields)
            )
            WHERE abl.authority = unauthorized_entry.record;
        ELSE
            --do usual procedure
            SELECT INTO all_arecords, result_row.sees, afields
                    ARRAY_AGG(DISTINCT abl.bib), -- bibs to check for visibility
                    STRING_AGG(DISTINCT aal.source::TEXT, $$,$$), -- authority record ids
                    ARRAY_AGG(DISTINCT map.metabib_field) -- authority-tag-linked CMF rows

            FROM  metabib.browse_entry_simple_heading_map mbeshm
                    JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
                    JOIN authority.authority_linking aal ON ( ash.record = aal.source )
                    JOIN authority.bib_linking abl ON ( aal.target = abl.authority )
                    JOIN authority.control_set_auth_field_metabib_field_map_refs map ON (
                        ash.atag = map.authority_field
                        AND map.metabib_field = ANY(fields)
                    )
                    JOIN authority.control_set_authority_field acsaf ON (
                        map.authority_field = acsaf.id
                    )
                    JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field)
              WHERE mbeshm.entry = rec.id
              AND   ahf.heading_purpose = 'variant';

        END IF;

        -- Gather aggregate data based on the MBE row we're looking at now, bib axis
        SELECT INTO all_brecords, result_row.authorities, bfields
                ARRAY_AGG(DISTINCT source),
                STRING_AGG(DISTINCT authority::TEXT, $$,$$),
                ARRAY_AGG(DISTINCT def)
          FROM  metabib.browse_entry_def_map
          WHERE entry = rec.id
                AND def = ANY(fields);

        SELECT INTO result_row.fields STRING_AGG(DISTINCT x::TEXT, $$,$$) FROM UNNEST(afields || bfields) x;

        result_row.sources := 0;
        result_row.asources := 0;

        -- Bib-linked vis checking
        IF ARRAY_UPPER(all_brecords,1) IS NOT NULL THEN

            SELECT  INTO result_row.sources COUNT(DISTINCT b.id)
              FROM  biblio.record_entry b
                    LEFT JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id)
              WHERE b.id = ANY(all_brecords[1:browse_superpage_size])
                    AND (
                        acvac.vis_attr_vector @@ c_tests::query_int
                        OR b.vis_attr_vector @@ b_tests::query_int
                    );

            result_row.accurate := TRUE;

        END IF;

        -- Authority-linked vis checking
        IF ARRAY_UPPER(all_arecords,1) IS NOT NULL THEN

            SELECT  INTO result_row.asources COUNT(DISTINCT b.id)
              FROM  biblio.record_entry b
                    LEFT JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id)
              WHERE b.id = ANY(all_arecords[1:browse_superpage_size])
                    AND (
                        acvac.vis_attr_vector @@ c_tests::query_int
                        OR b.vis_attr_vector @@ b_tests::query_int
                    );

            result_row.aaccurate := TRUE;

        END IF;

        IF result_row.sources > 0 OR result_row.asources > 0 THEN

            -- The function that calls this function needs row_number in order
            -- to correctly order results from two different runs of this
            -- functions.
            result_row.row_number := row_number;

            -- Now, if row_counter is still less than limit, return a row.  If
            -- not, but it is less than next_pivot_pos, continue on without
            -- returning actual result rows until we find
            -- that next pivot, and return it.

            IF row_counter < result_limit THEN
                result_row.browse_entry := rec.id;
                result_row.value := rec.value;

                RETURN NEXT result_row;
            ELSE
                result_row.browse_entry := NULL;
                result_row.authorities := NULL;
                result_row.fields := NULL;
                result_row.value := NULL;
                result_row.sources := NULL;
                result_row.sees := NULL;
                result_row.accurate := NULL;
                result_row.aaccurate := NULL;
                result_row.pivot_point := rec.id;

                IF row_counter >= next_pivot_pos THEN
                    RETURN NEXT result_row;
                    RETURN;
                END IF;
            END IF;

            IF count_up_from_zero THEN
                row_number := row_number + 1;
            ELSE
                row_number := row_number - 1;
            END IF;

            -- row_counter is different from row_number.
            -- It simply counts up from zero so that we know when
            -- we've reached our limit.
            row_counter := row_counter + 1;
        END IF;
    END LOOP;
END;

Function: metabib.suggest_browse_entries(match text, buoyant text, rank text, field_weight integer, field_match integer, buoyant_and_class_match integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    prepared_query_texts    TEXT[];
    query                   TSQUERY;
    plain_query             TSQUERY;
    opac_visibility_join    TEXT;
    search_class_join       TEXT;
    r_fields                RECORD;
    b_tests                 TEXT := '';
BEGIN
    prepared_query_texts := metabib.autosuggest_prepare_tsquery(raw_query_text);

    query := TO_TSQUERY('keyword', prepared_query_texts[1]);
    plain_query := TO_TSQUERY('keyword', prepared_query_texts[2]);

    visibility_org := NULLIF(visibility_org,-1);
    IF visibility_org IS NOT NULL THEN
        PERFORM FROM actor.org_unit WHERE id = visibility_org AND parent_ou IS NULL;
        IF FOUND THEN
            opac_visibility_join := '';
        ELSE
            PERFORM 1 FROM config.internal_flag WHERE enabled AND name = 'opac.located_uri.act_as_copy';
            IF FOUND THEN
                b_tests := search.calculate_visibility_attribute_test(
                    'luri_org',
                    (SELECT ARRAY_AGG(id) FROM actor.org_unit_full_path(visibility_org))
                );
            ELSE
                b_tests := search.calculate_visibility_attribute_test(
                    'luri_org',
                    (SELECT ARRAY_AGG(id) FROM actor.org_unit_ancestors(visibility_org))
                );
            END IF;
            opac_visibility_join := '
    LEFT JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = x.source)
    LEFT JOIN biblio.record_entry b ON (b.id = x.source)
    JOIN vm ON (acvac.vis_attr_vector @@
            (vm.c_attrs || $$&$$ ||
                search.calculate_visibility_attribute_test(
                    $$circ_lib$$,
                    (SELECT ARRAY_AGG(id) FROM actor.org_unit_descendants($4))
                )
            )::query_int
         ) OR (b.vis_attr_vector @@ $$' || b_tests || '$$::query_int)
';
        END IF;
    ELSE
        opac_visibility_join := '';
    END IF;

    -- The following determines whether we only provide suggestsons matching
    -- the user's selected search_class, or whether we show other suggestions
    -- too. The reason for MIN() is that for search_classes like
    -- 'title|proper|uniform' you would otherwise get multiple rows.  The
    -- implication is that if title as a class doesn't have restrict,
    -- nor does the proper field, but the uniform field does, you're going
    -- to get 'false' for your overall evaluation of 'should we restrict?'
    -- To invert that, change from MIN() to MAX().

    SELECT
        INTO r_fields
            MIN(cmc.restrict::INT) AS restrict_class,
            MIN(cmf.restrict::INT) AS restrict_field
        FROM metabib.search_class_to_registered_components(search_class)
            AS _registered (field_class TEXT, field INT)
        JOIN
            config.metabib_class cmc ON (cmc.name = _registered.field_class)
        LEFT JOIN
            config.metabib_field cmf ON (cmf.id = _registered.field);

    -- evaluate 'should we restrict?'
    IF r_fields.restrict_field::BOOL OR r_fields.restrict_class::BOOL THEN
        search_class_join := '
    JOIN
        metabib.search_class_to_registered_components($2)
        AS _registered (field_class TEXT, field INT) ON (
            (_registered.field IS NULL AND
                _registered.field_class = cmf.field_class) OR
            (_registered.field = cmf.id)
        )
    ';
    ELSE
        search_class_join := '
    LEFT JOIN
        metabib.search_class_to_registered_components($2)
        AS _registered (field_class TEXT, field INT) ON (
            _registered.field_class = cmc.name
        )
    ';
    END IF;

    RETURN QUERY EXECUTE '
WITH vm AS ( SELECT * FROM asset.patron_default_visibility_mask() ),
     mbe AS (SELECT * FROM metabib.browse_entry WHERE index_vector @@ $1 LIMIT 10000)
SELECT  DISTINCT
        x.value,
        x.id,
        x.push,
        x.restrict,
        x.weight,
        x.ts_rank_cd,
        x.buoyant,
        TS_HEADLINE(value, $7, $3)
  FROM  (SELECT DISTINCT
                mbe.value,
                cmf.id,
                cmc.buoyant AND _registered.field_class IS NOT NULL AS push,
                _registered.field = cmf.id AS restrict,
                cmf.weight,
                TS_RANK_CD(mbe.index_vector, $1, $6),
                cmc.buoyant,
                mbedm.source
          FROM  metabib.browse_entry_def_map mbedm
                JOIN mbe ON (mbe.id = mbedm.entry)
                JOIN config.metabib_field cmf ON (cmf.id = mbedm.def)
                JOIN config.metabib_class cmc ON (cmf.field_class = cmc.name)
                '  || search_class_join || '
          ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
          LIMIT 1000) AS x
        ' || opac_visibility_join || '
  ORDER BY 3 DESC, 4 DESC NULLS LAST, 5 DESC, 6 DESC, 7 DESC, 1 ASC
  LIMIT $5
'   -- sic, repeat the order by clause in the outer select too
    USING
        query, search_class, headline_opts,
        visibility_org, query_limit, normalization, plain_query
        ;

    -- sort order:
    --  buoyant AND chosen class = match class
    --  chosen field = match field
    --  field weight
    --  rank
    --  buoyancy
    --  value itself

END;

Function: metabib.trim_trailing_punctuation(text)

Returns: text

Language: PLPGSQL

DECLARE
    result    TEXT;
    last_char TEXT;
BEGIN
    result := $1;
    last_char = substring(result from '.$');

    IF last_char = ',' THEN
        result := substring(result from '^(.*),$');

    ELSIF last_char = '.' THEN
        -- must have a single word-character following at least one non-word character
        IF substring(result from '\W\w\.$') IS NULL THEN
            result := substring(result from '^(.*)\.$');
        END IF;

    ELSIF last_char IN ('/',':',';','=') THEN -- Dangling subtitle/SoR separator
        IF substring(result from ' .$') IS NOT NULL THEN -- must have a space before last_char
            result := substring(result from '^(.*) .$');
        END IF;
    END IF;

    RETURN result;

END;

Function: metabib.update_combined_index_vectors(bib_id bigint)

Returns: void

Language: PLPGSQL

DECLARE
    rdata       TSVECTOR;
    vclass      TEXT;
    vfield      INT;
    rfields     INT[];
BEGIN
    DELETE FROM metabib.combined_keyword_field_entry WHERE record = bib_id;
    INSERT INTO metabib.combined_keyword_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.keyword_field_entry WHERE source = bib_id GROUP BY field;
    INSERT INTO metabib.combined_keyword_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.keyword_field_entry WHERE source = bib_id;

    DELETE FROM metabib.combined_title_field_entry WHERE record = bib_id;
    INSERT INTO metabib.combined_title_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.title_field_entry WHERE source = bib_id GROUP BY field;
    INSERT INTO metabib.combined_title_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.title_field_entry WHERE source = bib_id;

    DELETE FROM metabib.combined_author_field_entry WHERE record = bib_id;
    INSERT INTO metabib.combined_author_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.author_field_entry WHERE source = bib_id GROUP BY field;
    INSERT INTO metabib.combined_author_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.author_field_entry WHERE source = bib_id;

    DELETE FROM metabib.combined_subject_field_entry WHERE record = bib_id;
    INSERT INTO metabib.combined_subject_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.subject_field_entry WHERE source = bib_id GROUP BY field;
    INSERT INTO metabib.combined_subject_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.subject_field_entry WHERE source = bib_id;

    DELETE FROM metabib.combined_series_field_entry WHERE record = bib_id;
    INSERT INTO metabib.combined_series_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.series_field_entry WHERE source = bib_id GROUP BY field;
    INSERT INTO metabib.combined_series_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.series_field_entry WHERE source = bib_id;

    DELETE FROM metabib.combined_identifier_field_entry WHERE record = bib_id;
    INSERT INTO metabib.combined_identifier_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, field, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.identifier_field_entry WHERE source = bib_id GROUP BY field;
    INSERT INTO metabib.combined_identifier_field_entry(record, metabib_field, index_vector)
        SELECT bib_id, NULL, strip(COALESCE(string_agg(index_vector::TEXT,' '),'')::tsvector)
        FROM metabib.identifier_field_entry WHERE source = bib_id;

    -- For each virtual def, gather the data from the combined real field
    -- entries and append it to the virtual combined entry.
    FOR vfield, rfields IN SELECT virtual, ARRAY_AGG(real)  FROM config.metabib_field_virtual_map GROUP BY virtual LOOP
        SELECT  field_class INTO vclass
          FROM  config.metabib_field
          WHERE id = vfield;

        SELECT  string_agg(index_vector::TEXT,' ')::tsvector INTO rdata
          FROM  metabib.combined_all_field_entry
          WHERE record = bib_id
                AND metabib_field = ANY (rfields);

        BEGIN -- I cannot wait for INSERT ON CONFLICT ... 9.5, though
            EXECUTE $$
                INSERT INTO metabib.combined_$$ || vclass || $$_field_entry
                    (record, metabib_field, index_vector) VALUES ($1, $2, $3)
            $$ USING bib_id, vfield, rdata;
        EXCEPTION WHEN unique_violation THEN
            EXECUTE $$
                UPDATE  metabib.combined_$$ || vclass || $$_field_entry
                  SET   index_vector = index_vector || $3
                  WHERE record = $1
                        AND metabib_field = $2
            $$ USING bib_id, vfield, rdata;
        WHEN OTHERS THEN
            -- ignore and move on
        END;
    END LOOP;
END;

Schema money


Table: money.account_adjustment

money.account_adjustment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL
money.billing.id billing bigint

Table money.account_adjustment Inherits bnm_payment,

money_account_adjustment_accepting_usr_idx accepting_usr money_account_adjustment_bill_idx billing money_account_adjustment_payment_ts_idx payment_ts money_account_adjustment_xact_idx xact money_adjustment_id_idx id

Index - Schema money


Table: money.aged_billing

money.aged_billing Structure
F-Key Name Type Description
id bigint PRIMARY KEY
xact bigint NOT NULL
billing_ts timestamp with time zone NOT NULL
voided boolean NOT NULL
voider integer
void_time timestamp with time zone
amount numeric(6,2) NOT NULL
billing_type text NOT NULL
btype integer NOT NULL
note text
create_date timestamp with time zone NOT NULL
period_start timestamp with time zone
period_end timestamp with time zone
aged_billing_billing_ts_idx billing_ts aged_billing_create_date_idx create_date aged_billing_period_end_idx period_end aged_billing_period_start_idx period_start aged_billing_voider_idx voider aged_billing_xact_idx xact

Index - Schema money


Table: money.aged_payment

money.aged_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL
voided boolean NOT NULL
amount numeric(6,2) NOT NULL
note text
payment_type text NOT NULL
accepting_usr integer
cash_drawer integer
billing bigint
aged_payment_accepting_usr_idx accepting_usr aged_payment_billing_idx billing aged_payment_cash_drawer_idx cash_drawer aged_payment_payment_ts_idx payment_ts aged_payment_xact_idx xact

Index - Schema money


View: money.all_billings

money.all_billings Structure
F-Key Name Type Description
id bigint
xact bigint
billing_ts timestamp with time zone
voided boolean
voider integer
void_time timestamp with time zone
amount numeric(6,2)
billing_type text
btype integer
note text
create_date timestamp with time zone
period_start timestamp with time zone
period_end timestamp with time zone
SELECT billing.id
,
    billing.xact
,
    billing.billing_ts
,
    billing.voided
,
    billing.voider
,
    billing.void_time
,
    billing.amount
,
    billing.billing_type
,
    billing.btype
,
    billing.note
,
    billing.create_date
,
    billing.period_start
,
    billing.period_end
   
FROM money.billing

UNION ALL
 
SELECT aged_billing.id
,
    aged_billing.xact
,
    aged_billing.billing_ts
,
    aged_billing.voided
,
    aged_billing.voider
,
    aged_billing.void_time
,
    aged_billing.amount
,
    aged_billing.billing_type
,
    aged_billing.btype
,
    aged_billing.note
,
    aged_billing.create_date
,
    aged_billing.period_start
,
    aged_billing.period_end
   
FROM money.aged_billing;

Index - Schema money


View: money.all_payments

money.all_payments Structure
F-Key Name Type Description
id bigint
xact bigint
payment_ts timestamp with time zone
voided boolean
amount numeric(6,2)
note text
payment_type name
accepting_usr integer
cash_drawer integer
billing bigint
SELECT payment_view_for_aging.id
,
    payment_view_for_aging.xact
,
    payment_view_for_aging.payment_ts
,
    payment_view_for_aging.voided
,
    payment_view_for_aging.amount
,
    payment_view_for_aging.note
,
    payment_view_for_aging.payment_type
,
    payment_view_for_aging.accepting_usr
,
    payment_view_for_aging.cash_drawer
,
    payment_view_for_aging.billing
   
FROM money.payment_view_for_aging

UNION ALL
 
SELECT aged_payment.id
,
    aged_payment.xact
,
    aged_payment.payment_ts
,
    aged_payment.voided
,
    aged_payment.amount
,
    aged_payment.note
,
    aged_payment.payment_type
,
    aged_payment.accepting_usr
,
    aged_payment.cash_drawer
,
    aged_payment.billing
   
FROM money.aged_payment;

Index - Schema money


Table: money.billable_xact

money.billable_xact Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id usr integer NOT NULL
xact_start timestamp with time zone NOT NULL DEFAULT now()
xact_finish timestamp with time zone
unrecovered boolean
m_b_x_open_xacts_idx usr

Index - Schema money


View: money.billable_xact_summary

money.billable_xact_summary Structure
F-Key Name Type Description
id bigint
usr integer
xact_start timestamp with time zone
xact_finish timestamp with time zone
total_paid numeric
last_payment_ts timestamp with time zone
last_payment_note text
last_payment_type name
total_owed numeric
last_billing_ts timestamp with time zone
last_billing_note text
last_billing_type text
balance_owed numeric
xact_type name
SELECT materialized_billable_xact_summary.id
,
    materialized_billable_xact_summary.usr
,
    materialized_billable_xact_summary.xact_start
,
    materialized_billable_xact_summary.xact_finish
,
    materialized_billable_xact_summary.total_paid
,
    materialized_billable_xact_summary.last_payment_ts
,
    materialized_billable_xact_summary.last_payment_note
,
    materialized_billable_xact_summary.last_payment_type
,
    materialized_billable_xact_summary.total_owed
,
    materialized_billable_xact_summary.last_billing_ts
,
    materialized_billable_xact_summary.last_billing_note
,
    materialized_billable_xact_summary.last_billing_type
,
    materialized_billable_xact_summary.balance_owed
,
    materialized_billable_xact_summary.xact_type
   
FROM money.materialized_billable_xact_summary;

Index - Schema money


View: money.billable_xact_summary_location_view

money.billable_xact_summary_location_view Structure
F-Key Name Type Description
id bigint
usr integer
xact_start timestamp with time zone
xact_finish timestamp with time zone
total_paid numeric
last_payment_ts timestamp with time zone
last_payment_note text
last_payment_type name
total_owed numeric
last_billing_ts timestamp with time zone
last_billing_note text
last_billing_type text
balance_owed numeric
xact_type name
billing_location integer
SELECT m.id
,
    m.usr
,
    m.xact_start
,
    m.xact_finish
,
    m.total_paid
,
    m.last_payment_ts
,
    m.last_payment_note
,
    m.last_payment_type
,
    m.total_owed
,
    m.last_billing_ts
,
    m.last_billing_note
,
    m.last_billing_type
,
    m.balance_owed
,
    m.xact_type
,
    COALESCE
(c.circ_lib
     , g.billing_location
     , r.pickup_lib
) AS billing_location
   
FROM (
     (
           (money.materialized_billable_xact_summary m
     
         LEFT JOIN action.circulation c 
                ON (
                       (c.id = m.id)
                 )
           )
     
   LEFT JOIN money.grocery g 
          ON (
                 (g.id = m.id)
           )
     )
     
LEFT JOIN booking.reservation r 
    ON (
           (r.id = m.id)
     )
);

Index - Schema money


View: money.billable_xact_with_void_summary

money.billable_xact_with_void_summary Structure
F-Key Name Type Description
id bigint
usr integer
xact_start timestamp with time zone
xact_finish timestamp with time zone
total_paid numeric
last_payment_ts timestamp with time zone
last_payment_note text
last_payment_type name
total_owed numeric
last_billing_ts timestamp with time zone
last_billing_note text
last_billing_type text
balance_owed numeric
xact_type name
SELECT xact.id
,
    xact.usr
,
    xact.xact_start
,
    xact.xact_finish
,
    sum
(credit.amount) AS total_paid
,
    max
(credit.payment_ts) AS last_payment_ts
,
    last
(credit.note) AS last_payment_note
,
    last
(credit.payment_type) AS last_payment_type
,
    sum
(debit.amount) AS total_owed
,
    max
(debit.billing_ts) AS last_billing_ts
,
    last
(debit.note) AS last_billing_note
,
    last
(debit.billing_type) AS last_billing_type
,
    
(COALESCE
     (sum
           (debit.amount)
           , (0)::numeric
     ) - COALESCE
     (sum
           (credit.amount)
           , (0)::numeric
     )
) AS balance_owed
,
    p.relname AS xact_type
   
FROM (
     (
           (money.billable_xact xact
     
              JOIN pg_class p 
                ON (
                       (xact.tableoid = p.oid)
                 )
           )
     
   LEFT JOIN money.billing debit 
          ON (
                 (xact.id = debit.xact)
           )
     )
     
LEFT JOIN money.payment_view credit 
    ON (
           (xact.id = credit.xact)
     )
)
  
GROUP BY xact.id
, xact.usr
, xact.xact_start
, xact.xact_finish
, p.relname
  
ORDER BY (max
     (debit.billing_ts)
)
, (max
     (credit.payment_ts)
);

Index - Schema money


Table: money.billing

money.billing Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
xact bigint NOT NULL
billing_ts timestamp with time zone NOT NULL
voided boolean NOT NULL DEFAULT false
voider integer
void_time timestamp with time zone
amount numeric(6,2) NOT NULL
billing_type text NOT NULL
config.billing_type.id btype integer NOT NULL
note text
create_date timestamp with time zone NOT NULL DEFAULT now()
period_start timestamp with time zone
period_end timestamp with time zone

Tables referencing this one via Foreign Key Constraints:

m_b_create_date_idx create_date m_b_period_end_idx period_end m_b_period_start_idx period_start m_b_time_idx billing_ts m_b_voider_idx voider m_b_xact_idx xact

Index - Schema money


Table: money.bnm_desk_payment

money.bnm_desk_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL
actor.workstation.id cash_drawer integer

Table money.bnm_desk_payment Inherits bnm_payment,

Index - Schema money


Table: money.bnm_payment

money.bnm_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL

Table money.bnm_payment Inherits payment,

Index - Schema money


View: money.bnm_payment_view

money.bnm_payment_view Structure
F-Key Name Type Description
id bigint
xact bigint
payment_ts timestamp with time zone
voided boolean
amount numeric(6,2)
note text
amount_collected numeric(6,2)
accepting_usr integer
payment_type name
SELECT p.id
,
    p.xact
,
    p.payment_ts
,
    p.voided
,
    p.amount
,
    p.note
,
    p.amount_collected
,
    p.accepting_usr
,
    c.relname AS payment_type
   
FROM (money.bnm_payment p
     
  JOIN pg_class c 
    ON (
           (p.tableoid = c.oid)
     )
);

Index - Schema money


Table: money.cash_payment

money.cash_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL
cash_drawer integer

Table money.cash_payment Inherits bnm_desk_payment,

money_cash_id_idx id money_cash_payment_accepting_usr_idx accepting_usr money_cash_payment_cash_drawer_idx cash_drawer money_cash_payment_ts_idx payment_ts money_cash_payment_xact_idx xact

Index - Schema money


View: money.cashdrawer_payment_view

money.cashdrawer_payment_view Structure
F-Key Name Type Description
org_unit integer
cashdrawer integer
payment_type name
payment_ts timestamp with time zone
amount numeric(6,2)
voided boolean
note text
SELECT ou.id AS org_unit
,
    ws.id AS cashdrawer
,
    t.payment_type
,
    p.payment_ts
,
    p.amount
,
    p.voided
,
    p.note
   
FROM (
     (
           (actor.org_unit ou
     
              JOIN actor.workstation ws 
                ON (
                       (ou.id = ws.owning_lib)
                 )
           )
     
   LEFT JOIN money.bnm_desk_payment p 
          ON (
                 (ws.id = p.cash_drawer)
           )
     )
     
LEFT JOIN money.payment_view t 
    ON (
           (p.id = t.id)
     )
);

Index - Schema money


Table: money.check_payment

money.check_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL
cash_drawer integer
check_number text NOT NULL

Table money.check_payment Inherits bnm_desk_payment,

money_check_id_idx id money_check_payment_accepting_usr_idx accepting_usr money_check_payment_cash_drawer_idx cash_drawer money_check_payment_ts_idx payment_ts money_check_payment_xact_idx xact

Index - Schema money


Table: money.collections_tracker

money.collections_tracker Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id usr integer NOT NULL
actor.usr.id collector integer NOT NULL
actor.org_unit.id location integer NOT NULL
enter_time timestamp with time zone
m_c_t_collector_idx collector

Index - Schema money


Table: money.credit_card_payment

money.credit_card_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL
cash_drawer integer
cc_number text
cc_processor text
cc_order_number text
approval_code text

Table money.credit_card_payment Inherits bnm_desk_payment,

money_credit_card_id_idx id money_credit_card_payment_accepting_usr_idx accepting_usr money_credit_card_payment_cash_drawer_idx cash_drawer money_credit_card_payment_ts_idx payment_ts money_credit_card_payment_xact_idx xact

Index - Schema money


Table: money.credit_payment

money.credit_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL

Table money.credit_payment Inherits bnm_payment,

money_credit_id_idx id money_credit_payment_accepting_usr_idx accepting_usr money_credit_payment_payment_ts_idx payment_ts money_credit_payment_xact_idx xact

Index - Schema money


Table: money.debit_card_payment

money.debit_card_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL
cash_drawer integer

Table money.debit_card_payment Inherits bnm_desk_payment,

money_debit_card_id_idx id money_debit_card_payment_accepting_usr_idx accepting_usr money_debit_card_payment_cash_drawer_idx cash_drawer money_debit_card_payment_ts_idx payment_ts money_debit_card_payment_xact_idx xact

Index - Schema money


View: money.desk_payment_view

money.desk_payment_view Structure
F-Key Name Type Description
id bigint
xact bigint
payment_ts timestamp with time zone
voided boolean
amount numeric(6,2)
note text
amount_collected numeric(6,2)
accepting_usr integer
cash_drawer integer
payment_type name
SELECT p.id
,
    p.xact
,
    p.payment_ts
,
    p.voided
,
    p.amount
,
    p.note
,
    p.amount_collected
,
    p.accepting_usr
,
    p.cash_drawer
,
    c.relname AS payment_type
   
FROM (money.bnm_desk_payment p
     
  JOIN pg_class c 
    ON (
           (p.tableoid = c.oid)
     )
);

Index - Schema money


Table: money.forgive_payment

money.forgive_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL

Table money.forgive_payment Inherits bnm_payment,

money_forgive_id_idx id money_forgive_payment_accepting_usr_idx accepting_usr money_forgive_payment_payment_ts_idx payment_ts money_forgive_payment_xact_idx xact

Index - Schema money


Table: money.goods_payment

money.goods_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL

Table money.goods_payment Inherits bnm_payment,

money_goods_id_idx id money_goods_payment_accepting_usr_idx accepting_usr money_goods_payment_payment_ts_idx payment_ts money_goods_payment_xact_idx xact

Index - Schema money


Table: money.grocery

money.grocery Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.billable_xact_id_seq'::regclass)
usr integer NOT NULL
xact_start timestamp with time zone NOT NULL DEFAULT now()
xact_finish timestamp with time zone
unrecovered boolean
billing_location integer NOT NULL
note text

Table money.grocery Inherits billable_xact,

circ_open_date_idx xact_start) WHERE (xact_finish IS NULL m_g_usr_idx usr

Index - Schema money


Table: money.materialized_billable_xact_summary

money.materialized_billable_xact_summary Structure
F-Key Name Type Description
id bigint PRIMARY KEY
usr integer
xact_start timestamp with time zone
xact_finish timestamp with time zone
total_paid numeric
last_payment_ts timestamp with time zone
last_payment_note text
last_payment_type name
total_owed numeric
last_billing_ts timestamp with time zone
last_billing_note text
last_billing_type text
balance_owed numeric
xact_type name
money_mat_summary_usr_idx usr money_mat_summary_xact_start_idx xact_start

Index - Schema money


View: money.non_drawer_payment_view

money.non_drawer_payment_view Structure
F-Key Name Type Description
id bigint
xact bigint
payment_ts timestamp with time zone
voided boolean
amount numeric(6,2)
note text
amount_collected numeric(6,2)
accepting_usr integer
payment_type name
SELECT p.id
,
    p.xact
,
    p.payment_ts
,
    p.voided
,
    p.amount
,
    p.note
,
    p.amount_collected
,
    p.accepting_usr
,
    c.relname AS payment_type
   
FROM (money.bnm_payment p
     
  JOIN pg_class c 
    ON (
           (p.tableoid = c.oid)
     )
)
  
WHERE (c.relname <> ALL 
     (ARRAY['cash_payment'::name
           ,'check_payment'::name
           ,'credit_card_payment'::name
           ,'debit_card_payment'::name]
     )
);

Index - Schema money


View: money.open_billable_xact_summary

money.open_billable_xact_summary Structure
F-Key Name Type Description
id bigint
usr integer
xact_start timestamp with time zone
xact_finish timestamp with time zone
total_paid numeric
last_payment_ts timestamp with time zone
last_payment_note text
last_payment_type name
total_owed numeric
last_billing_ts timestamp with time zone
last_billing_note text
last_billing_type text
balance_owed numeric
xact_type name
billing_location integer
SELECT billable_xact_summary_location_view.id
,
    billable_xact_summary_location_view.usr
,
    billable_xact_summary_location_view.xact_start
,
    billable_xact_summary_location_view.xact_finish
,
    billable_xact_summary_location_view.total_paid
,
    billable_xact_summary_location_view.last_payment_ts
,
    billable_xact_summary_location_view.last_payment_note
,
    billable_xact_summary_location_view.last_payment_type
,
    billable_xact_summary_location_view.total_owed
,
    billable_xact_summary_location_view.last_billing_ts
,
    billable_xact_summary_location_view.last_billing_note
,
    billable_xact_summary_location_view.last_billing_type
,
    billable_xact_summary_location_view.balance_owed
,
    billable_xact_summary_location_view.xact_type
,
    billable_xact_summary_location_view.billing_location
   
FROM money.billable_xact_summary_location_view
  
WHERE (billable_xact_summary_location_view.xact_finish IS NULL);

Index - Schema money


View: money.open_transaction_billing_summary

money.open_transaction_billing_summary Structure
F-Key Name Type Description
xact bigint
last_billing_type text
last_billing_note text
last_billing_ts timestamp with time zone
total_owed numeric
SELECT billing.xact
,
    last
(billing.billing_type) AS last_billing_type
,
    last
(billing.note) AS last_billing_note
,
    max
(billing.billing_ts) AS last_billing_ts
,
    sum
(COALESCE
     (billing.amount
           , (0)::numeric
     )
) AS total_owed
   
FROM money.billing
  
WHERE (billing.voided IS FALSE)
  
GROUP BY billing.xact
  
ORDER BY (max
     (billing.billing_ts)
);

Index - Schema money


View: money.open_transaction_billing_type_summary

money.open_transaction_billing_type_summary Structure
F-Key Name Type Description
xact bigint
last_billing_type text
last_billing_note text
last_billing_ts timestamp with time zone
total_owed numeric
SELECT billing.xact
,
    billing.billing_type AS last_billing_type
,
    last
(billing.note) AS last_billing_note
,
    max
(billing.billing_ts) AS last_billing_ts
,
    sum
(COALESCE
     (billing.amount
           , (0)::numeric
     )
) AS total_owed
   
FROM money.billing
  
WHERE (billing.voided IS FALSE)
  
GROUP BY billing.xact
, billing.billing_type
  
ORDER BY (max
     (billing.billing_ts)
);

Index - Schema money


View: money.open_transaction_payment_summary

money.open_transaction_payment_summary Structure
F-Key Name Type Description
xact bigint
last_payment_type name
last_payment_note text
last_payment_ts timestamp with time zone
total_paid numeric
SELECT payment_view.xact
,
    last
(payment_view.payment_type) AS last_payment_type
,
    last
(payment_view.note) AS last_payment_note
,
    max
(payment_view.payment_ts) AS last_payment_ts
,
    sum
(COALESCE
     (payment_view.amount
           , (0)::numeric
     )
) AS total_paid
   
FROM money.payment_view
  
WHERE (payment_view.voided IS FALSE)
  
GROUP BY payment_view.xact
  
ORDER BY (max
     (payment_view.payment_ts)
);

Index - Schema money


View: money.open_usr_circulation_summary

money.open_usr_circulation_summary Structure
F-Key Name Type Description
usr integer
total_paid numeric
total_owed numeric
balance_owed numeric
SELECT materialized_billable_xact_summary.usr
,
    sum
(materialized_billable_xact_summary.total_paid) AS total_paid
,
    sum
(materialized_billable_xact_summary.total_owed) AS total_owed
,
    sum
(materialized_billable_xact_summary.balance_owed) AS balance_owed
   
FROM money.materialized_billable_xact_summary
  
WHERE (
     (materialized_billable_xact_summary.xact_type = 'circulation'::name)
   AND (materialized_billable_xact_summary.xact_finish IS NULL)
)
  
GROUP BY materialized_billable_xact_summary.usr;

Index - Schema money


View: money.open_usr_summary

money.open_usr_summary Structure
F-Key Name Type Description
usr integer
total_paid numeric
total_owed numeric
balance_owed numeric
SELECT materialized_billable_xact_summary.usr
,
    sum
(materialized_billable_xact_summary.total_paid) AS total_paid
,
    sum
(materialized_billable_xact_summary.total_owed) AS total_owed
,
    sum
(materialized_billable_xact_summary.balance_owed) AS balance_owed
   
FROM money.materialized_billable_xact_summary
  
WHERE (materialized_billable_xact_summary.xact_finish IS NULL)
  
GROUP BY materialized_billable_xact_summary.usr;

Index - Schema money


View: money.open_with_balance_usr_summary

money.open_with_balance_usr_summary Structure
F-Key Name Type Description
usr integer
total_paid numeric
total_owed numeric
balance_owed numeric
SELECT materialized_billable_xact_summary.usr
,
    sum
(materialized_billable_xact_summary.total_paid) AS total_paid
,
    sum
(materialized_billable_xact_summary.total_owed) AS total_owed
,
    sum
(materialized_billable_xact_summary.balance_owed) AS balance_owed
   
FROM money.materialized_billable_xact_summary
  
WHERE (
     (materialized_billable_xact_summary.xact_finish IS NULL)
   AND (materialized_billable_xact_summary.balance_owed <> 0.0)
)
  
GROUP BY materialized_billable_xact_summary.usr;

Index - Schema money


Table: money.payment

money.payment Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
m_p_time_idx payment_ts m_p_xact_idx xact

Index - Schema money


View: money.payment_view

money.payment_view Structure
F-Key Name Type Description
id bigint
xact bigint
payment_ts timestamp with time zone
voided boolean
amount numeric(6,2)
note text
payment_type name
SELECT p.id
,
    p.xact
,
    p.payment_ts
,
    p.voided
,
    p.amount
,
    p.note
,
    c.relname AS payment_type
   
FROM (money.payment p
     
  JOIN pg_class c 
    ON (
           (p.tableoid = c.oid)
     )
);

Index - Schema money


View: money.payment_view_for_aging

money.payment_view_for_aging Structure
F-Key Name Type Description
id bigint
xact bigint
payment_ts timestamp with time zone
voided boolean
amount numeric(6,2)
note text
payment_type name
accepting_usr integer
cash_drawer integer
billing bigint
SELECT p.id
,
    p.xact
,
    p.payment_ts
,
    p.voided
,
    p.amount
,
    p.note
,
    p.payment_type
,
    bnm.accepting_usr
,
    bnmd.cash_drawer
,
    maa.billing
   
FROM (
     (
           (money.payment_view p
     
         LEFT JOIN money.bnm_payment bnm 
                ON (
                       (bnm.id = p.id)
                 )
           )
     
   LEFT JOIN money.bnm_desk_payment bnmd 
          ON (
                 (bnmd.id = p.id)
           )
     )
     
LEFT JOIN money.account_adjustment maa 
    ON (
           (maa.id = p.id)
     )
);

Index - Schema money


View: money.transaction_billing_summary

money.transaction_billing_summary Structure
F-Key Name Type Description
xact bigint
last_billing_type text
last_billing_note text
last_billing_ts timestamp with time zone
total_owed numeric
SELECT materialized_billable_xact_summary.id AS xact
,
    materialized_billable_xact_summary.last_billing_type
,
    materialized_billable_xact_summary.last_billing_note
,
    materialized_billable_xact_summary.last_billing_ts
,
    materialized_billable_xact_summary.total_owed
   
FROM money.materialized_billable_xact_summary;

Index - Schema money


View: money.transaction_billing_type_summary

money.transaction_billing_type_summary Structure
F-Key Name Type Description
xact bigint
last_billing_type text
last_billing_note text
last_billing_ts timestamp with time zone
total_owed numeric
SELECT billing.xact
,
    billing.billing_type AS last_billing_type
,
    last
(billing.note) AS last_billing_note
,
    max
(billing.billing_ts) AS last_billing_ts
,
    sum
(COALESCE
     (billing.amount
           , (0)::numeric
     )
) AS total_owed
   
FROM money.billing
  
WHERE (billing.voided IS FALSE)
  
GROUP BY billing.xact
, billing.billing_type
  
ORDER BY (max
     (billing.billing_ts)
);

Index - Schema money


View: money.transaction_billing_with_void_summary

money.transaction_billing_with_void_summary Structure
F-Key Name Type Description
xact bigint
last_billing_type text
last_billing_note text
last_billing_ts timestamp with time zone
total_owed numeric
SELECT billing.xact
,
    last
(billing.billing_type) AS last_billing_type
,
    last
(billing.note) AS last_billing_note
,
    max
(billing.billing_ts) AS last_billing_ts
,
    sum
(
        CASE
            WHEN billing.voided THEN 
     (0)::numeric
            ELSE COALESCE
     (billing.amount
           , (0)::numeric
     )
        END
) AS total_owed
   
FROM money.billing
  
GROUP BY billing.xact
  
ORDER BY (max
     (billing.billing_ts)
);

Index - Schema money


View: money.transaction_payment_summary

money.transaction_payment_summary Structure
F-Key Name Type Description
xact bigint
last_payment_type name
last_payment_note text
last_payment_ts timestamp with time zone
total_paid numeric
SELECT payment_view.xact
,
    last
(payment_view.payment_type) AS last_payment_type
,
    last
(payment_view.note) AS last_payment_note
,
    max
(payment_view.payment_ts) AS last_payment_ts
,
    sum
(COALESCE
     (payment_view.amount
           , (0)::numeric
     )
) AS total_paid
   
FROM money.payment_view
  
WHERE (payment_view.voided IS FALSE)
  
GROUP BY payment_view.xact
  
ORDER BY (max
     (payment_view.payment_ts)
);

Index - Schema money


View: money.transaction_payment_with_void_summary

money.transaction_payment_with_void_summary Structure
F-Key Name Type Description
xact bigint
last_payment_type name
last_payment_note text
last_payment_ts timestamp with time zone
total_paid numeric
SELECT payment_view.xact
,
    last
(payment_view.payment_type) AS last_payment_type
,
    last
(payment_view.note) AS last_payment_note
,
    max
(payment_view.payment_ts) AS last_payment_ts
,
    sum
(
        CASE
            WHEN payment_view.voided THEN 
     (0)::numeric
            ELSE COALESCE
     (payment_view.amount
           , (0)::numeric
     )
        END
) AS total_paid
   
FROM money.payment_view
  
GROUP BY payment_view.xact
  
ORDER BY (max
     (payment_view.payment_ts)
);

Index - Schema money


View: money.usr_circulation_summary

money.usr_circulation_summary Structure
F-Key Name Type Description
usr integer
total_paid numeric
total_owed numeric
balance_owed numeric
SELECT billable_xact_summary.usr
,
    sum
(billable_xact_summary.total_paid) AS total_paid
,
    sum
(billable_xact_summary.total_owed) AS total_owed
,
    sum
(billable_xact_summary.balance_owed) AS balance_owed
   
FROM money.billable_xact_summary
  
WHERE (billable_xact_summary.xact_type = 'circulation'::name)
  
GROUP BY billable_xact_summary.usr;

Index - Schema money


View: money.usr_summary

money.usr_summary Structure
F-Key Name Type Description
usr integer
total_paid numeric
total_owed numeric
balance_owed numeric
SELECT materialized_billable_xact_summary.usr
,
    sum
(materialized_billable_xact_summary.total_paid) AS total_paid
,
    sum
(materialized_billable_xact_summary.total_owed) AS total_owed
,
    sum
(materialized_billable_xact_summary.balance_owed) AS balance_owed
   
FROM money.materialized_billable_xact_summary
  
GROUP BY materialized_billable_xact_summary.usr;

Index - Schema money


Table: money.work_payment

money.work_payment Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('money.payment_id_seq'::regclass)
xact bigint NOT NULL
payment_ts timestamp with time zone NOT NULL DEFAULT now()
voided boolean NOT NULL DEFAULT false
amount numeric(6,2) NOT NULL
note text
amount_collected numeric(6,2) NOT NULL
accepting_usr integer NOT NULL

Table money.work_payment Inherits bnm_payment,

money_work_id_idx id money_work_payment_accepting_usr_idx accepting_usr money_work_payment_payment_ts_idx payment_ts money_work_payment_xact_idx xact

Index - Schema money


Function: money.age_billings_and_payments()

Returns: integer

Language: PLPGSQL

-- Age billings and payments linked to transactions which were 
-- completed at least 'older_than' time ago.
DECLARE
    xact_id BIGINT;
    counter INTEGER DEFAULT 0;
    keep_age INTERVAL;
BEGIN

    SELECT value::INTERVAL INTO keep_age FROM config.global_flag 
        WHERE name = 'history.money.retention_age' AND enabled;

    -- Confirm interval-based aging is enabled.
    IF keep_age IS NULL THEN RETURN counter; END IF;

    -- Start with non-circulation transactions
    FOR xact_id IN SELECT DISTINCT(xact.id) FROM money.billable_xact xact
        -- confirm there is something to age
        JOIN money.billing mb ON mb.xact = xact.id
        -- Avoid aging money linked to non-aged circulations.
        LEFT JOIN action.circulation circ ON circ.id = xact.id
        WHERE circ.id IS NULL AND AGE(NOW(), xact.xact_finish) > keep_age LOOP

        PERFORM money.age_billings_and_payments_for_xact(xact_id);
        counter := counter + 1;
    END LOOP;

    -- Then handle aged circulation money.
    FOR xact_id IN SELECT DISTINCT(xact.id) FROM action.aged_circulation xact
        -- confirm there is something to age
        JOIN money.billing mb ON mb.xact = xact.id
        WHERE AGE(NOW(), xact.xact_finish) > keep_age LOOP

        PERFORM money.age_billings_and_payments_for_xact(xact_id);
        counter := counter + 1;
    END LOOP;

    RETURN counter;
END;

Function: money.age_billings_and_payments_for_xact(xact_id bigint)

Returns: void

Language: SQL


    INSERT INTO money.aged_billing
        SELECT * FROM money.billing WHERE xact = $1;

    INSERT INTO money.aged_payment 
        SELECT * FROM money.payment_view_for_aging WHERE xact = xact_id;

    DELETE FROM money.payment WHERE xact = $1;
    DELETE FROM money.billing WHERE xact = $1;


Function: money.maintain_billing_ts()

Returns: trigger

Language: PLPGSQL

BEGIN
	NEW.billing_ts := COALESCE(NEW.period_end, NEW.create_date);
	RETURN NEW;
END;

Function: money.mat_summary_create()

Returns: trigger

Language: PLPGSQL

BEGIN
	INSERT INTO money.materialized_billable_xact_summary (id, usr, xact_start, xact_finish, total_paid, total_owed, balance_owed, xact_type)
		VALUES ( NEW.id, NEW.usr, NEW.xact_start, NEW.xact_finish, 0.0, 0.0, 0.0, TG_ARGV[0]);
	RETURN NEW;
END;

Function: money.mat_summary_delete()

Returns: trigger

Language: PLPGSQL

BEGIN
	DELETE FROM money.materialized_billable_xact_summary WHERE id = OLD.id;
	RETURN OLD;
END;

Function: money.mat_summary_update()

Returns: trigger

Language: PLPGSQL

BEGIN
	UPDATE	money.materialized_billable_xact_summary
	  SET	usr = NEW.usr,
		xact_start = NEW.xact_start,
		xact_finish = NEW.xact_finish
	  WHERE	id = NEW.id;
	RETURN NEW;
END;

Function: money.materialized_summary_billing_add()

Returns: trigger

Language: PLPGSQL

BEGIN
	IF NOT NEW.voided THEN
		UPDATE	money.materialized_billable_xact_summary
		  SET	total_owed = COALESCE(total_owed, 0.0::numeric) + NEW.amount,
			last_billing_ts = NEW.billing_ts,
			last_billing_note = NEW.note,
			last_billing_type = NEW.billing_type,
			balance_owed = balance_owed + NEW.amount
		  WHERE	id = NEW.xact;
	END IF;

	RETURN NEW;
END;

Function: money.materialized_summary_billing_del()

Returns: trigger

Language: PLPGSQL

DECLARE
	prev_billing	money.billing%ROWTYPE;
	old_billing	money.billing%ROWTYPE;
BEGIN
	SELECT * INTO prev_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1 OFFSET 1;
	SELECT * INTO old_billing FROM money.billing WHERE xact = OLD.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;

	IF OLD.id = old_billing.id THEN
		UPDATE	money.materialized_billable_xact_summary
		  SET	last_billing_ts = prev_billing.billing_ts,
			last_billing_note = prev_billing.note,
			last_billing_type = prev_billing.billing_type
		  WHERE	id = OLD.xact;
	END IF;

	IF NOT OLD.voided THEN
		UPDATE	money.materialized_billable_xact_summary
		  SET	total_owed = total_owed - OLD.amount,
			balance_owed = balance_owed - OLD.amount
		  WHERE	id = OLD.xact;
	END IF;

	RETURN OLD;
END;

Function: money.materialized_summary_billing_update()

Returns: trigger

Language: PLPGSQL

DECLARE
	old_billing	money.billing%ROWTYPE;
	old_voided	money.billing%ROWTYPE;
BEGIN

	SELECT * INTO old_billing FROM money.billing WHERE xact = NEW.xact AND NOT voided ORDER BY billing_ts DESC LIMIT 1;
	SELECT * INTO old_voided FROM money.billing WHERE xact = NEW.xact ORDER BY billing_ts DESC LIMIT 1;

	IF NEW.voided AND NOT OLD.voided THEN
		IF OLD.id = old_voided.id THEN
			UPDATE	money.materialized_billable_xact_summary
			  SET	last_billing_ts = old_billing.billing_ts,
				last_billing_note = old_billing.note,
				last_billing_type = old_billing.billing_type
			  WHERE	id = OLD.xact;
		END IF;

		UPDATE	money.materialized_billable_xact_summary
		  SET	total_owed = total_owed - NEW.amount,
			balance_owed = balance_owed - NEW.amount
		  WHERE	id = NEW.xact;

	ELSIF NOT NEW.voided AND OLD.voided THEN

		IF OLD.id = old_billing.id THEN
			UPDATE	money.materialized_billable_xact_summary
			  SET	last_billing_ts = old_billing.billing_ts,
				last_billing_note = old_billing.note,
				last_billing_type = old_billing.billing_type
			  WHERE	id = OLD.xact;
		END IF;

		UPDATE	money.materialized_billable_xact_summary
		  SET	total_owed = total_owed + NEW.amount,
			balance_owed = balance_owed + NEW.amount
		  WHERE	id = NEW.xact;

	ELSE
		UPDATE	money.materialized_billable_xact_summary
		  SET	total_owed = total_owed - (OLD.amount - NEW.amount),
			balance_owed = balance_owed - (OLD.amount - NEW.amount)
		  WHERE	id = NEW.xact;
	END IF;

	RETURN NEW;
END;

Function: money.materialized_summary_payment_add()

Returns: trigger

Language: PLPGSQL

BEGIN
	IF NOT NEW.voided THEN
		UPDATE	money.materialized_billable_xact_summary
		  SET	total_paid = COALESCE(total_paid, 0.0::numeric) + NEW.amount,
			last_payment_ts = NEW.payment_ts,
			last_payment_note = NEW.note,
			last_payment_type = TG_ARGV[0],
			balance_owed = balance_owed - NEW.amount
		  WHERE	id = NEW.xact;
	END IF;

	RETURN NEW;
END;

Function: money.materialized_summary_payment_del()

Returns: trigger

Language: PLPGSQL

DECLARE
	prev_payment	money.payment_view%ROWTYPE;
	old_payment	money.payment_view%ROWTYPE;
BEGIN
	SELECT * INTO prev_payment FROM money.payment_view WHERE xact = OLD.xact AND NOT voided ORDER BY payment_ts DESC LIMIT 1 OFFSET 1;
	SELECT * INTO old_payment FROM money.payment_view WHERE xact = OLD.xact AND NOT voided ORDER BY payment_ts DESC LIMIT 1;

	IF OLD.id = old_payment.id THEN
		UPDATE	money.materialized_billable_xact_summary
		  SET	last_payment_ts = prev_payment.payment_ts,
			last_payment_note = prev_payment.note,
			last_payment_type = prev_payment.payment_type
		  WHERE	id = OLD.xact;
	END IF;

	IF NOT OLD.voided THEN
		UPDATE	money.materialized_billable_xact_summary
		  SET	total_paid = total_paid - OLD.amount,
			balance_owed = balance_owed + OLD.amount
		  WHERE	id = OLD.xact;
	END IF;

	RETURN OLD;
END;

Function: money.materialized_summary_payment_update()

Returns: trigger

Language: PLPGSQL

DECLARE
	old_payment	money.payment_view%ROWTYPE;
	old_voided	money.payment_view%ROWTYPE;
BEGIN

	SELECT * INTO old_payment FROM money.payment_view WHERE xact = NEW.xact AND NOT voided ORDER BY payment_ts DESC LIMIT 1;
	SELECT * INTO old_voided FROM money.payment_view WHERE xact = NEW.xact ORDER BY payment_ts DESC LIMIT 1;

	IF NEW.voided AND NOT OLD.voided THEN
		IF OLD.id = old_voided.id THEN
			UPDATE	money.materialized_billable_xact_summary
			  SET	last_payment_ts = old_payment.payment_ts,
				last_payment_note = old_payment.note,
				last_payment_type = old_payment.payment_type
			  WHERE	id = OLD.xact;
		END IF;

		UPDATE	money.materialized_billable_xact_summary
		  SET	total_paid = total_paid - NEW.amount,
			balance_owed = balance_owed + NEW.amount
		  WHERE	id = NEW.xact;

	ELSIF NOT NEW.voided AND OLD.voided THEN

		IF OLD.id = old_payment.id THEN
			UPDATE	money.materialized_billable_xact_summary
			  SET	last_payment_ts = old_payment.payment_ts,
				last_payment_note = old_payment.note,
				last_payment_type = old_payment.payment_type
			  WHERE	id = OLD.xact;
		END IF;

		UPDATE	money.materialized_billable_xact_summary
		  SET	total_paid = total_paid + NEW.amount,
			balance_owed = balance_owed - NEW.amount
		  WHERE	id = NEW.xact;

	ELSE
		UPDATE	money.materialized_billable_xact_summary
		  SET	total_paid = total_paid - (OLD.amount - NEW.amount),
			balance_owed = balance_owed + (OLD.amount - NEW.amount)
		  WHERE	id = NEW.xact;
	END IF;

	RETURN NEW;
END;

Schema oai


View: oai.authority

oai.authority Structure
F-Key Name Type Description
rec_id bigint
datestamp timestamp without time zone
deleted boolean
SELECT are.id AS rec_id
,
    timezone
('UTC'::text
     , are.edit_date
) AS datestamp
,
    are.deleted
   
FROM authority.record_entry are
  
ORDER BY are.id;

Index - Schema oai


View: oai.biblio

oai.biblio Structure
F-Key Name Type Description
rec_id bigint
datestamp timestamp without time zone
deleted boolean
SELECT bre.id AS rec_id
,
    timezone
('UTC'::text
     , bre.edit_date
) AS datestamp
,
    bre.deleted
   
FROM biblio.record_entry bre
  
ORDER BY bre.id;

Index - Schema oai


Function: oai.auth_is_visible_by_axis(ax bigint, auth text)

Returns: boolean

Language: SQL

  SELECT EXISTS (SELECT 1 FROM authority.browse_axis_authority_field_map m JOIN authority.simple_heading r on (r.atag = m.field AND r.record = auth AND m.axis = ax))

Function: oai.bib_is_visible_at_org_by_copy(org bigint, bib integer)

Returns: boolean

Language: SQL

WITH corgs AS (SELECT array_agg(id) AS list FROM actor.org_unit_descendants(org))
  SELECT EXISTS (SELECT 1 FROM asset.copy_vis_attr_cache, corgs WHERE vis_attr_vector @@ search.calculate_visibility_attribute_test('circ_lib', corgs.list)::query_int AND bib=record)

Function: oai.bib_is_visible_at_org_by_luri(org bigint, bib integer)

Returns: boolean

Language: SQL

WITH lorgs AS(SELECT array_agg(id) AS list FROM actor.org_unit_ancestors(org))
  SELECT EXISTS (SELECT 1 FROM biblio.record_entry, lorgs WHERE vis_attr_vector @@ search.calculate_visibility_attribute_test('luri_org', lorgs.list)::query_int AND bib=id)

Function: oai.bib_is_visible_by_source(src bigint, bib text)

Returns: boolean

Language: SQL

  SELECT EXISTS (SELECT 1 FROM biblio.record_entry b JOIN config.bib_source s ON (b.source = s.id) WHERE transcendant AND s.source = src AND bib=b.id)

Schema offline


Table: offline.script

offline.script Structure
F-Key Name Type Description
id serial PRIMARY KEY
session text NOT NULL
requestor integer NOT NULL
create_time integer NOT NULL
workstation text NOT NULL
logfile text NOT NULL
time_delta integer NOT NULL
count integer NOT NULL
offline_script_pkey id offline_script_session session offline_script_ws workstation

Index - Schema offline


Table: offline.session

offline.session Structure
F-Key Name Type Description
key text PRIMARY KEY
org integer NOT NULL
description text
creator integer NOT NULL
create_time integer NOT NULL
in_process integer NOT NULL
start_time integer
end_time integer
num_complete integer NOT NULL
offline_session_creation create_time offline_session_org org offline_session_pkey key

Index - Schema offline


Schema permission


Table: permission.grp_penalty_threshold

permission.grp_penalty_threshold Structure
F-Key Name Type Description
id serial PRIMARY KEY
permission.grp_tree.id grp integer UNIQUE#1 NOT NULL
actor.org_unit.id org_unit integer UNIQUE#1 NOT NULL
config.standing_penalty.id penalty integer UNIQUE#1 NOT NULL
threshold numeric(8,2) NOT NULL

Index - Schema permission


Table: permission.grp_perm_map

permission.grp_perm_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
permission.grp_tree.id grp integer UNIQUE#1 NOT NULL
permission.perm_list.id perm integer UNIQUE#1 NOT NULL
depth integer NOT NULL
grantable boolean NOT NULL DEFAULT false

Index - Schema permission


Table: permission.grp_tree

permission.grp_tree Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE NOT NULL
permission.grp_tree.id parent integer
usergroup boolean NOT NULL DEFAULT true
perm_interval interval NOT NULL DEFAULT '3 years'::interval
description text
application_perm text
hold_priority integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

grp_tree_parent_idx parent

Index - Schema permission


Table: permission.grp_tree_display_entry

permission.grp_tree_display_entry Structure
F-Key Name Type Description
id serial PRIMARY KEY
position integer NOT NULL
actor.org_unit.id org integer UNIQUE#1 NOT NULL
permission.grp_tree.id grp integer UNIQUE#1 NOT NULL
permission.grp_tree_display_entry.id parent integer

Tables referencing this one via Foreign Key Constraints:

Index - Schema permission


Table: permission.perm_list

permission.perm_list Structure
F-Key Name Type Description
id serial PRIMARY KEY
code text UNIQUE NOT NULL
description text

Tables referencing this one via Foreign Key Constraints:

perm_list_code_idx code

Index - Schema permission


Table: permission.usr_grp_map

permission.usr_grp_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id usr integer UNIQUE#1 NOT NULL
permission.grp_tree.id grp integer UNIQUE#1 NOT NULL

Index - Schema permission


Table: permission.usr_object_perm_map

permission.usr_object_perm_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id usr integer UNIQUE#1 NOT NULL
permission.perm_list.id perm integer UNIQUE#1 NOT NULL
object_type text UNIQUE#1 NOT NULL
object_id text UNIQUE#1 NOT NULL
grantable boolean NOT NULL DEFAULT false
uopm_usr_idx usr

Index - Schema permission


Table: permission.usr_perm_map

permission.usr_perm_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id usr integer UNIQUE#1 NOT NULL
permission.perm_list.id perm integer UNIQUE#1 NOT NULL
depth integer NOT NULL
grantable boolean NOT NULL DEFAULT false

Index - Schema permission


Table: permission.usr_work_ou_map

permission.usr_work_ou_map Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id usr integer UNIQUE#1 NOT NULL
actor.org_unit.id work_ou integer UNIQUE#1 NOT NULL

Index - Schema permission


Function: permission.grp_ancestors(integer)

Returns: SET OF grp_tree

Language: SQL

    WITH RECURSIVE grp_ancestors_distance(id, distance) AS (
            SELECT $1, 0
        UNION
            SELECT ou.parent, ouad.distance+1
            FROM permission.grp_tree ou JOIN grp_ancestors_distance ouad ON (ou.id = ouad.id)
            WHERE ou.parent IS NOT NULL
    )
    SELECT ou.* FROM permission.grp_tree ou JOIN grp_ancestors_distance ouad USING (id) ORDER BY ouad.distance DESC;

Function: permission.grp_ancestors_distance(distance integer)

Returns: SET OF record

Language: SQL

    WITH RECURSIVE grp_ancestors_distance(id, distance) AS (
            SELECT $1, 0
        UNION
            SELECT pgt.parent, gad.distance+1
            FROM permission.grp_tree pgt JOIN grp_ancestors_distance gad ON (pgt.id = gad.id)
            WHERE pgt.parent IS NOT NULL
    )
    SELECT * FROM grp_ancestors_distance;

Function: permission.grp_descendants(integer)

Returns: SET OF grp_tree

Language: SQL

    WITH RECURSIVE descendant_depth AS (
        SELECT  gr.id,
                gr.parent
          FROM  permission.grp_tree gr
          WHERE gr.id = $1
            UNION ALL
        SELECT  gr.id,
                gr.parent
          FROM  permission.grp_tree gr
                JOIN descendant_depth dd ON (dd.id = gr.parent)
    ) SELECT gr.* FROM permission.grp_tree gr JOIN descendant_depth USING (id);

Function: permission.grp_descendants_distance(distance integer)

Returns: SET OF record

Language: SQL

    WITH RECURSIVE grp_descendants_distance(id, distance) AS (
            SELECT $1, 0
        UNION
            SELECT pgt.id, gdd.distance+1
            FROM permission.grp_tree pgt JOIN grp_descendants_distance gdd ON (pgt.parent = gdd.id)
    )
    SELECT * FROM grp_descendants_distance;

Function: permission.grp_tree_combined_ancestors(integer, integer)

Returns: SET OF grp_tree

Language: SQL

        SELECT  *
          FROM  permission.grp_ancestors($1)
                        UNION
        SELECT  *
          FROM  permission.grp_ancestors($2);

Function: permission.grp_tree_common_ancestors(integer, integer)

Returns: SET OF grp_tree

Language: SQL

        SELECT  *
          FROM  permission.grp_ancestors($1)
                        INTERSECT
        SELECT  *
          FROM  permission.grp_ancestors($2);

Function: permission.grp_tree_full_path(integer)

Returns: SET OF grp_tree

Language: SQL

        SELECT  *
          FROM  permission.grp_ancestors($1)
                        UNION
        SELECT  *
          FROM  permission.grp_descendants($1);

Function: permission.usr_can_grant_perm(target_ou integer, tperm text, iuser integer)

Returns: boolean

Language: PLPGSQL

DECLARE
	r_usr	actor.usr%ROWTYPE;
	r_perm	permission.usr_perm_map%ROWTYPE;
BEGIN

	SELECT * INTO r_usr FROM actor.usr WHERE id = iuser;

	IF r_usr.active = FALSE THEN
		RETURN FALSE;
	END IF;

	IF r_usr.super_user = TRUE THEN
		RETURN TRUE;
	END IF;

	FOR r_perm IN	SELECT	*
			  FROM	permission.usr_perms(iuser) p
				JOIN permission.perm_list l
					ON (l.id = p.perm)
			  WHERE	(l.code = tperm AND p.grantable IS TRUE)
		LOOP

		PERFORM	*
		  FROM	actor.org_unit_descendants(target_ou,r_perm.depth)
		  WHERE	id = r_usr.home_ou;

		IF FOUND THEN
			RETURN TRUE;
		ELSE
			RETURN FALSE;
		END IF;
	END LOOP;

	RETURN FALSE;
END;

Function: permission.usr_has_home_perm(target_ou integer, tperm text, iuser integer)

Returns: boolean

Language: PLPGSQL

DECLARE
	r_usr	actor.usr%ROWTYPE;
	r_perm	permission.usr_perm_map%ROWTYPE;
BEGIN

	SELECT * INTO r_usr FROM actor.usr WHERE id = iuser;

	IF r_usr.active = FALSE THEN
		RETURN FALSE;
	END IF;

	IF r_usr.super_user = TRUE THEN
		RETURN TRUE;
	END IF;

	FOR r_perm IN	SELECT	*
			  FROM	permission.usr_perms(iuser) p
				JOIN permission.perm_list l
					ON (l.id = p.perm)
			  WHERE	l.code = tperm
			  	OR p.perm = -1 LOOP

		PERFORM	*
		  FROM	actor.org_unit_descendants(target_ou,r_perm.depth)
		  WHERE	id = r_usr.home_ou;

		IF FOUND THEN
			RETURN TRUE;
		ELSE
			RETURN FALSE;
		END IF;
	END LOOP;

	RETURN FALSE;
END;

Function: permission.usr_has_object_perm(integer, text, text, text)

Returns: boolean

Language: SQL

    SELECT permission.usr_has_object_perm( $1, $2, $3, $4, -1 );

Function: permission.usr_has_object_perm(target_ou integer, obj_id text, obj_type text, tperm text, iuser integer)

Returns: boolean

Language: PLPGSQL

DECLARE
	r_usr	actor.usr%ROWTYPE;
	res     BOOL;
BEGIN

	SELECT * INTO r_usr FROM actor.usr WHERE id = iuser;

	IF r_usr.active = FALSE THEN
		RETURN FALSE;
	END IF;

	IF r_usr.super_user = TRUE THEN
		RETURN TRUE;
	END IF;

	SELECT TRUE INTO res FROM permission.usr_object_perm_map WHERE usr = r_usr.id AND object_type = obj_type AND object_id = obj_id;

	IF FOUND THEN
		RETURN TRUE;
	END IF;

	IF target_ou > -1 THEN
		RETURN permission.usr_has_perm( iuser, tperm, target_ou);
	END IF;

	RETURN FALSE;

END;

Function: permission.usr_has_perm(integer, text, integer)

Returns: boolean

Language: SQL

	SELECT	CASE
			WHEN permission.usr_has_home_perm( $1, $2, $3 ) THEN TRUE
			WHEN permission.usr_has_work_perm( $1, $2, $3 ) THEN TRUE
			ELSE FALSE
		END;

Function: permission.usr_has_perm_at(perm_code integer, user_id text)

Returns: SET OF integer

Language: SQL

SELECT DISTINCT * FROM permission.usr_has_perm_at_nd( $1, $2 );

Function: permission.usr_has_perm_at_all(perm_code integer, user_id text)

Returns: SET OF integer

Language: SQL

SELECT DISTINCT * FROM permission.usr_has_perm_at_all_nd( $1, $2 );

Function: permission.usr_has_perm_at_all_nd(perm_code integer, user_id text)

Returns: SET OF integer

Language: PLPGSQL

--
-- Return a set of all the org units for which a given user has a given
-- permission, granted either directly or through inheritance from a parent
-- org unit.
--
-- The permissions apply to a minimum depth of the org unit hierarchy, and
-- to the subordinates of those org units, for the org unit(s) to which the
-- user is assigned.
--
-- For purposes of this function, the permission.usr_work_ou_map table
-- assigns users to org units.  I.e. we ignore the home_ou column of actor.usr.
--
-- The result set may contain duplicates, which should be eliminated
-- by a DISTINCT clause.
--
DECLARE
	n_head_ou     INTEGER;
	n_child_ou    INTEGER;
BEGIN
	FOR n_head_ou IN
		SELECT DISTINCT * FROM permission.usr_has_perm_at_nd( user_id, perm_code )
	LOOP
		--
		-- The permission applies only at a depth greater than the work org unit.
		--
		FOR n_child_ou IN
            SELECT id
            FROM actor.org_unit_descendants(n_head_ou)
		LOOP
			RETURN NEXT n_child_ou;
		END LOOP;
	END LOOP;
	--
	RETURN;
	--
END;

Function: permission.usr_has_perm_at_nd(perm_code integer, user_id text)

Returns: SET OF integer

Language: PLPGSQL

--
-- Return a set of all the org units for which a given user has a given
-- permission, granted directly (not through inheritance from a parent
-- org unit).
--
-- The permissions apply to a minimum depth of the org unit hierarchy,
-- for the org unit(s) to which the user is assigned.  (They also apply
-- to the subordinates of those org units, but we don't report the
-- subordinates here.)
--
-- For purposes of this function, the permission.usr_work_ou_map table
-- defines which users belong to which org units.  I.e. we ignore the
-- home_ou column of actor.usr.
--
-- The result set may contain duplicates, which should be eliminated
-- by a DISTINCT clause.
--
DECLARE
	b_super       BOOLEAN;
	n_perm        INTEGER;
	n_min_depth   INTEGER; 
	n_work_ou     INTEGER;
	n_curr_ou     INTEGER;
	n_depth       INTEGER;
	n_curr_depth  INTEGER;
BEGIN
	--
	-- Check for superuser
	--
	SELECT INTO b_super
		super_user
	FROM
		actor.usr
	WHERE
		id = user_id;
	--
	IF NOT FOUND THEN
		return;				-- No user?  No permissions.
	ELSIF b_super THEN
		--
		-- Super user has all permissions everywhere
		--
		FOR n_work_ou IN
			SELECT
				id
			FROM
				actor.org_unit
			WHERE
				parent_ou IS NULL
		LOOP
			RETURN NEXT n_work_ou; 
		END LOOP;
		RETURN;
	END IF;
	--
	-- Translate the permission name
	-- to a numeric permission id
	--
	SELECT INTO n_perm
		id
	FROM
		permission.perm_list
	WHERE
		code = perm_code;
	--
	IF NOT FOUND THEN
		RETURN;               -- No such permission
	END IF;
	--
	-- Find the highest-level org unit (i.e. the minimum depth)
	-- to which the permission is applied for this user
	--
	-- This query is modified from the one in permission.usr_perms().
	--
	SELECT INTO n_min_depth
		min( depth )
	FROM	(
		SELECT depth 
		  FROM permission.usr_perm_map upm
		 WHERE upm.usr = user_id 
		   AND (upm.perm = n_perm OR upm.perm = -1)
       				UNION
		SELECT	gpm.depth
		  FROM	permission.grp_perm_map gpm
		  WHERE	(gpm.perm = n_perm OR gpm.perm = -1)
	        AND gpm.grp IN (
	 		   SELECT	(permission.grp_ancestors(
					(SELECT profile FROM actor.usr WHERE id = user_id)
				)).id
			)
       				UNION
		SELECT	p.depth
		  FROM	permission.grp_perm_map p 
		  WHERE (p.perm = n_perm OR p.perm = -1)
		    AND p.grp IN (
		  		SELECT (permission.grp_ancestors(m.grp)).id 
				FROM   permission.usr_grp_map m
				WHERE  m.usr = user_id
			)
	) AS x;
	--
	IF NOT FOUND THEN
		RETURN;                -- No such permission for this user
	END IF;
	--
	-- Identify the org units to which the user is assigned.  Note that
	-- we pay no attention to the home_ou column in actor.usr.
	--
	FOR n_work_ou IN
		SELECT
			work_ou
		FROM
			permission.usr_work_ou_map
		WHERE
			usr = user_id
	LOOP            -- For each org unit to which the user is assigned
		--
		-- Determine the level of the org unit by a lookup in actor.org_unit_type.
		-- We take it on faith that this depth agrees with the actual hierarchy
		-- defined in actor.org_unit.
		--
		SELECT INTO n_depth
		    type.depth
		FROM
		    actor.org_unit_type type
		        INNER JOIN actor.org_unit ou
		            ON ( ou.ou_type = type.id )
		WHERE
		    ou.id = n_work_ou;
		--
		IF NOT FOUND THEN
			CONTINUE;        -- Maybe raise exception?
		END IF;
		--
		-- Compare the depth of the work org unit to the
		-- minimum depth, and branch accordingly
		--
		IF n_depth = n_min_depth THEN
			--
			-- The org unit is at the right depth, so return it.
			--
			RETURN NEXT n_work_ou;
		ELSIF n_depth > n_min_depth THEN
			--
			-- Traverse the org unit tree toward the root,
			-- until you reach the minimum depth determined above
			--
			n_curr_depth := n_depth;
			n_curr_ou := n_work_ou;
			WHILE n_curr_depth > n_min_depth LOOP
				SELECT INTO n_curr_ou
					parent_ou
				FROM
					actor.org_unit
				WHERE
					id = n_curr_ou;
				--
				IF FOUND THEN
					n_curr_depth := n_curr_depth - 1;
				ELSE
					--
					-- This can happen only if the hierarchy defined in
					-- actor.org_unit is corrupted, or out of sync with
					-- the depths defined in actor.org_unit_type.
					-- Maybe we should raise an exception here, instead
					-- of silently ignoring the problem.
					--
					n_curr_ou = NULL;
					EXIT;
				END IF;
			END LOOP;
			--
			IF n_curr_ou IS NOT NULL THEN
				RETURN NEXT n_curr_ou;
			END IF;
		ELSE
			--
			-- The permission applies only at a depth greater than the work org unit.
			-- Use connectby() to find all dependent org units at the specified depth.
			--
			FOR n_curr_ou IN
				SELECT id
				FROM actor.org_unit_descendants_distance(n_work_ou)
				WHERE
					distance = n_min_depth - n_depth
			LOOP
				RETURN NEXT n_curr_ou;
			END LOOP;
		END IF;
		--
	END LOOP;
	--
	RETURN;
	--
END;

Function: permission.usr_has_work_perm(target_ou integer, tperm text, iuser integer)

Returns: boolean

Language: PLPGSQL

DECLARE
	r_woum	permission.usr_work_ou_map%ROWTYPE;
	r_usr	actor.usr%ROWTYPE;
	r_perm	permission.usr_perm_map%ROWTYPE;
BEGIN

	SELECT * INTO r_usr FROM actor.usr WHERE id = iuser;

	IF r_usr.active = FALSE THEN
		RETURN FALSE;
	END IF;

	IF r_usr.super_user = TRUE THEN
		RETURN TRUE;
	END IF;

	FOR r_perm IN	SELECT	*
			  FROM	permission.usr_perms(iuser) p
				JOIN permission.perm_list l
					ON (l.id = p.perm)
			  WHERE	l.code = tperm
			  	OR p.perm = -1
		LOOP

		FOR r_woum IN	SELECT	*
				  FROM	permission.usr_work_ou_map
				  WHERE	usr = iuser
			LOOP

			PERFORM	*
			  FROM	actor.org_unit_descendants(target_ou,r_perm.depth)
			  WHERE	id = r_woum.work_ou;

			IF FOUND THEN
				RETURN TRUE;
			END IF;

		END LOOP;

	END LOOP;

	RETURN FALSE;
END;

Function: permission.usr_perms(integer)

Returns: SET OF usr_perm_map

Language: SQL

	SELECT	DISTINCT ON (usr,perm) *
	  FROM	(
			(SELECT * FROM permission.usr_perm_map WHERE usr = $1)
        				UNION ALL
			(SELECT	-p.id, $1 AS usr, p.perm, p.depth, p.grantable
			  FROM	permission.grp_perm_map p
			  WHERE	p.grp IN (
			  	SELECT	(permission.grp_ancestors(
						(SELECT profile FROM actor.usr WHERE id = $1)
					)).id
				)
			)
        				UNION ALL
			(SELECT	-p.id, $1 AS usr, p.perm, p.depth, p.grantable
			  FROM	permission.grp_perm_map p 
			  WHERE	p.grp IN (SELECT (permission.grp_ancestors(m.grp)).id FROM permission.usr_grp_map m WHERE usr = $1))
		) AS x
	  ORDER BY 2, 3, 4 ASC, 5 DESC ;

Schema public

standard public schema


Function: public._int_contained(integer[], integer[])

Returns: boolean

Language: C

contained in

_int_contained

Function: public._int_contained_joinsel(internal, oid, internal, smallint, internal)

Returns: double precision

Language: C

_int_contained_joinsel

Function: public._int_contained_sel(internal, oid, internal, integer)

Returns: double precision

Language: C

_int_contained_sel

Function: public._int_contains(integer[], integer[])

Returns: boolean

Language: C

contains

_int_contains

Function: public._int_contains_joinsel(internal, oid, internal, smallint, internal)

Returns: double precision

Language: C

_int_contains_joinsel

Function: public._int_contains_sel(internal, oid, internal, integer)

Returns: double precision

Language: C

_int_contains_sel

Function: public._int_different(integer[], integer[])

Returns: boolean

Language: C

different

_int_different

Function: public._int_inter(integer[], integer[])

Returns: integer[]

Language: C

_int_inter

Function: public._int_matchsel(internal, oid, internal, integer)

Returns: double precision

Language: C

_int_matchsel

Function: public._int_overlap(integer[], integer[])

Returns: boolean

Language: C

overlaps

_int_overlap

Function: public._int_overlap_joinsel(internal, oid, internal, smallint, internal)

Returns: double precision

Language: C

_int_overlap_joinsel

Function: public._int_overlap_sel(internal, oid, internal, integer)

Returns: double precision

Language: C

_int_overlap_sel

Function: public._int_same(integer[], integer[])

Returns: boolean

Language: C

same as

_int_same

Function: public._int_union(integer[], integer[])

Returns: integer[]

Language: C

_int_union

Function: public._intbig_in(cstring)

Returns: intbig_gkey

Language: C

_intbig_in

Function: public._intbig_out(public.intbig_gkey)

Returns: cstring

Language: C

_intbig_out

Function: public.agg_text(text)

Returns: text

Language: INTERNAL

aggregate_dummy

Function: public.akeys(public.hstore)

Returns: text[]

Language: C

hstore_akeys

Function: public.approximate_date(text, text)

Returns: text

Language: SQL

        SELECT REGEXP_REPLACE( $1, E'\\D', $2, 'g' );

Function: public.approximate_high_date(text)

Returns: text

Language: SQL

        SELECT approximate_date( $1, '9');

Function: public.approximate_low_date(text)

Returns: text

Language: SQL

        SELECT approximate_date( $1, '0');

Function: public.armor(bytea)

Returns: text

Language: C

pg_armor

Function: public.armor(bytea, text[], text[])

Returns: text

Language: C

pg_armor

Function: public.avals(public.hstore)

Returns: text[]

Language: C

hstore_avals

Function: public.boolop(integer[], public.query_int)

Returns: boolean

Language: C

boolean operation with array

boolop

Function: public.bqarr_in(cstring)

Returns: query_int

Language: C

bqarr_in

Function: public.bqarr_out(public.query_int)

Returns: cstring

Language: C

bqarr_out

Function: public.call_number_dewey(text)

Returns: text

Language: PLPERLU

	my $txt = shift;
	$txt =~ s/^\s+//o;
	$txt =~ s/[\[\]\{\}\(\)`'"#<>\*\?\-\+\$\\]+//og;
	$txt =~ s/\s+$//o;
	if ($txt =~ /(\d{3}(?:\.\d+)?)/o) {
		return $1;
	} else {
		return (split /\s+/, $txt)[0];
	}

Function: public.call_number_dewey(text, integer)

Returns: text

Language: SQL

	SELECT SUBSTRING(call_number_dewey($1) FROM 1 FOR $2);

Function: public.cleanup_acq_marc()

Returns: trigger

Language: PLPGSQL

BEGIN
	IF TG_OP = 'UPDATE' THEN
		DELETE FROM acq.lineitem_attr
	    		WHERE lineitem = OLD.id AND attr_type IN ('lineitem_provider_attr_definition', 'lineitem_marc_attr_definition','lineitem_generated_attr_definition');
		RETURN NEW;
	ELSE
		DELETE FROM acq.lineitem_attr WHERE lineitem = OLD.id;
		RETURN OLD;
	END IF;
END;

Function: public.content_or_null(text)

Returns: text

Language: SQL

        SELECT CASE WHEN $1 ~ E'^\\s*$' THEN NULL ELSE $1 END

Function: public.crypt(text, text)

Returns: text

Language: C

pg_crypt

Function: public.cube(double precision)

Returns: cube

Language: C

cube_f8

Function: public.cube(double precision, double precision)

Returns: cube

Language: C

cube_f8_f8

Function: public.cube(double precision[])

Returns: cube

Language: C

cube_a_f8

Function: public.cube(double precision[], double precision[])

Returns: cube

Language: C

cube_a_f8_f8

Function: public.cube(public.cube, double precision)

Returns: cube

Language: C

cube_c_f8

Function: public.cube(public.cube, double precision, double precision)

Returns: cube

Language: C

cube_c_f8_f8

Function: public.cube_cmp(public.cube, public.cube)

Returns: integer

Language: C

btree comparison function

cube_cmp

Function: public.cube_contained(public.cube, public.cube)

Returns: boolean

Language: C

contained in

cube_contained

Function: public.cube_contains(public.cube, public.cube)

Returns: boolean

Language: C

contains

cube_contains

Function: public.cube_coord(public.cube, integer)

Returns: double precision

Language: C

cube_coord

Function: public.cube_coord_llur(public.cube, integer)

Returns: double precision

Language: C

cube_coord_llur

Function: public.cube_dim(public.cube)

Returns: integer

Language: C

cube_dim

Function: public.cube_distance(public.cube, public.cube)

Returns: double precision

Language: C

cube_distance

Function: public.cube_enlarge(public.cube, double precision, integer)

Returns: cube

Language: C

cube_enlarge

Function: public.cube_eq(public.cube, public.cube)

Returns: boolean

Language: C

same as

cube_eq

Function: public.cube_ge(public.cube, public.cube)

Returns: boolean

Language: C

greater than or equal to

cube_ge

Function: public.cube_gt(public.cube, public.cube)

Returns: boolean

Language: C

greater than

cube_gt

Function: public.cube_in(cstring)

Returns: cube

Language: C

cube_in

Function: public.cube_inter(public.cube, public.cube)

Returns: cube

Language: C

cube_inter

Function: public.cube_is_point(public.cube)

Returns: boolean

Language: C

cube_is_point

Function: public.cube_le(public.cube, public.cube)

Returns: boolean

Language: C

lower than or equal to

cube_le

Function: public.cube_ll_coord(public.cube, integer)

Returns: double precision

Language: C

cube_ll_coord

Function: public.cube_lt(public.cube, public.cube)

Returns: boolean

Language: C

lower than

cube_lt

Function: public.cube_ne(public.cube, public.cube)

Returns: boolean

Language: C

different

cube_ne

Function: public.cube_out(public.cube)

Returns: cstring

Language: C

cube_out

Function: public.cube_overlap(public.cube, public.cube)

Returns: boolean

Language: C

overlaps

cube_overlap

Function: public.cube_size(public.cube)

Returns: double precision

Language: C

cube_size

Function: public.cube_subset(public.cube, integer[])

Returns: cube

Language: C

cube_subset

Function: public.cube_union(public.cube, public.cube)

Returns: cube

Language: C

cube_union

Function: public.cube_ur_coord(public.cube, integer)

Returns: double precision

Language: C

cube_ur_coord

Function: public.dearmor(text)

Returns: bytea

Language: C

pg_dearmor

Function: public.decrypt(bytea, bytea, text)

Returns: bytea

Language: C

pg_decrypt

Function: public.decrypt_iv(bytea, bytea, bytea, text)

Returns: bytea

Language: C

pg_decrypt_iv

Function: public.defined(public.hstore, text)

Returns: boolean

Language: C

hstore_defined

Function: public.delete(public.hstore, public.hstore)

Returns: hstore

Language: C

hstore_delete_hstore

Function: public.delete(public.hstore, text)

Returns: hstore

Language: C

hstore_delete

Function: public.delete(public.hstore, text[])

Returns: hstore

Language: C

hstore_delete_array

Function: public.difference(text, text)

Returns: integer

Language: C

difference

Function: public.digest(bytea, text)

Returns: bytea

Language: C

pg_digest

Function: public.digest(text, text)

Returns: bytea

Language: C

pg_digest

Function: public.distance_chebyshev(public.cube, public.cube)

Returns: double precision

Language: C

distance_chebyshev

Function: public.distance_taxicab(public.cube, public.cube)

Returns: double precision

Language: C

distance_taxicab

Function: public.dmetaphone(text)

Returns: text

Language: C

dmetaphone

Function: public.dmetaphone_alt(text)

Returns: text

Language: C

dmetaphone_alt

Function: public.each(value public.hstore)

Returns: SET OF record

Language: C

hstore_each

Function: public.earth()

Returns: double precision

Language: SQL

SELECT '6378168'::float8

Function: public.earth_box(public.earth, double precision)

Returns: cube

Language: SQL

SELECT cube_enlarge($1, gc_to_sec($2), 3)

Function: public.earth_distance(public.earth, public.earth)

Returns: double precision

Language: SQL

SELECT sec_to_gc(cube_distance($1, $2))

Function: public.encrypt(bytea, bytea, text)

Returns: bytea

Language: C

pg_encrypt

Function: public.encrypt_iv(bytea, bytea, bytea, text)

Returns: bytea

Language: C

pg_encrypt_iv

Function: public.entityize(text)

Returns: text

Language: PLPERLU

    use Unicode::Normalize;

    my $x = NFC(shift);
    $x =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
    return $x;


Function: public.exist(public.hstore, text)

Returns: boolean

Language: C

hstore_exists

Function: public.exists_all(public.hstore, text[])

Returns: boolean

Language: C

hstore_exists_all

Function: public.exists_any(public.hstore, text[])

Returns: boolean

Language: C

hstore_exists_any

Function: public.extract_acq_marc_field(bigint, text, text)

Returns: text

Language: SQL

	SELECT extract_marc_field('acq.lineitem', $1, $2, $3);

Function: public.extract_acq_marc_field_set(bigint, text, text)

Returns: SET OF text

Language: SQL

	SELECT extract_marc_field_set('acq.lineitem', $1, $2, $3);

Function: public.fetchval(public.hstore, text)

Returns: text

Language: C

hstore_fetchval

Function: public.first(anyelement)

Returns: anyelement

Language: INTERNAL

aggregate_dummy

Function: public.first5(text)

Returns: text

Language: SQL

	SELECT SUBSTRING( $1, 1, 5);

Function: public.first_agg(anyelement, anyelement)

Returns: anyelement

Language: SQL

	SELECT CASE WHEN $1 IS NULL THEN $2 ELSE $1 END;

Function: public.first_word(text)

Returns: text

Language: SQL

        SELECT COALESCE(SUBSTRING( $1 FROM $_$^\S+$_$), '');

Function: public.force_to_isbn13(text)

Returns: text

Language: PLPERLU

Inspired by translate_isbn1013 The force_to_isbn13 function takes an input ISBN and returns the ISBN13 version without hypens and with a repaired checksum if the checksum was bad

    use Business::ISBN;
    use strict;
    use warnings;

    # Find the first ISBN, force it to ISBN13 and return it

    my $input = shift;

    foreach my $word (split(/\s/, $input)) {
        my $isbn = Business::ISBN->new($word);

        # First check the checksum; if it is not valid, fix it and add the original
        # bad-checksum ISBN to the output
        if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
            $isbn->fix_checksum();
        }

        # If we now have a valid ISBN, force it to ISBN13 and return it
        return $isbn->as_isbn13->isbn if ($isbn && $isbn->is_valid());
    }
    return undef;

Function: public.g_cube_compress(internal)

Returns: internal

Language: C

g_cube_compress

Function: public.g_cube_consistent(internal, public.cube, smallint, oid, internal)

Returns: boolean

Language: C

g_cube_consistent

Function: public.g_cube_decompress(internal)

Returns: internal

Language: C

g_cube_decompress

Function: public.g_cube_distance(internal, public.cube, smallint, oid, internal)

Returns: double precision

Language: C

g_cube_distance

Function: public.g_cube_penalty(internal, internal, internal)

Returns: internal

Language: C

g_cube_penalty

Function: public.g_cube_picksplit(internal, internal)

Returns: internal

Language: C

g_cube_picksplit

Function: public.g_cube_same(public.cube, public.cube, internal)

Returns: internal

Language: C

g_cube_same

Function: public.g_cube_union(internal, internal)

Returns: cube

Language: C

g_cube_union

Function: public.g_int_compress(internal)

Returns: internal

Language: C

g_int_compress

Function: public.g_int_consistent(internal, integer[], smallint, oid, internal)

Returns: boolean

Language: C

g_int_consistent

Function: public.g_int_decompress(internal)

Returns: internal

Language: C

g_int_decompress

Function: public.g_int_penalty(internal, internal, internal)

Returns: internal

Language: C

g_int_penalty

Function: public.g_int_picksplit(internal, internal)

Returns: internal

Language: C

g_int_picksplit

Function: public.g_int_same(integer[], integer[], internal)

Returns: internal

Language: C

g_int_same

Function: public.g_int_union(internal, internal)

Returns: integer[]

Language: C

g_int_union

Function: public.g_intbig_compress(internal)

Returns: internal

Language: C

g_intbig_compress

Function: public.g_intbig_consistent(internal, integer[], smallint, oid, internal)

Returns: boolean

Language: C

g_intbig_consistent

Function: public.g_intbig_decompress(internal)

Returns: internal

Language: C

g_intbig_decompress

Function: public.g_intbig_penalty(internal, internal, internal)

Returns: internal

Language: C

g_intbig_penalty

Function: public.g_intbig_picksplit(internal, internal)

Returns: internal

Language: C

g_intbig_picksplit

Function: public.g_intbig_same(public.intbig_gkey, public.intbig_gkey, internal)

Returns: internal

Language: C

g_intbig_same

Function: public.g_intbig_union(internal, internal)

Returns: intbig_gkey

Language: C

g_intbig_union

Function: public.gc_to_sec(double precision)

Returns: double precision

Language: SQL

SELECT CASE WHEN $1 < 0 THEN 0::float8 WHEN $1/earth() > pi() THEN 2*earth() ELSE 2*earth()*sin($1/(2*earth())) END

Function: public.gen_random_bytes(integer)

Returns: bytea

Language: C

pg_random_bytes

Function: public.gen_random_uuid()

Returns: uuid

Language: C

pg_random_uuid

Function: public.gen_salt(text)

Returns: text

Language: C

pg_gen_salt

Function: public.gen_salt(text, integer)

Returns: text

Language: C

pg_gen_salt_rounds

Function: public.geo_distance(point, point)

Returns: double precision

Language: C

geo_distance

Function: public.ghstore_compress(internal)

Returns: internal

Language: C

ghstore_compress

Function: public.ghstore_consistent(internal, public.hstore, smallint, oid, internal)

Returns: boolean

Language: C

ghstore_consistent

Function: public.ghstore_decompress(internal)

Returns: internal

Language: C

ghstore_decompress

Function: public.ghstore_in(cstring)

Returns: ghstore

Language: C

ghstore_in

Function: public.ghstore_out(public.ghstore)

Returns: cstring

Language: C

ghstore_out

Function: public.ghstore_penalty(internal, internal, internal)

Returns: internal

Language: C

ghstore_penalty

Function: public.ghstore_picksplit(internal, internal)

Returns: internal

Language: C

ghstore_picksplit

Function: public.ghstore_same(public.ghstore, public.ghstore, internal)

Returns: internal

Language: C

ghstore_same

Function: public.ghstore_union(internal, internal)

Returns: ghstore

Language: C

ghstore_union

Function: public.gin_consistent_hstore(internal, smallint, public.hstore, integer, internal, internal)

Returns: boolean

Language: C

gin_consistent_hstore

Function: public.gin_extract_hstore(public.hstore, internal)

Returns: internal

Language: C

gin_extract_hstore

Function: public.gin_extract_hstore_query(public.hstore, internal, smallint, internal, internal)

Returns: internal

Language: C

gin_extract_hstore_query

Function: public.gin_extract_query_trgm(text, internal, smallint, internal, internal, internal, internal)

Returns: internal

Language: C

gin_extract_query_trgm

Function: public.gin_extract_value_trgm(text, internal)

Returns: internal

Language: C

gin_extract_value_trgm

Function: public.gin_trgm_consistent(internal, smallint, text, integer, internal, internal, internal, internal)

Returns: boolean

Language: C

gin_trgm_consistent

Function: public.gin_trgm_triconsistent(internal, smallint, text, integer, internal, internal, internal)

Returns: "char"

Language: C

gin_trgm_triconsistent

Function: public.ginint4_consistent(internal, smallint, integer[], integer, internal, internal, internal, internal)

Returns: boolean

Language: C

ginint4_consistent

Function: public.ginint4_queryextract(integer[], internal, smallint, internal, internal, internal, internal)

Returns: internal

Language: C

ginint4_queryextract

Function: public.gtrgm_compress(internal)

Returns: internal

Language: C

gtrgm_compress

Function: public.gtrgm_consistent(internal, text, smallint, oid, internal)

Returns: boolean

Language: C

gtrgm_consistent

Function: public.gtrgm_decompress(internal)

Returns: internal

Language: C

gtrgm_decompress

Function: public.gtrgm_distance(internal, text, smallint, oid, internal)

Returns: double precision

Language: C

gtrgm_distance

Function: public.gtrgm_in(cstring)

Returns: gtrgm

Language: C

gtrgm_in

Function: public.gtrgm_out(public.gtrgm)

Returns: cstring

Language: C

gtrgm_out

Function: public.gtrgm_penalty(internal, internal, internal)

Returns: internal

Language: C

gtrgm_penalty

Function: public.gtrgm_picksplit(internal, internal)

Returns: internal

Language: C

gtrgm_picksplit

Function: public.gtrgm_same(public.gtrgm, public.gtrgm, internal)

Returns: internal

Language: C

gtrgm_same

Function: public.gtrgm_union(internal, internal)

Returns: gtrgm

Language: C

gtrgm_union

Function: public.hmac(bytea, bytea, text)

Returns: bytea

Language: C

pg_hmac

Function: public.hmac(text, text, text)

Returns: bytea

Language: C

pg_hmac

Function: public.hs_concat(public.hstore, public.hstore)

Returns: hstore

Language: C

hstore_concat

Function: public.hs_contained(public.hstore, public.hstore)

Returns: boolean

Language: C

hstore_contained

Function: public.hs_contains(public.hstore, public.hstore)

Returns: boolean

Language: C

hstore_contains

Function: public.hstore(record)

Returns: hstore

Language: C

hstore_from_record

Function: public.hstore(text, text)

Returns: hstore

Language: C

hstore_from_text

Function: public.hstore(text[])

Returns: hstore

Language: C

hstore_from_array

Function: public.hstore(text[], text[])

Returns: hstore

Language: C

hstore_from_arrays

Function: public.hstore_cmp(public.hstore, public.hstore)

Returns: integer

Language: C

hstore_cmp

Function: public.hstore_eq(public.hstore, public.hstore)

Returns: boolean

Language: C

hstore_eq

Function: public.hstore_ge(public.hstore, public.hstore)

Returns: boolean

Language: C

hstore_ge

Function: public.hstore_gt(public.hstore, public.hstore)

Returns: boolean

Language: C

hstore_gt

Function: public.hstore_hash(public.hstore)

Returns: integer

Language: C

hstore_hash

Function: public.hstore_in(cstring)

Returns: hstore

Language: C

hstore_in

Function: public.hstore_le(public.hstore, public.hstore)

Returns: boolean

Language: C

hstore_le

Function: public.hstore_lt(public.hstore, public.hstore)

Returns: boolean

Language: C

hstore_lt

Function: public.hstore_ne(public.hstore, public.hstore)

Returns: boolean

Language: C

hstore_ne

Function: public.hstore_out(public.hstore)

Returns: cstring

Language: C

hstore_out

Function: public.hstore_recv(internal)

Returns: hstore

Language: C

hstore_recv

Function: public.hstore_send(public.hstore)

Returns: bytea

Language: C

hstore_send

Function: public.hstore_to_array(public.hstore)

Returns: text[]

Language: C

hstore_to_array

Function: public.hstore_to_json(public.hstore)

Returns: json

Language: C

hstore_to_json

Function: public.hstore_to_json_loose(public.hstore)

Returns: json

Language: C

hstore_to_json_loose

Function: public.hstore_to_jsonb(public.hstore)

Returns: jsonb

Language: C

hstore_to_jsonb

Function: public.hstore_to_jsonb_loose(public.hstore)

Returns: jsonb

Language: C

hstore_to_jsonb_loose

Function: public.hstore_to_matrix(public.hstore)

Returns: text[]

Language: C

hstore_to_matrix

Function: public.hstore_version_diag(public.hstore)

Returns: integer

Language: C

hstore_version_diag

Function: public.icount(integer[])

Returns: integer

Language: C

icount

Function: public.idx(integer[], integer)

Returns: integer

Language: C

idx

Function: public.ingest_acq_marc()

Returns: trigger

Language: PLPGSQL

DECLARE
	value		TEXT;
	atype		TEXT;
	prov		INT;
	pos 		INT;
	adef		RECORD;
	xpath_string	TEXT;
BEGIN
	FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP

		SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;

		IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
			IF (atype = 'lineitem_provider_attr_definition') THEN
				SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
				CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
			END IF;
			
			IF (atype = 'lineitem_provider_attr_definition') THEN
				SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
			ELSIF (atype = 'lineitem_marc_attr_definition') THEN
				SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
			ELSIF (atype = 'lineitem_generated_attr_definition') THEN
				SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
			END IF;

            xpath_string := REGEXP_REPLACE(xpath_string,$re$//?text\(\)$$re$,'');

            IF (adef.code = 'title' OR adef.code = 'author') THEN
                -- title and author should not be split
                -- FIXME: once oils_xpath can grok XPATH 2.0 functions, we can use
                -- string-join in the xpath and remove this special case
    			SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
    			IF (value IS NOT NULL AND value <> '') THEN
				    INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
	     			    VALUES (NEW.id, adef.id, atype, adef.code, value);
                END IF;
            ELSE
                pos := 1;
                LOOP
                    -- each application of the regex may produce multiple values
                    FOR value IN
                        SELECT * FROM extract_acq_marc_field_set(
                            NEW.id, xpath_string || '[' || pos || ']', adef.remove)
                        LOOP

                        IF (value IS NOT NULL AND value <> '') THEN
                            INSERT INTO acq.lineitem_attr
                                (lineitem, definition, attr_type, attr_name, attr_value)
                                VALUES (NEW.id, adef.id, atype, adef.code, value);
                        ELSE
                            EXIT;
                        END IF;
                    END LOOP;
                    IF NOT FOUND THEN
                        EXIT;
                    END IF;
                    pos := pos + 1;
               END LOOP;
            END IF;

		END IF;

	END LOOP;

	RETURN NULL;
END;

Function: public.intarray_del_elem(integer[], integer)

Returns: integer[]

Language: C

intarray_del_elem

Function: public.intarray_push_array(integer[], integer[])

Returns: integer[]

Language: C

intarray_push_array

Function: public.intarray_push_elem(integer[], integer)

Returns: integer[]

Language: C

intarray_push_elem

Function: public.integer_or_null(text)

Returns: text

Language: SQL

        SELECT CASE WHEN $1 ~ E'^\\d+$' THEN $1 ELSE NULL END

Function: public.intset(integer)

Returns: integer[]

Language: C

intset

Function: public.intset_subtract(integer[], integer[])

Returns: integer[]

Language: C

intset_subtract

Function: public.intset_union_elem(integer[], integer)

Returns: integer[]

Language: C

intset_union_elem

Function: public.isdefined(public.hstore, text)

Returns: boolean

Language: C

hstore_defined

Function: public.isexists(public.hstore, text)

Returns: boolean

Language: C

hstore_exists

Function: public.last(anyelement)

Returns: anyelement

Language: INTERNAL

aggregate_dummy

Function: public.last_agg(anyelement, anyelement)

Returns: anyelement

Language: SQL

	SELECT $2;

Function: public.latitude(public.earth)

Returns: double precision

Language: SQL

SELECT CASE WHEN cube_ll_coord($1, 3)/earth() < -1 THEN -90::float8 WHEN cube_ll_coord($1, 3)/earth() > 1 THEN 90::float8 ELSE degrees(asin(cube_ll_coord($1, 3)/earth())) END

Function: public.left_trunc(text, integer)

Returns: text

Language: SQL

        SELECT SUBSTRING($1,$2);

Function: public.levenshtein(text, text)

Returns: integer

Language: C

levenshtein

Function: public.levenshtein(text, text, integer, integer, integer)

Returns: integer

Language: C

levenshtein_with_costs

Function: public.levenshtein_less_equal(text, text, integer)

Returns: integer

Language: C

levenshtein_less_equal

Function: public.levenshtein_less_equal(text, text, integer, integer, integer, integer)

Returns: integer

Language: C

levenshtein_less_equal_with_costs

Function: public.ll_to_earth(double precision, double precision)

Returns: earth

Language: SQL

SELECT cube(cube(cube(earth()*cos(radians($1))*cos(radians($2))),earth()*cos(radians($1))*sin(radians($2))),earth()*sin(radians($1)))::earth

Function: public.longitude(public.earth)

Returns: double precision

Language: SQL

SELECT degrees(atan2(cube_ll_coord($1, 2), cube_ll_coord($1, 1)))

Function: public.lowercase(text)

Returns: text

Language: PLPERLU

    return lc(shift);

Function: public.metaphone(text, integer)

Returns: text

Language: C

metaphone

Function: public.naco_normalize(text)

Returns: text

Language: SQL

	SELECT public.naco_normalize($1,'');

Function: public.naco_normalize(text, text)

Returns: text

Language: PLPERLU


    use strict;
    use Unicode::Normalize;
    use Encode;

    my $str = shift;
    my $sf = shift;

    # Apply NACO normalization to input string; based on
    # https://www.loc.gov/aba/pcc/naco/documents/SCA_PccNormalization_Final_revised.pdf
    #
    # Note that unlike a strict reading of the NACO normalization rules,
    # output is returned as lowercase instead of uppercase for compatibility
    # with previous versions of the Evergreen naco_normalize routine.

    # Convert to upper-case first; even though final output will be lowercase, doing this will
    # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
    # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
    $str = uc $str;

    # remove non-filing strings
    $str =~ s/\x{0098}.*?\x{009C}//g;

    # Replace curly single and double quote-like characters with straight single and double quotes
    $str =~ s/[\x{2018}\x{2019}\x{201B}\x{FF07}\x{201A}]/\x{0027}/g;
    $str =~ s/[\x{201C}\x{201D}\x{201F}\x{FF0C}\x{201E}\x{2E42}]/\x{0022}/g;

    $str = NFKD($str);

    # additional substitutions - 3.6.
    $str =~ s/\x{00C6}/AE/g;
    $str =~ s/\x{00DE}/TH/g;
    $str =~ s/\x{0152}/OE/g;
    $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}]['/DDOLl/d;

    # transformations based on Unicode category codes
    $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;

	if ($sf && $sf =~ /^a/o) {
		my $commapos = index($str, ',');
		if ($commapos > -1) {
			if ($commapos != length($str) - 1) {
                $str =~ s/,/\x07/; # preserve first comma
			}
		}
	}

    # since we've stripped out the control characters, we can now
    # use a few as placeholders temporarily
    $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
    $str =~ s/[\p{Pc}\p{Pd}\p{Pe}\p{Pf}\p{Pi}\p{Po}\p{Ps}\p{Sk}\p{Sm}\p{So}\p{Zl}\p{Zp}\p{Zs}]/ /g;
    $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;

    # decimal digits
    $str =~ tr/\x{0660}-\x{0669}\x{06F0}-\x{06F9}\x{07C0}-\x{07C9}\x{0966}-\x{096F}\x{09E6}-\x{09EF}\x{0A66}-\x{0A6F}\x{0AE6}-\x{0AEF}\x{0B66}-\x{0B6F}\x{0BE6}-\x{0BEF}\x{0C66}-\x{0C6F}\x{0CE6}-\x{0CEF}\x{0D66}-\x{0D6F}\x{0E50}-\x{0E59}\x{0ED0}-\x{0ED9}\x{0F20}-\x{0F29}\x{1040}-\x{1049}\x{1090}-\x{1099}\x{17E0}-\x{17E9}\x{1810}-\x{1819}\x{1946}-\x{194F}\x{19D0}-\x{19D9}\x{1A80}-\x{1A89}\x{1A90}-\x{1A99}\x{1B50}-\x{1B59}\x{1BB0}-\x{1BB9}\x{1C40}-\x{1C49}\x{1C50}-\x{1C59}\x{A620}-\x{A629}\x{A8D0}-\x{A8D9}\x{A900}-\x{A909}\x{A9D0}-\x{A9D9}\x{AA50}-\x{AA59}\x{ABF0}-\x{ABF9}\x{FF10}-\x{FF19}/0-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-9/;

    # intentionally skipping step 8 of the NACO algorithm; if the string
    # gets normalized away, that's fine.

    # leading and trailing spaces
    $str =~ s/\s+/ /g;
    $str =~ s/^\s+//;
    $str =~ s/\s+$//g;

    return lc $str;

Function: public.naco_normalize_keep_comma(text)

Returns: text

Language: SQL

        SELECT public.naco_normalize($1,'a');

Function: public.non_filing_normalize(text, "char")

Returns: text

Language: SQL

        SELECT  SUBSTRING(
                        REGEXP_REPLACE(
                                REGEXP_REPLACE(
                                        $1,
                                        E'\W*$',
					''
				),
                                '  ',
                                ' '
                        ),
                        CASE
				WHEN $2::INT NOT BETWEEN 48 AND 57 THEN 1
				ELSE $2::TEXT::INT + 1
			END
		);

Function: public.normalize_space(text)

Returns: text

Language: SQL

    SELECT regexp_replace(regexp_replace(regexp_replace($1, E'\\n', ' ', 'g'), E'(?:^\\s+)|(\\s+$)', '', 'g'), E'\\s+', ' ', 'g');

Function: public.oils_tsearch2()

Returns: trigger

Language: PLPGSQL

DECLARE
    normalizer      RECORD;
    value           TEXT := '';
    temp_vector     TEXT := '';
    ts_rec          RECORD;
    cur_weight      "char";
BEGIN

    value := NEW.value;
    NEW.index_vector = ''::tsvector;

    IF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
        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.metabib_field_index_norm_map m ON (m.norm = n.id)
              WHERE field = NEW.field AND m.pos < 0
              ORDER BY m.pos LOOP
                EXECUTE 'SELECT ' || normalizer.func || '(' ||
                    quote_literal( value ) ||
                    CASE
                        WHEN normalizer.param_count > 0
                            THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
                            ELSE ''
                        END ||
                    ')' INTO value;

        END LOOP;

        NEW.value = 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.metabib_field_index_norm_map m ON (m.norm = n.id)
              WHERE field = NEW.field AND m.pos >= 0
              ORDER BY m.pos LOOP
                EXECUTE 'SELECT ' || normalizer.func || '(' ||
                    quote_literal( value ) ||
                    CASE
                        WHEN normalizer.param_count > 0
                            THEN ',' || REPLACE(REPLACE(BTRIM(normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
                            ELSE ''
                        END ||
                    ')' INTO value;

        END LOOP;
   END IF;

    IF TG_TABLE_NAME::TEXT ~ 'browse_entry$' THEN

        value :=  ARRAY_TO_STRING(
            evergreen.regexp_split_to_array(value, E'\\W+'), ' '
        );
        value := public.search_normalize(value);
        NEW.index_vector = to_tsvector(TG_ARGV[0]::regconfig, value);

    ELSIF TG_TABLE_NAME::TEXT ~ 'field_entry$' THEN
        FOR ts_rec IN

            SELECT DISTINCT m.ts_config, m.index_weight
            FROM config.metabib_class_ts_map m
                 LEFT JOIN metabib.record_attr_vector_list r ON (r.source = NEW.source)
                 LEFT JOIN config.coded_value_map ccvm ON (
                    ccvm.ctype IN ('item_lang', 'language') AND
                    ccvm.code = m.index_lang AND
                    r.vlist @> intset(ccvm.id)
                )
            WHERE m.field_class = TG_ARGV[0]
                AND m.active
                AND (m.always OR NOT EXISTS (SELECT 1 FROM config.metabib_field_ts_map WHERE metabib_field = NEW.field))
                AND (m.index_lang IS NULL OR ccvm.id IS NOT NULL)
                        UNION
            SELECT DISTINCT m.ts_config, m.index_weight
            FROM config.metabib_field_ts_map m
                 LEFT JOIN metabib.record_attr_vector_list r ON (r.source = NEW.source)
                 LEFT JOIN config.coded_value_map ccvm ON (
                    ccvm.ctype IN ('item_lang', 'language') AND
                    ccvm.code = m.index_lang AND
                    r.vlist @> intset(ccvm.id)
                )
            WHERE m.metabib_field = NEW.field
                AND m.active
                AND (m.index_lang IS NULL OR ccvm.id IS NOT NULL)
            ORDER BY index_weight ASC

        LOOP

            IF cur_weight IS NOT NULL AND cur_weight != ts_rec.index_weight THEN
                NEW.index_vector = NEW.index_vector || setweight(temp_vector::tsvector,cur_weight);
                temp_vector = '';
            END IF;

            cur_weight = ts_rec.index_weight;
            SELECT INTO temp_vector temp_vector || ' ' || to_tsvector(ts_rec.ts_config::regconfig, value)::TEXT;

        END LOOP;
        NEW.index_vector = NEW.index_vector || setweight(temp_vector::tsvector,cur_weight);
    ELSE
        NEW.index_vector = to_tsvector(TG_ARGV[0]::regconfig, value);
    END IF;

    RETURN NEW;
END;

Function: public.pgp_armor_headers(value text)

Returns: SET OF record

Language: C

pgp_armor_headers

Function: public.pgp_key_id(bytea)

Returns: text

Language: C

pgp_key_id_w

Function: public.pgp_pub_decrypt(bytea, bytea)

Returns: text

Language: C

pgp_pub_decrypt_text

Function: public.pgp_pub_decrypt(bytea, bytea, text)

Returns: text

Language: C

pgp_pub_decrypt_text

Function: public.pgp_pub_decrypt(bytea, bytea, text, text)

Returns: text

Language: C

pgp_pub_decrypt_text

Function: public.pgp_pub_decrypt_bytea(bytea, bytea)

Returns: bytea

Language: C

pgp_pub_decrypt_bytea

Function: public.pgp_pub_decrypt_bytea(bytea, bytea, text)

Returns: bytea

Language: C

pgp_pub_decrypt_bytea

Function: public.pgp_pub_decrypt_bytea(bytea, bytea, text, text)

Returns: bytea

Language: C

pgp_pub_decrypt_bytea

Function: public.pgp_pub_encrypt(text, bytea)

Returns: bytea

Language: C

pgp_pub_encrypt_text

Function: public.pgp_pub_encrypt(text, bytea, text)

Returns: bytea

Language: C

pgp_pub_encrypt_text

Function: public.pgp_pub_encrypt_bytea(bytea, bytea)

Returns: bytea

Language: C

pgp_pub_encrypt_bytea

Function: public.pgp_pub_encrypt_bytea(bytea, bytea, text)

Returns: bytea

Language: C

pgp_pub_encrypt_bytea

Function: public.pgp_sym_decrypt(bytea, text)

Returns: text

Language: C

pgp_sym_decrypt_text

Function: public.pgp_sym_decrypt(bytea, text, text)

Returns: text

Language: C

pgp_sym_decrypt_text

Function: public.pgp_sym_decrypt_bytea(bytea, text)

Returns: bytea

Language: C

pgp_sym_decrypt_bytea

Function: public.pgp_sym_decrypt_bytea(bytea, text, text)

Returns: bytea

Language: C

pgp_sym_decrypt_bytea

Function: public.pgp_sym_encrypt(text, text)

Returns: bytea

Language: C

pgp_sym_encrypt_text

Function: public.pgp_sym_encrypt(text, text, text)

Returns: bytea

Language: C

pgp_sym_encrypt_text

Function: public.pgp_sym_encrypt_bytea(bytea, text)

Returns: bytea

Language: C

pgp_sym_encrypt_bytea

Function: public.pgp_sym_encrypt_bytea(bytea, text, text)

Returns: bytea

Language: C

pgp_sym_encrypt_bytea

Function: public.populate_record(anyelement, public.hstore)

Returns: anyelement

Language: C

hstore_populate_record

Function: public.querytree(public.query_int)

Returns: text

Language: C

querytree

Function: public.rboolop(public.query_int, integer[])

Returns: boolean

Language: C

boolean operation with array

rboolop

Function: public.remove_commas(text)

Returns: text

Language: SQL

    SELECT regexp_replace($1, ',', '', 'g');

Function: public.remove_diacritics(text)

Returns: text

Language: PLPERLU

    use Unicode::Normalize;

    my $x = NFD(shift);
    $x =~ s/\pM+//go;
    return $x;


Function: public.remove_paren_substring(text)

Returns: text

Language: SQL

    SELECT regexp_replace($1, $$\([^)]+\)$$, '', 'g');

Function: public.remove_whitespace(text)

Returns: text

Language: SQL

    SELECT regexp_replace(normalize_space($1), E'\\s+', '', 'g');

Function: public.right_trunc(text, integer)

Returns: text

Language: SQL

        SELECT SUBSTRING($1,1,$2);

Function: public.search_normalize(text)

Returns: text

Language: SQL

	SELECT public.search_normalize($1,'');

Function: public.search_normalize(text, text)

Returns: text

Language: PLPERLU


    use strict;
    use Unicode::Normalize;
    use Encode;

    my $str = shift;
    my $sf = shift;

    # Apply NACO normalization to input string; based on
    # https://www.loc.gov/aba/pcc/naco/documents/SCA_PccNormalization_Final_revised.pdf
    #
    # Note that unlike a strict reading of the NACO normalization rules,
    # output is returned as lowercase instead of uppercase for compatibility
    # with previous versions of the Evergreen naco_normalize routine.

    # Convert to upper-case first; even though final output will be lowercase, doing this will
    # ensure that the German eszett (ß) and certain ligatures (ff, fi, ffl, etc.) will be handled correctly.
    # If there are any bugs in Perl's implementation of upcasing, they will be passed through here.
    $str = uc $str;

    # remove non-filing strings
    $str =~ s/\x{0098}.*?\x{009C}//g;

    # Replace curly single and double quote-like characters with straight single and double quotes
    $str =~ s/[\x{2018}\x{2019}\x{201B}\x{FF07}\x{201A}]/\x{0027}/g;
    $str =~ s/[\x{201C}\x{201D}\x{201F}\x{FF0C}\x{201E}\x{2E42}]/\x{0022}/g;

    $str = NFKD($str);

    # additional substitutions - 3.6.
    $str =~ s/\x{00C6}/AE/g;
    $str =~ s/\x{00DE}/TH/g;
    $str =~ s/\x{0152}/OE/g;
    $str =~ tr/\x{0110}\x{00D0}\x{00D8}\x{0141}\x{2113}\x{02BB}\x{02BC}][/DDOLl/d;

    # transformations based on Unicode category codes
    $str =~ s/[\p{Cc}\p{Cf}\p{Co}\p{Cs}\p{Lm}\p{Mc}\p{Me}\p{Mn}]//g;

	if ($sf && $sf =~ /^a/o) {
		my $commapos = index($str, ',');
		if ($commapos > -1) {
			if ($commapos != length($str) - 1) {
                $str =~ s/,/\x07/; # preserve first comma
			}
		}
	}

    # since we've stripped out the control characters, we can now
    # use a few as placeholders temporarily
    $str =~ tr/+&@\x{266D}\x{266F}#/\x01\x02\x03\x04\x05\x06/;
    $str =~ s/[\p{Pc}\p{Pd}\p{Pe}\p{Pf}\p{Pi}\p{Po}\p{Ps}\p{Sk}\p{Sm}\p{So}\p{Zl}\p{Zp}\p{Zs}]/ /g;
    $str =~ tr/\x01\x02\x03\x04\x05\x06\x07/+&@\x{266D}\x{266F}#,/;

    # decimal digits
    $str =~ tr/\x{0660}-\x{0669}\x{06F0}-\x{06F9}\x{07C0}-\x{07C9}\x{0966}-\x{096F}\x{09E6}-\x{09EF}\x{0A66}-\x{0A6F}\x{0AE6}-\x{0AEF}\x{0B66}-\x{0B6F}\x{0BE6}-\x{0BEF}\x{0C66}-\x{0C6F}\x{0CE6}-\x{0CEF}\x{0D66}-\x{0D6F}\x{0E50}-\x{0E59}\x{0ED0}-\x{0ED9}\x{0F20}-\x{0F29}\x{1040}-\x{1049}\x{1090}-\x{1099}\x{17E0}-\x{17E9}\x{1810}-\x{1819}\x{1946}-\x{194F}\x{19D0}-\x{19D9}\x{1A80}-\x{1A89}\x{1A90}-\x{1A99}\x{1B50}-\x{1B59}\x{1BB0}-\x{1BB9}\x{1C40}-\x{1C49}\x{1C50}-\x{1C59}\x{A620}-\x{A629}\x{A8D0}-\x{A8D9}\x{A900}-\x{A909}\x{A9D0}-\x{A9D9}\x{AA50}-\x{AA59}\x{ABF0}-\x{ABF9}\x{FF10}-\x{FF19}/0-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-90-9/;

    # intentionally skipping step 8 of the NACO algorithm; if the string
    # gets normalized away, that's fine.

    # leading and trailing spaces
    $str =~ s/\s+/ /g;
    $str =~ s/^\s+//;
    $str =~ s/\s+$//g;

    return lc $str;

Function: public.search_normalize_keep_comma(text)

Returns: text

Language: SQL

        SELECT public.search_normalize($1,'a');

Function: public.sec_to_gc(double precision)

Returns: double precision

Language: SQL

SELECT CASE WHEN $1 < 0 THEN 0::float8 WHEN $1/(2*earth()) > 1 THEN pi()*earth() ELSE 2*earth()*asin($1/(2*earth())) END

Function: public.set_limit(real)

Returns: real

Language: C

set_limit

Function: public.show_limit()

Returns: real

Language: C

show_limit

Function: public.show_trgm(text)

Returns: text[]

Language: C

show_trgm

Function: public.similarity(text, text)

Returns: real

Language: C

similarity

Function: public.similarity_dist(text, text)

Returns: real

Language: C

similarity_dist

Function: public.similarity_op(text, text)

Returns: boolean

Language: C

similarity_op

Function: public.skeys(public.hstore)

Returns: SET OF text

Language: C

hstore_skeys

Function: public.slice(public.hstore, text[])

Returns: hstore

Language: C

hstore_slice_to_hstore

Function: public.slice_array(public.hstore, text[])

Returns: text[]

Language: C

hstore_slice_to_array

Function: public.sort(integer[])

Returns: integer[]

Language: C

sort

Function: public.sort(integer[], text)

Returns: integer[]

Language: C

sort

Function: public.sort_asc(integer[])

Returns: integer[]

Language: C

sort_asc

Function: public.sort_desc(integer[])

Returns: integer[]

Language: C

sort_desc

Function: public.soundex(text)

Returns: text

Language: C

soundex

Function: public.split_date_range(text)

Returns: text

Language: SQL

        SELECT REGEXP_REPLACE( $1, E'(\\d{4})-(\\d{4})', E'\\1 \\2', 'g' );

Function: public.subarray(integer[], integer)

Returns: integer[]

Language: C

subarray

Function: public.subarray(integer[], integer, integer)

Returns: integer[]

Language: C

subarray

Function: public.svals(public.hstore)

Returns: SET OF text

Language: C

hstore_svals

Function: public.tconvert(text, text)

Returns: hstore

Language: C

hstore_from_text

Function: public.text_concat(text, text)

Returns: text

Language: SQL

SELECT
	CASE	WHEN $1 IS NULL
			THEN $2
		WHEN $2 IS NULL
			THEN $1
		ELSE $1 || ' ' || $2
	END;

Function: public.text_soundex(text)

Returns: text

Language: C

soundex

Function: public.translate_isbn1013(text)

Returns: text

Language: PLPERLU

The translate_isbn1013 function takes an input ISBN and returns the following in a single space-delimited string if the input ISBN is valid: - The normalized input ISBN (hyphens stripped) - The normalized input ISBN with a fixed checksum if the checksum was bad - The ISBN converted to its ISBN10 or ISBN13 counterpart, if possible

    use Business::ISBN;
    use strict;
    use warnings;

    # For each ISBN found in a single string containing a set of ISBNs:
    #   * Normalize an incoming ISBN to have the correct checksum and no hyphens
    #   * Convert an incoming ISBN10 or ISBN13 to its counterpart and return

    my $input = shift;
    my $output = '';

    foreach my $word (split(/\s/, $input)) {
        my $isbn = Business::ISBN->new($word);

        # First check the checksum; if it is not valid, fix it and add the original
        # bad-checksum ISBN to the output
        if ($isbn && $isbn->is_valid_checksum() == Business::ISBN::BAD_CHECKSUM) {
            $output .= $isbn->isbn() . " ";
            $isbn->fix_checksum();
        }

        # If we now have a valid ISBN, convert it to its counterpart ISBN10/ISBN13
        # and add the normalized original ISBN to the output
        if ($isbn && $isbn->is_valid()) {
            my $isbn_xlated = ($isbn->type eq "ISBN13") ? $isbn->as_isbn10 : $isbn->as_isbn13;
            $output .= $isbn->isbn . " ";

            # If we successfully converted the ISBN to its counterpart, add the
            # converted ISBN to the output as well
            $output .= ($isbn_xlated->isbn . " ") if ($isbn_xlated);
        }
    }
    return $output if $output;

    # If there were no valid ISBNs, just return the raw input
    return $input;

Function: public.unaccent(regdictionary, text)

Returns: text

Language: C

unaccent_dict

Function: public.unaccent(text)

Returns: text

Language: C

unaccent_dict

Function: public.unaccent_init(internal)

Returns: internal

Language: C

unaccent_init

Function: public.unaccent_lexize(internal, internal, internal, internal)

Returns: internal

Language: C

unaccent_lexize

Function: public.uniq(integer[])

Returns: integer[]

Language: C

uniq

Function: public.uppercase(text)

Returns: text

Language: PLPERLU

    return uc(shift);

Function: public.word_similarity(text, text)

Returns: real

Language: C

word_similarity

Function: public.word_similarity_commutator_op(text, text)

Returns: boolean

Language: C

word_similarity_commutator_op

Function: public.word_similarity_dist_commutator_op(text, text)

Returns: real

Language: C

word_similarity_dist_commutator_op

Function: public.word_similarity_dist_op(text, text)

Returns: real

Language: C

word_similarity_dist_op

Function: public.word_similarity_op(text, text)

Returns: boolean

Language: C

word_similarity_op

Function: public.xml_encode_special_chars(text)

Returns: text

Language: C

xml_encode_special_chars

Function: public.xml_valid(text)

Returns: boolean

Language: INTERNAL

xml_is_well_formed

Function: public.xpath_bool(text, text)

Returns: boolean

Language: C

xpath_bool

Function: public.xpath_list(text, text)

Returns: text

Language: SQL

SELECT xpath_list($1,$2,',')

Function: public.xpath_list(text, text, text)

Returns: text

Language: C

xpath_list

Function: public.xpath_nodeset(text, text)

Returns: text

Language: SQL

SELECT xpath_nodeset($1,$2,'','')

Function: public.xpath_nodeset(text, text, text)

Returns: text

Language: SQL

SELECT xpath_nodeset($1,$2,'',$3)

Function: public.xpath_nodeset(text, text, text, text)

Returns: text

Language: C

xpath_nodeset

Function: public.xpath_number(text, text)

Returns: real

Language: C

xpath_number

Function: public.xpath_string(text, text)

Returns: text

Language: C

xpath_string

Function: public.xpath_table(text, text, text, text, text)

Returns: SET OF record

Language: C

xpath_table

Function: public.xslt_process(text, text)

Returns: text

Language: C

xslt_process

Function: public.xslt_process(text, text, text)

Returns: text

Language: C

xslt_process

Schema query

Contains tables designed to represent user-defined queries for reports and the like.


Table: query.bind_variable

query.bind_variable Structure
F-Key Name Type Description
name text PRIMARY KEY
type text NOT NULL
description text NOT NULL
default_value text
label text NOT NULL

 

query.bind_variable Constraints
Name Constraint
bind_variable_type CHECK ((type = ANY (ARRAY['string'::text, 'number'::text, 'string_list'::text, 'number_list'::text])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema query


Table: query.case_branch

query.case_branch Structure
F-Key Name Type Description
id serial PRIMARY KEY
query.expression.id parent_expr integer UNIQUE#1 NOT NULL
seq_no integer UNIQUE#1 NOT NULL
query.expression.id condition integer
query.expression.id result integer NOT NULL

Index - Schema query


Table: query.datatype

query.datatype Structure
F-Key Name Type Description
id serial PRIMARY KEY
datatype_name text UNIQUE NOT NULL
is_numeric boolean NOT NULL DEFAULT false
is_composite boolean NOT NULL DEFAULT false

 

query.datatype Constraints
Name Constraint
qdt_comp_not_num CHECK (((is_numeric IS FALSE) OR (is_composite IS FALSE)))

Tables referencing this one via Foreign Key Constraints:

Index - Schema query


View: query.expr_xbet

query.expr_xbet Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
left_operand integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.left_operand
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xbet'::text);

Index - Schema query


View: query.expr_xbind

query.expr_xbind Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
bind_variable text
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.bind_variable
   
FROM query.expression
  
WHERE (expression.type = 'xbind'::text);

Index - Schema query


View: query.expr_xbool

query.expr_xbool Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
literal text
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.literal
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xbool'::text);

Index - Schema query


View: query.expr_xcase

query.expr_xcase Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
left_operand integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.left_operand
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xcase'::text);

Index - Schema query


View: query.expr_xcast

query.expr_xcast Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
left_operand integer
cast_type integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.left_operand
,
    expression.cast_type
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xcast'::text);

Index - Schema query


View: query.expr_xcol

query.expr_xcol Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
table_alias text
column_name text
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.table_alias
,
    expression.column_name
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xcol'::text);

Index - Schema query


View: query.expr_xex

query.expr_xex Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
subquery integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.subquery
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xex'::text);

Index - Schema query


View: query.expr_xfunc

query.expr_xfunc Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
column_name text
function_id integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.column_name
,
    expression.function_id
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xfunc'::text);

Index - Schema query


View: query.expr_xin

query.expr_xin Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
left_operand integer
subquery integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.left_operand
,
    expression.subquery
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xin'::text);

Index - Schema query


View: query.expr_xisnull

query.expr_xisnull Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
left_operand integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.left_operand
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xisnull'::text);

Index - Schema query


View: query.expr_xnull

query.expr_xnull Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xnull'::text);

Index - Schema query


View: query.expr_xnum

query.expr_xnum Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
literal text
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.literal
   
FROM query.expression
  
WHERE (expression.type = 'xnum'::text);

Index - Schema query


View: query.expr_xop

query.expr_xop Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
left_operand integer
operator text
right_operand integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.left_operand
,
    expression.operator
,
    expression.right_operand
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xop'::text);

Index - Schema query


View: query.expr_xser

query.expr_xser Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
operator text
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.operator
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xser'::text);

Index - Schema query


View: query.expr_xstr

query.expr_xstr Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
literal text
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.literal
   
FROM query.expression
  
WHERE (expression.type = 'xstr'::text);

Index - Schema query


View: query.expr_xsubq

query.expr_xsubq Structure
F-Key Name Type Description
id integer
parenthesize boolean
parent_expr integer
seq_no integer
subquery integer
negate boolean
SELECT expression.id
,
    expression.parenthesize
,
    expression.parent_expr
,
    expression.seq_no
,
    expression.subquery
,
    expression.negate
   
FROM query.expression
  
WHERE (expression.type = 'xsubq'::text);

Index - Schema query


Table: query.expression

query.expression Structure
F-Key Name Type Description
id serial PRIMARY KEY
type text NOT NULL
parenthesize boolean NOT NULL DEFAULT false
query.expression.id parent_expr integer
seq_no integer NOT NULL DEFAULT 1
literal text
table_alias text
column_name text
query.expression.id left_operand integer
operator text
query.expression.id right_operand integer
query.function_sig.id function_id integer
query.stored_query.id subquery integer
query.datatype.id cast_type integer
negate boolean NOT NULL DEFAULT false
query.bind_variable.name bind_variable text

 

query.expression Constraints
Name Constraint
expression_type CHECK ((type = ANY (ARRAY['xbet'::text, 'xbind'::text, 'xbool'::text, 'xcase'::text, 'xcast'::text, 'xcol'::text, 'xex'::text, 'xfunc'::text, 'xin'::text, 'xisnull'::text, 'xnull'::text, 'xnum'::text, 'xop'::text, 'xser'::text, 'xstr'::text, 'xsubq'::text])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema query


Table: query.from_relation

query.from_relation Structure
F-Key Name Type Description
id serial PRIMARY KEY
type text NOT NULL
table_name text
class_name text
query.stored_query.id subquery integer
query.expression.id function_call integer
table_alias text
query.from_relation.id parent_relation integer
seq_no integer NOT NULL DEFAULT 1
join_type text
query.expression.id on_clause integer

 

query.from_relation Constraints
Name Constraint
good_join_type CHECK (((join_type IS NULL) OR (join_type = ANY (ARRAY['INNER'::text, 'LEFT'::text, 'RIGHT'::text, 'FULL'::text]))))
join_or_core CHECK ((((parent_relation IS NULL) AND (join_type IS NULL) AND (on_clause IS NULL)) OR ((parent_relation IS NOT NULL) AND (join_type IS NOT NULL) AND (on_clause IS NOT NULL))))
relation_type CHECK ((type = ANY (ARRAY['RELATION'::text, 'SUBQUERY'::text, 'FUNCTION'::text])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema query


Table: query.function_param_def

query.function_param_def Structure
F-Key Name Type Description
id serial PRIMARY KEY
query.function_sig.id function_id integer UNIQUE#1 NOT NULL
seq_no integer UNIQUE#1 NOT NULL
query.datatype.id datatype integer NOT NULL

 

query.function_param_def Constraints
Name Constraint
qfpd_pos_seq_no CHECK ((seq_no > 0))

Index - Schema query


Table: query.function_sig

query.function_sig Structure
F-Key Name Type Description
id serial PRIMARY KEY
function_name text NOT NULL
query.datatype.id return_type integer
is_aggregate boolean NOT NULL DEFAULT false

 

query.function_sig Constraints
Name Constraint
qfd_rtn_or_aggr CHECK (((return_type IS NULL) OR (is_aggregate = false)))

Tables referencing this one via Foreign Key Constraints:

query_function_sig_name_idx function_name

Index - Schema query


Table: query.order_by_item

query.order_by_item Structure
F-Key Name Type Description
id serial PRIMARY KEY
query.stored_query.id stored_query integer UNIQUE#1 NOT NULL
seq_no integer UNIQUE#1 NOT NULL
query.expression.id expression integer NOT NULL

Index - Schema query


Table: query.query_sequence

query.query_sequence Structure
F-Key Name Type Description
id serial PRIMARY KEY
query.stored_query.id parent_query integer UNIQUE#1 NOT NULL
seq_no integer UNIQUE#1 NOT NULL
query.stored_query.id child_query integer NOT NULL

Index - Schema query


Table: query.record_column

query.record_column Structure
F-Key Name Type Description
id serial PRIMARY KEY
query.from_relation.id from_relation integer UNIQUE#1 NOT NULL
seq_no integer UNIQUE#1 NOT NULL
column_name text NOT NULL
query.datatype.id column_type integer NOT NULL

Index - Schema query


Table: query.select_item

query.select_item Structure
F-Key Name Type Description
id serial PRIMARY KEY
query.stored_query.id stored_query integer UNIQUE#1 NOT NULL
seq_no integer UNIQUE#1 NOT NULL
query.expression.id expression integer NOT NULL
column_alias text
grouped_by boolean NOT NULL DEFAULT false

Index - Schema query


Table: query.stored_query

query.stored_query Structure
F-Key Name Type Description
id serial PRIMARY KEY
type text NOT NULL
use_all boolean NOT NULL DEFAULT false
use_distinct boolean NOT NULL DEFAULT false
query.from_relation.id from_clause integer
query.expression.id where_clause integer
query.expression.id having_clause integer
query.expression.id limit_count integer
query.expression.id offset_count integer

 

query.stored_query Constraints
Name Constraint
query_type CHECK ((type = ANY (ARRAY['SELECT'::text, 'UNION'::text, 'INTERSECT'::text, 'EXCEPT'::text])))

Tables referencing this one via Foreign Key Constraints:

Index - Schema query


Table: query.subfield

query.subfield Structure
F-Key Name Type Description
id serial PRIMARY KEY
query.datatype.id composite_type integer UNIQUE#1 NOT NULL
seq_no integer UNIQUE#1 NOT NULL
query.datatype.id subfield_type integer NOT NULL

 

query.subfield Constraints
Name Constraint
qsf_pos_seq_no CHECK ((seq_no > 0))

Index - Schema query


Schema rating


Table: rating.badge

rating.badge Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
description text
actor.org_unit.id scope integer UNIQUE#1 NOT NULL
weight integer NOT NULL DEFAULT 1
horizon_age interval
importance_age interval
importance_interval interval NOT NULL DEFAULT '1 day'::interval
importance_scale numeric
recalc_interval interval NOT NULL DEFAULT '1 mon'::interval
attr_filter text
config.bib_source.id src_filter integer
config.circ_modifier.code circ_mod_filter text
asset.copy_location_group.id loc_grp_filter integer
rating.popularity_parameter.id popularity_parameter integer NOT NULL
fixed_rating integer
percentile numeric
discard integer NOT NULL
last_calc timestamp with time zone

 

rating.badge Constraints
Name Constraint
badge_fixed_rating_check CHECK (((fixed_rating IS NULL) OR ((fixed_rating >= '-5'::integer) AND (fixed_rating <= 5))))
badge_importance_scale_check CHECK (((importance_scale IS NULL) OR (importance_scale > 0.0)))
badge_percentile_check CHECK (((percentile IS NULL) OR ((percentile >= 50.0) AND (percentile < 100.0))))

Tables referencing this one via Foreign Key Constraints:

Index - Schema rating


View: rating.badge_with_orgs

rating.badge_with_orgs Structure
F-Key Name Type Description
id integer
name text
description text
scope integer
weight integer
horizon_age interval
importance_age interval
importance_interval interval
importance_scale numeric
recalc_interval interval
attr_filter text
src_filter integer
circ_mod_filter text
loc_grp_filter integer
popularity_parameter integer
fixed_rating integer
percentile numeric
discard integer
last_calc timestamp with time zone
orgs integer[]
 WITH org_scope AS 
(
         
SELECT x.id
     ,
            array_agg
     (x.tree) AS orgs
           
  FROM (
      SELECT org_unit.id
           ,
                    
           (actor.org_unit_descendants
                 (org_unit.id)
           ).id AS tree
                   
        FROM actor.org_unit
     ) x
          
GROUP BY x.id
        
)
 
SELECT b.id
,
    b.name
,
    b.description
,
    b.scope
,
    b.weight
,
    b.horizon_age
,
    b.importance_age
,
    b.importance_interval
,
    b.importance_scale
,
    b.recalc_interval
,
    b.attr_filter
,
    b.src_filter
,
    b.circ_mod_filter
,
    b.loc_grp_filter
,
    b.popularity_parameter
,
    b.fixed_rating
,
    b.percentile
,
    b.discard
,
    b.last_calc
,
    s.orgs
   
FROM (rating.badge b
     
  JOIN org_scope s 
    ON (
           (b.scope = s.id)
     )
);

Index - Schema rating


Table: rating.popularity_parameter

rating.popularity_parameter Structure
F-Key Name Type Description
id integer PRIMARY KEY
name text UNIQUE NOT NULL
description text
func text
require_horizon boolean NOT NULL DEFAULT false
require_importance boolean NOT NULL DEFAULT false
require_percentile boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema rating


Table: rating.record_badge_score

rating.record_badge_score Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id record bigint UNIQUE#1 NOT NULL
rating.badge.id badge integer UNIQUE#1 NOT NULL
score integer NOT NULL

 

rating.record_badge_score Constraints
Name Constraint
record_badge_score_score_check CHECK (((score >= '-5'::integer) AND (score <= 5)))
record_badge_score_badge_idx badge record_badge_score_record_idx record

Index - Schema rating


Function: rating.bib_pub_age(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy_or_uri(badge_id);

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_bib_list;
    CREATE TEMP TABLE precalc_bib_list ON COMMIT DROP AS
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_or_uri_list;

    RETURN QUERY
     SELECT pop.id AS bib,
            s.value::NUMERIC
      FROM  precalc_bib_list pop
            JOIN metabib.record_sorter s ON (
                s.source = pop.id
                AND s.attr = 'pubdate'
                AND s.value ~ '^\d+$'
            )
      WHERE s.value::INT <= EXTRACT(YEAR FROM NOW())::INT;
END;

Function: rating.bib_record_age(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy_or_uri(badge_id);

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_bib_list;
    CREATE TEMP TABLE precalc_bib_list ON COMMIT DROP AS
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_or_uri_list;

    RETURN QUERY
     SELECT b.id,
            1.0 / EXTRACT(EPOCH FROM AGE(b.create_date))::NUMERIC + 1.0
      FROM  precalc_bib_list pop
            JOIN biblio.record_entry b ON (b.id = pop.id);
END;

Function: rating.checked_out_total_ratio(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );

    ANALYZE precalc_copy_filter_bib_list;

    RETURN QUERY
     SELECT bib,
            SUM(checked_out)::NUMERIC / SUM(total)::NUMERIC
      FROM  (SELECT cn.record AS bib,
                    (cp.status = 1)::INT AS checked_out,
                    1 AS total
              FROM  asset.copy cp
                    JOIN precalc_copy_filter_bib_list c ON (cp.id = c.copy)
                    JOIN asset.call_number cn ON (cn.id = cp.call_number)
              WHERE cn.owning_lib = ANY (badge.orgs)
            ) x
      GROUP BY 1;
END;

Function: rating.circs_over_time(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
    iage    INT     := 1;
    iint    INT     := NULL;
    iscale  NUMERIC := NULL;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    IF badge.horizon_age IS NULL THEN
        RAISE EXCEPTION 'Badge "%" with id % requires a horizon age but has none.',
            badge.name,
            badge.id;
    END IF;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );

    ANALYZE precalc_copy_filter_bib_list;

    iint := EXTRACT(EPOCH FROM badge.importance_interval);
    IF badge.importance_age IS NOT NULL THEN
        iage := (EXTRACT(EPOCH FROM badge.importance_age) / iint)::INT;
    END IF;

    -- if iscale is smaller than 1, scaling slope will be shallow ... BEWARE!
    iscale := COALESCE(badge.importance_scale, 1.0);

    RETURN QUERY
     SELECT bib,
            SUM( circs * GREATEST( iscale * (iage - cage), 1.0 ))
      FROM (
         SELECT cn.record AS bib,
                (1 + EXTRACT(EPOCH FROM AGE(c.xact_start)) / iint)::INT AS cage,
                COUNT(c.id)::INT AS circs
          FROM  action.circulation c
                JOIN precalc_copy_filter_bib_list cf ON (c.target_copy = cf.copy)
                JOIN asset.copy cp ON (cp.id = c.target_copy)
                JOIN asset.call_number cn ON (cn.id = cp.call_number)
          WHERE c.xact_start >= NOW() - badge.horizon_age
                AND cn.owning_lib = ANY (badge.orgs)
                AND c.phone_renewal IS FALSE  -- we don't count renewals
                AND c.desk_renewal IS FALSE
                AND c.opac_renewal IS FALSE
          GROUP BY 1, 2
      ) x
      GROUP BY 1;
END;

Function: rating.copy_count(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );
    ANALYZE precalc_copy_filter_bib_list;

    RETURN QUERY
     SELECT f.id::INT AS bib,
            COUNT(f.copy)::NUMERIC
      FROM  precalc_copy_filter_bib_list f
            JOIN asset.copy cp ON (f.copy = cp.id)
            JOIN asset.call_number cn ON (cn.id = cp.call_number)
      WHERE cn.owning_lib = ANY (badge.orgs) GROUP BY 1;

END;

Function: rating.current_circ_count(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );

    ANALYZE precalc_copy_filter_bib_list;

    RETURN QUERY
     SELECT cn.record AS bib,
            COUNT(c.id)::NUMERIC AS circs
      FROM  action.circulation c
            JOIN precalc_copy_filter_bib_list cf ON (c.target_copy = cf.copy)
            JOIN asset.copy cp ON (cp.id = c.target_copy)
            JOIN asset.call_number cn ON (cn.id = cp.call_number)
      WHERE c.checkin_time IS NULL
            AND cn.owning_lib = ANY (badge.orgs)
      GROUP BY 1;

END;

Function: rating.current_hold_count(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );

    ANALYZE precalc_copy_filter_bib_list;

    RETURN QUERY
     SELECT rhrr.bib_record AS bib,
            COUNT(DISTINCT h.id)::NUMERIC AS holds
      FROM  action.hold_request h
            JOIN reporter.hold_request_record rhrr ON (rhrr.id = h.id)
            JOIN action.hold_copy_map m ON (m.hold = h.id)
            JOIN precalc_copy_filter_bib_list cf ON (rhrr.bib_record = cf.id AND m.target_copy = cf.copy)
      WHERE h.fulfillment_time IS NULL
            AND h.request_lib = ANY (badge.orgs)
      GROUP BY 1;
END;

Function: rating.generic_fixed_rating_by_copy(value integer)

Returns: SET OF record

Language: PLPGSQL

BEGIN
    PERFORM rating.precalc_bibs_by_copy(badge_id);
    RETURN QUERY
        SELECT id, 1.0 FROM precalc_filter_bib_list
            INTERSECT
        SELECT id, 1.0 FROM precalc_bibs_by_copy_list;
END;

Function: rating.generic_fixed_rating_by_copy_or_uri(value integer)

Returns: SET OF record

Language: PLPGSQL

BEGIN
    PERFORM rating.precalc_bibs_by_copy_or_uri(badge_id);
    RETURN QUERY
        (SELECT id, 1.0 FROM precalc_filter_bib_list
            INTERSECT
        SELECT id, 1.0 FROM precalc_bibs_by_copy_list)
            UNION
        (SELECT id, 1.0 FROM precalc_bib_filter_bib_list
            INTERSECT
        SELECT id, 1.0 FROM precalc_bibs_by_uri_list);
END;

Function: rating.generic_fixed_rating_by_uri(value integer)

Returns: SET OF record

Language: PLPGSQL

BEGIN
    PERFORM rating.precalc_bibs_by_uri(badge_id);
    RETURN QUERY
        SELECT id, 1.0 FROM precalc_bib_filter_bib_list
            INTERSECT
        SELECT id, 1.0 FROM precalc_bibs_by_uri_list;
END;

Function: rating.generic_fixed_rating_global(value integer)

Returns: SET OF record

Language: PLPGSQL

BEGIN
    RETURN QUERY
        SELECT id, 1.0 FROM precalc_bib_filter_bib_list;
END;

Function: rating.holds_filled_over_time(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
    iage    INT     := 1;
    iint    INT     := NULL;
    iscale  NUMERIC := NULL;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    IF badge.horizon_age IS NULL THEN
        RAISE EXCEPTION 'Badge "%" with id % requires a horizon age but has none.',
            badge.name,
            badge.id;
    END IF;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_bib_list;
    CREATE TEMP TABLE precalc_bib_list ON COMMIT DROP AS
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list;

    iint := EXTRACT(EPOCH FROM badge.importance_interval);
    IF badge.importance_age IS NOT NULL THEN
        iage := (EXTRACT(EPOCH FROM badge.importance_age) / iint)::INT;
    END IF;

    -- if iscale is smaller than 1, scaling slope will be shallow ... BEWARE!
    iscale := COALESCE(badge.importance_scale, 1.0);

    RETURN QUERY
     SELECT bib,
            SUM( holds * GREATEST( iscale * (iage - hage), 1.0 ))
      FROM (
         SELECT f.id AS bib,
                (1 + EXTRACT(EPOCH FROM AGE(h.fulfillment_time)) / iint)::INT AS hage,
                COUNT(h.id)::INT AS holds
          FROM  action.hold_request h
                JOIN reporter.hold_request_record rhrr ON (rhrr.id = h.id)
                JOIN precalc_bib_list f ON (f.id = rhrr.bib_record)
          WHERE h.fulfillment_time >= NOW() - badge.horizon_age
                AND h.request_lib = ANY (badge.orgs)
          GROUP BY 1, 2
      ) x
      GROUP BY 1;
END;

Function: rating.holds_holdable_ratio(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );

    ANALYZE precalc_copy_filter_bib_list;

    RETURN QUERY
     SELECT cn.record AS bib,
            COUNT(DISTINCT m.hold)::NUMERIC / COUNT(DISTINCT cp.id)::NUMERIC
      FROM  asset.copy cp
            JOIN precalc_copy_filter_bib_list c ON (cp.id = c.copy)
            JOIN asset.copy_location cl ON (cl.id = cp.location)
            JOIN config.copy_status cs ON (cs.id = cp.status)
            JOIN asset.call_number cn ON (cn.id = cp.call_number)
            JOIN action.hold_copy_map m ON (m.target_copy = cp.id)
      WHERE cn.owning_lib = ANY (badge.orgs)
            AND cp.holdable IS TRUE
            AND cl.holdable IS TRUE
            AND cs.holdable IS TRUE
      GROUP BY 1;
END;

Function: rating.holds_placed_over_time(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
    iage    INT     := 1;
    iint    INT     := NULL;
    iscale  NUMERIC := NULL;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    IF badge.horizon_age IS NULL THEN
        RAISE EXCEPTION 'Badge "%" with id % requires a horizon age but has none.',
            badge.name,
            badge.id;
    END IF;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_bib_list;
    CREATE TEMP TABLE precalc_bib_list ON COMMIT DROP AS
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list;

    iint := EXTRACT(EPOCH FROM badge.importance_interval);
    IF badge.importance_age IS NOT NULL THEN
        iage := (EXTRACT(EPOCH FROM badge.importance_age) / iint)::INT;
    END IF;

    -- if iscale is smaller than 1, scaling slope will be shallow ... BEWARE!
    iscale := COALESCE(badge.importance_scale, 1.0);

    RETURN QUERY
     SELECT bib,
            SUM( holds * GREATEST( iscale * (iage - hage), 1.0 ))
      FROM (
         SELECT f.id AS bib,
                (1 + EXTRACT(EPOCH FROM AGE(h.request_time)) / iint)::INT AS hage,
                COUNT(h.id)::INT AS holds
          FROM  action.hold_request h
                JOIN reporter.hold_request_record rhrr ON (rhrr.id = h.id)
                JOIN precalc_bib_list f ON (f.id = rhrr.bib_record)
          WHERE h.request_time >= NOW() - badge.horizon_age
                AND h.request_lib = ANY (badge.orgs)
          GROUP BY 1, 2
      ) x
      GROUP BY 1;
END;

Function: rating.holds_total_ratio(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );

    ANALYZE precalc_copy_filter_bib_list;

    RETURN QUERY
     SELECT cn.record AS bib,
            COUNT(DISTINCT m.hold)::NUMERIC / COUNT(DISTINCT cp.id)::NUMERIC
      FROM  asset.copy cp
            JOIN precalc_copy_filter_bib_list c ON (cp.id = c.copy)
            JOIN asset.call_number cn ON (cn.id = cp.call_number)
            JOIN action.hold_copy_map m ON (m.target_copy = cp.id)
      WHERE cn.owning_lib = ANY (badge.orgs)
      GROUP BY 1;
END;

Function: rating.inhouse_over_time(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
    iage    INT     := 1;
    iint    INT     := NULL;
    iscale  NUMERIC := NULL;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    IF badge.horizon_age IS NULL THEN
        RAISE EXCEPTION 'Badge "%" with id % requires a horizon age but has none.',
            badge.name,
            badge.id;
    END IF;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );

    ANALYZE precalc_copy_filter_bib_list;

    iint := EXTRACT(EPOCH FROM badge.importance_interval);
    IF badge.importance_age IS NOT NULL THEN
        iage := (EXTRACT(EPOCH FROM badge.importance_age) / iint)::INT;
    END IF;

    -- if iscale is smaller than 1, scaling slope will be shallow ... BEWARE!
    iscale := COALESCE(badge.importance_scale, 1.0);

    RETURN QUERY
     SELECT bib,
            SUM( uses * GREATEST( iscale * (iage - cage), 1.0 ))
      FROM (
         SELECT cn.record AS bib,
                (1 + EXTRACT(EPOCH FROM AGE(u.use_time)) / iint)::INT AS cage,
                COUNT(u.id)::INT AS uses
          FROM  action.in_house_use u
                JOIN precalc_copy_filter_bib_list cf ON (u.item = cf.copy)
                JOIN asset.copy cp ON (cp.id = u.item)
                JOIN asset.call_number cn ON (cn.id = cp.call_number)
          WHERE u.use_time >= NOW() - badge.horizon_age
                AND cn.owning_lib = ANY (badge.orgs)
          GROUP BY 1, 2
      ) x
      GROUP BY 1;
END;

Function: rating.org_unit_count(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );
    ANALYZE precalc_copy_filter_bib_list;

    -- Use circ rather than owning lib here as that means "on the shelf at..."
    RETURN QUERY
     SELECT f.id::INT AS bib,
            COUNT(DISTINCT cp.circ_lib)::NUMERIC
     FROM asset.copy cp
          JOIN precalc_copy_filter_bib_list f ON (cp.id = f.copy)
     WHERE cp.circ_lib = ANY (badge.orgs) GROUP BY 1;

END;

Function: rating.percent_time_circulating(value integer)

Returns: SET OF record

Language: PLPGSQL

DECLARE
    badge   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge FROM rating.badge_with_orgs WHERE id = badge_id;

    PERFORM rating.precalc_bibs_by_copy(badge_id);

    DELETE FROM precalc_copy_filter_bib_list WHERE id NOT IN (
        SELECT id FROM precalc_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_bibs_by_copy_list
    );

    ANALYZE precalc_copy_filter_bib_list;

    RETURN QUERY
     SELECT bib,
            SUM(COALESCE(circ_time,0))::NUMERIC / SUM(age)::NUMERIC
      FROM  (SELECT cn.record AS bib,
                    cp.id,
                    EXTRACT( EPOCH FROM AGE(cp.active_date) ) + 1 AS age,
                    SUM(  -- time copy spent circulating
                        EXTRACT(
                            EPOCH FROM
                            AGE(
                                COALESCE(circ.checkin_time, circ.stop_fines_time, NOW()),
                                circ.xact_start
                            )
                        )
                    )::NUMERIC AS circ_time
              FROM  asset.copy cp
                    JOIN precalc_copy_filter_bib_list c ON (cp.id = c.copy)
                    JOIN asset.call_number cn ON (cn.id = cp.call_number)
                    LEFT JOIN action.all_circulation_slim circ ON (
                        circ.target_copy = cp.id
                        AND stop_fines NOT IN (
                            'LOST',
                            'LONGOVERDUE',
                            'CLAIMSRETURNED',
                            'LONGOVERDUE'
                        )
                        AND NOT (
                            checkin_time IS NULL AND
                            stop_fines = 'MAXFINES'
                        )
                    )
              WHERE cn.owning_lib = ANY (badge.orgs)
                    AND cp.active_date IS NOT NULL
                    -- Next line requires that copies with no circs (circ.id IS NULL) also not be deleted
                    AND ((circ.id IS NULL AND NOT cp.deleted) OR circ.id IS NOT NULL)
              GROUP BY 1,2,3
            ) x
      GROUP BY 1;
END;

Function: rating.precalc_attr_filter(attr_filter text)

Returns: integer

Language: PLPGSQL

DECLARE
    cnt     INT := 0;
    afilter TEXT;
BEGIN

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_attr_filter_bib_list;
    IF attr_filter IS NOT NULL THEN
        afilter := metabib.compile_composite_attr(attr_filter);
        CREATE TEMP TABLE precalc_attr_filter_bib_list ON COMMIT DROP AS
            SELECT source AS id FROM metabib.record_attr_vector_list
            WHERE vlist @@ metabib.compile_composite_attr(attr_filter);
    ELSE
        CREATE TEMP TABLE precalc_attr_filter_bib_list ON COMMIT DROP AS
            SELECT source AS id FROM metabib.record_attr_vector_list;
    END IF;

    SELECT count(*) INTO cnt FROM precalc_attr_filter_bib_list;
    RETURN cnt;
END;

Function: rating.precalc_bibs_by_copy(badge_id integer)

Returns: integer

Language: PLPGSQL

DECLARE
    cnt         INT     := 0;
    badge_row   rating.badge_with_orgs%ROWTYPE;
    base        TEXT;
    whr         TEXT;
BEGIN

    SELECT * INTO badge_row FROM rating.badge_with_orgs WHERE id = badge_id;

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_bibs_by_copy_list;
    CREATE TEMP TABLE precalc_bibs_by_copy_list ON COMMIT DROP AS
        SELECT  DISTINCT cn.record AS id
          FROM  asset.call_number cn
                JOIN asset.copy cp ON (cp.call_number = cn.id AND NOT cp.deleted)
                JOIN precalc_copy_filter_bib_list f ON (cp.id = f.copy)
          WHERE cn.owning_lib = ANY (badge_row.orgs)
                AND NOT cn.deleted;

    SELECT count(*) INTO cnt FROM precalc_bibs_by_copy_list;
    RETURN cnt;
END;

Function: rating.precalc_bibs_by_copy_or_uri(badge_id integer)

Returns: integer

Language: PLPGSQL

DECLARE
    cnt         INT     := 0;
BEGIN

    PERFORM rating.precalc_bibs_by_copy(badge_id);
    PERFORM rating.precalc_bibs_by_uri(badge_id);

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_bibs_by_copy_or_uri_list;
    CREATE TEMP TABLE precalc_bibs_by_copy_or_uri_list ON COMMIT DROP AS
        SELECT id FROM precalc_bibs_by_copy_list
            UNION
        SELECT id FROM precalc_bibs_by_uri_list;

    SELECT count(*) INTO cnt FROM precalc_bibs_by_copy_or_uri_list;
    RETURN cnt;
END;

Function: rating.precalc_bibs_by_uri(badge_id integer)

Returns: integer

Language: PLPGSQL

DECLARE
    cnt         INT     := 0;
    badge_row   rating.badge_with_orgs%ROWTYPE;
BEGIN

    SELECT * INTO badge_row FROM rating.badge_with_orgs WHERE id = badge_id;

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_bibs_by_uri_list;
    CREATE TEMP TABLE precalc_bibs_by_uri_list ON COMMIT DROP AS
        SELECT  DISTINCT record AS id
          FROM  asset.call_number cn
                JOIN asset.uri_call_number_map urim ON (urim.call_number = cn.id)
                JOIN asset.uri uri ON (urim.uri = uri.id AND uri.active)
          WHERE cn.owning_lib = ANY (badge_row.orgs)
                AND cn.label = '##URI##'
                AND NOT cn.deleted;

    SELECT count(*) INTO cnt FROM precalc_bibs_by_uri_list;
    RETURN cnt;
END;

Function: rating.precalc_circ_mod_filter(cm text)

Returns: integer

Language: PLPGSQL

DECLARE
    cnt     INT     := 0;
BEGIN

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_circ_mod_filter_bib_list;
    IF cm IS NOT NULL THEN
        CREATE TEMP TABLE precalc_circ_mod_filter_bib_list ON COMMIT DROP AS
            SELECT  cn.record AS id,
                    cp.id AS copy
              FROM  asset.call_number cn
                    JOIN asset.copy cp ON (cn.id = cp.call_number)
              WHERE cp.circ_modifier = cm
                    AND NOT cp.deleted;
    ELSE
        CREATE TEMP TABLE precalc_circ_mod_filter_bib_list ON COMMIT DROP AS
            SELECT  cn.record AS id,
                    cp.id AS copy
              FROM  asset.call_number cn
                    JOIN asset.copy cp ON (cn.id = cp.call_number)
              WHERE NOT cp.deleted;
    END IF;

    SELECT count(*) INTO cnt FROM precalc_circ_mod_filter_bib_list;
    RETURN cnt;
END;

Function: rating.precalc_location_filter(loc integer)

Returns: integer

Language: PLPGSQL

DECLARE
    cnt     INT     := 0;
BEGIN

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_location_filter_bib_list;
    IF loc IS NOT NULL THEN
        CREATE TEMP TABLE precalc_location_filter_bib_list ON COMMIT DROP AS
            SELECT  cn.record AS id,
                    cp.id AS copy
              FROM  asset.call_number cn
                    JOIN asset.copy cp ON (cn.id = cp.call_number)
                    JOIN asset.copy_location_group_map lg ON (cp.location = lg.location)
              WHERE lg.lgroup = loc
                    AND NOT cp.deleted;
    ELSE
        CREATE TEMP TABLE precalc_location_filter_bib_list ON COMMIT DROP AS
            SELECT  cn.record AS id,
                    cp.id AS copy
              FROM  asset.call_number cn
                    JOIN asset.copy cp ON (cn.id = cp.call_number)
              WHERE NOT cp.deleted;
    END IF;

    SELECT count(*) INTO cnt FROM precalc_location_filter_bib_list;
    RETURN cnt;
END;

Function: rating.precalc_src_filter(src integer)

Returns: integer

Language: PLPGSQL

DECLARE
    cnt     INT     := 0;
BEGIN

    SET LOCAL client_min_messages = error;
    DROP TABLE IF EXISTS precalc_src_filter_bib_list;
    IF src IS NOT NULL THEN
        CREATE TEMP TABLE precalc_src_filter_bib_list ON COMMIT DROP AS
            SELECT id FROM biblio.record_entry
            WHERE source = src AND NOT deleted;
    ELSE
        CREATE TEMP TABLE precalc_src_filter_bib_list ON COMMIT DROP AS
            SELECT id FROM biblio.record_entry
            WHERE id > 0 AND NOT deleted;
    END IF;

    SELECT count(*) INTO cnt FROM precalc_src_filter_bib_list;
    RETURN cnt;
END;

Function: rating.recalculate_badge_score(setup_only integer, badge_id boolean)

Returns: void

Language: PLPGSQL

DECLARE
    badge_row           rating.badge%ROWTYPE;
    param           rating.popularity_parameter%ROWTYPE;
BEGIN
    SET LOCAL client_min_messages = error;

    -- Find what we're doing    
    SELECT * INTO badge_row FROM rating.badge WHERE id = badge_id;
    SELECT * INTO param FROM rating.popularity_parameter WHERE id = badge_row.popularity_parameter;

    -- Calculate the filtered bib set, or all bibs if none
    PERFORM rating.precalc_attr_filter(badge_row.attr_filter);
    PERFORM rating.precalc_src_filter(badge_row.src_filter);
    PERFORM rating.precalc_circ_mod_filter(badge_row.circ_mod_filter);
    PERFORM rating.precalc_location_filter(badge_row.loc_grp_filter);

    -- Bring the bib-level filter lists together
    DROP TABLE IF EXISTS precalc_bib_filter_bib_list;
    CREATE TEMP TABLE precalc_bib_filter_bib_list ON COMMIT DROP AS
        SELECT id FROM precalc_attr_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_src_filter_bib_list;

    -- Bring the copy-level filter lists together. We're keeping this for bib_by_copy filtering later.
    DROP TABLE IF EXISTS precalc_copy_filter_bib_list;
    CREATE TEMP TABLE precalc_copy_filter_bib_list ON COMMIT DROP AS
        SELECT id, copy FROM precalc_circ_mod_filter_bib_list
            INTERSECT
        SELECT id, copy FROM precalc_location_filter_bib_list;

    -- Bring the collapsed filter lists together
    DROP TABLE IF EXISTS precalc_filter_bib_list;
    CREATE TEMP TABLE precalc_filter_bib_list ON COMMIT DROP AS
        SELECT id FROM precalc_bib_filter_bib_list
            INTERSECT
        SELECT id FROM precalc_copy_filter_bib_list;

    CREATE INDEX precalc_filter_bib_list_idx
        ON precalc_filter_bib_list (id);

    IF setup_only THEN
        RETURN;
    END IF;

    -- If it's a fixed-rating badge, just do it ...
    IF badge_row.fixed_rating IS NOT NULL THEN
        DELETE FROM rating.record_badge_score WHERE badge = badge_id;
        EXECUTE $e$
            INSERT INTO rating.record_badge_score (record, badge, score)
                SELECT record, $1, $2 FROM $e$ || param.func || $e$($1)$e$
        USING badge_id, badge_row.fixed_rating;

        UPDATE rating.badge SET last_calc = NOW() WHERE id = badge_id;

        RETURN;
    END IF;
    -- else, calculate!

    -- Make a session-local scratchpad for calculating scores
    CREATE TEMP TABLE record_score_scratchpad (
        bib     BIGINT,
        value   NUMERIC
    ) ON COMMIT DROP;

    -- Gather raw values
    EXECUTE $e$
        INSERT INTO record_score_scratchpad (bib, value)
            SELECT * FROM $e$ || param.func || $e$($1)$e$
    USING badge_id;

    IF badge_row.discard > 0 OR badge_row.percentile IS NOT NULL THEN
        -- To speed up discard-common
        CREATE INDEX record_score_scratchpad_score_idx ON record_score_scratchpad (value);
        ANALYZE record_score_scratchpad;
    END IF;

    IF badge_row.discard > 0 THEN -- Remove common low values (trim the long tail)
        DELETE FROM record_score_scratchpad WHERE value IN (
            SELECT DISTINCT value FROM record_score_scratchpad ORDER BY value LIMIT badge_row.discard
        );
    END IF;

    IF badge_row.percentile IS NOT NULL THEN -- Cut population down to exceptional records
        DELETE FROM record_score_scratchpad WHERE value <= (
            SELECT value FROM (
                SELECT  value,
                        CUME_DIST() OVER (ORDER BY value) AS p
                  FROM  record_score_scratchpad
            ) x WHERE p < badge_row.percentile / 100.0 ORDER BY p DESC LIMIT 1
        );
    END IF;


    -- And, finally, push new data in
    DELETE FROM rating.record_badge_score WHERE badge = badge_id;
    INSERT INTO rating.record_badge_score (badge, record, score)
        SELECT  badge_id,
                bib,
                GREATEST(ROUND((CUME_DIST() OVER (ORDER BY value)) * 5), 1) AS value
          FROM  record_score_scratchpad;

    DROP TABLE record_score_scratchpad;

    -- Now, finally-finally, mark the badge as recalculated
    UPDATE rating.badge SET last_calc = NOW() WHERE id = badge_id;

    RETURN;
END;

Schema reporter


View: reporter.asset_call_number_dewey

reporter.asset_call_number_dewey Structure
F-Key Name Type Description
call_number bigint
dewey text
dewey_block_tens text
dewey_block_hundreds text
dewey_range_tens text
dewey_range_hundreds text
SELECT call_number.id AS call_number
,
    call_number_dewey
(call_number.label) AS dewey
,
        CASE
            WHEN 
(call_number_dewey
     (call_number.label) ~ '^[0-9]+\.?[0-9]*$'::text
) THEN btrim
(to_char
     (
           (
                 (10)::double precision * floor
                 (
                       (
                             (call_number_dewey
                                   (call_number.label)
                             )::double precision / 
                             (10)::double precision
                       )
                 )
           )
           ,'000'::text
     )
)
            ELSE NULL::text
        END AS dewey_block_tens
,
        CASE
            WHEN 
(call_number_dewey
     (call_number.label) ~ '^[0-9]+\.?[0-9]*$'::text
) THEN btrim
(to_char
     (
           (
                 (100)::double precision * floor
                 (
                       (
                             (call_number_dewey
                                   (call_number.label)
                             )::double precision / 
                             (100)::double precision
                       )
                 )
           )
           ,'000'::text
     )
)
            ELSE NULL::text
        END AS dewey_block_hundreds
,
        CASE
            WHEN 
(call_number_dewey
     (call_number.label) ~ '^[0-9]+\.?[0-9]*$'::text
) THEN 
(
     (btrim
           (to_char
                 (
                       (
                             (10)::double precision * floor
                             (
                                   (
                                         (call_number_dewey
                                               (call_number.label)
                                         )::double precision / 
                                         (10)::double precision
                                   )
                             )
                       )
                       ,'000'::text
                 )
           ) || '-'::text
     ) || btrim
     (to_char
           (
                 (
                       (
                             (10)::double precision * floor
                             (
                                   (
                                         (call_number_dewey
                                               (call_number.label)
                                         )::double precision / 
                                         (10)::double precision
                                   )
                             )
                       ) + 
                       (9)::double precision
                 )
                 ,'000'::text
           )
     )
)
            ELSE NULL::text
        END AS dewey_range_tens
,
        CASE
            WHEN 
(call_number_dewey
     (call_number.label) ~ '^[0-9]+\.?[0-9]*$'::text
) THEN 
(
     (btrim
           (to_char
                 (
                       (
                             (100)::double precision * floor
                             (
                                   (
                                         (call_number_dewey
                                               (call_number.label)
                                         )::double precision / 
                                         (100)::double precision
                                   )
                             )
                       )
                       ,'000'::text
                 )
           ) || '-'::text
     ) || btrim
     (to_char
           (
                 (
                       (
                             (100)::double precision * floor
                             (
                                   (
                                         (call_number_dewey
                                               (call_number.label)
                                         )::double precision / 
                                         (100)::double precision
                                   )
                             )
                       ) + 
                       (99)::double precision
                 )
                 ,'000'::text
           )
     )
)
            ELSE NULL::text
        END AS dewey_range_hundreds
   
FROM asset.call_number
  
WHERE (call_number_dewey
     (call_number.label) ~ '^[0-9]'::text
);

Index - Schema reporter


View: reporter.circ_type

reporter.circ_type Structure
F-Key Name Type Description
id bigint
type text
SELECT circulation.id
,
        CASE
            WHEN 
(circulation.opac_renewal 
    OR circulation.phone_renewal 
    OR circulation.desk_renewal 
    OR circulation.auto_renewal
) THEN 'RENEWAL'::text
            ELSE 'CHECKOUT'::text
        END AS type
   
FROM action.circulation;

Index - Schema reporter


View: reporter.completed_reports

reporter.completed_reports Structure
F-Key Name Type Description
run integer
report integer
template integer
template_owner integer
report_owner integer
runner integer
template_folder integer
report_folder integer
output_folder integer
report_name text
template_name text
start_time timestamp with time zone
run_time timestamp with time zone
complete_time timestamp with time zone
error_code integer
error_text text
SELECT s.id AS run
,
    r.id AS report
,
    t.id AS template
,
    t.owner AS template_owner
,
    r.owner AS report_owner
,
    s.runner
,
    t.folder AS template_folder
,
    r.folder AS report_folder
,
    s.folder AS output_folder
,
    r.name AS report_name
,
    t.name AS template_name
,
    s.start_time
,
    s.run_time
,
    s.complete_time
,
    s.error_code
,
    s.error_text
   
FROM (
     (reporter.schedule s
     
        JOIN reporter.report r 
          ON (
                 (r.id = s.report)
           )
     )
     
  JOIN reporter.template t 
    ON (
           (t.id = r.template)
     )
)
  
WHERE (s.complete_time IS NOT NULL);

Index - Schema reporter


View: reporter.currently_running

reporter.currently_running Structure
F-Key Name Type Description
id integer
runner_barcode text
name text
run_time timestamp with time zone
scheduled_wait_time interval
SELECT s.id
,
    c.barcode AS runner_barcode
,
    r.name
,
    s.run_time
,
    
(s.run_time - now
     ()
) AS scheduled_wait_time
   
FROM (
     (
           (reporter.schedule s
     
              JOIN reporter.report r 
                ON (
                       (r.id = s.report)
                 )
           )
     
        JOIN actor.usr u 
          ON (
                 (s.runner = u.id)
           )
     )
     
  JOIN actor.card c 
    ON (
           (c.id = u.card)
     )
)
  
WHERE (
     (s.start_time IS NOT NULL)
   AND (s.complete_time IS NULL)
);

Index - Schema reporter


View: reporter.demographic

reporter.demographic Structure
F-Key Name Type Description
id integer
dob date
general_division text
age_division text
SELECT u.id
,
    u.dob
,
        CASE
            WHEN 
(u.dob IS NULL) THEN 'Adult'::text
            WHEN 
(age
     (
           (u.dob)::timestamp with time zone
     ) > '18 years'::interval
) THEN 'Adult'::text
            ELSE 'Juvenile'::text
        END AS general_division
,
        CASE
            WHEN 
(u.dob IS NULL) THEN 'No Date of Birth Entered'::text
            WHEN 
(
     (age
           (
                 (u.dob)::timestamp with time zone
           ) >= '00:00:00'::interval
     )
   AND (age
           (
                 (u.dob)::timestamp with time zone
           ) < '6 years'::interval
     )
) THEN 'Child 0-5 Years Old'::text
            WHEN 
(
     (age
           (
                 (u.dob)::timestamp with time zone
           ) >= '6 years'::interval
     )
   AND (age
           (
                 (u.dob)::timestamp with time zone
           ) < '13 years'::interval
     )
) THEN 'Child 6-12 Years Old'::text
            WHEN 
(
     (age
           (
                 (u.dob)::timestamp with time zone
           ) >= '13 years'::interval
     )
   AND (age
           (
                 (u.dob)::timestamp with time zone
           ) < '18 years'::interval
     )
) THEN 'Teen 13-17 Years Old'::text
            WHEN 
(
     (age
           (
                 (u.dob)::timestamp with time zone
           ) >= '18 years'::interval
     )
   AND (age
           (
                 (u.dob)::timestamp with time zone
           ) < '26 years'::interval
     )
) THEN 'Adult 18-25 Years Old'::text
            WHEN 
(
     (age
           (
                 (u.dob)::timestamp with time zone
           ) >= '26 years'::interval
     )
   AND (age
           (
                 (u.dob)::timestamp with time zone
           ) < '50 years'::interval
     )
) THEN 'Adult 26-49 Years Old'::text
            WHEN 
(
     (age
           (
                 (u.dob)::timestamp with time zone
           ) >= '50 years'::interval
     )
   AND (age
           (
                 (u.dob)::timestamp with time zone
           ) < '60 years'::interval
     )
) THEN 'Adult 50-59 Years Old'::text
            WHEN 
(
     (age
           (
                 (u.dob)::timestamp with time zone
           ) >= '60 years'::interval
     )
   AND (age
           (
                 (u.dob)::timestamp with time zone
           ) < '70 years'::interval
     )
) THEN 'Adult 60-69 Years Old'::text
            WHEN 
(age
     (
           (u.dob)::timestamp with time zone
     ) >= '70 years'::interval
) THEN 'Adult 70+'::text
            ELSE NULL::text
        END AS age_division
   
FROM actor.usr u;

Index - Schema reporter


Table: reporter.hold_request_record

reporter.hold_request_record Structure
F-Key Name Type Description
id integer PRIMARY KEY
target bigint
hold_type text
bib_record bigint
reporter_hold_request_record_bib_record_idx bib_record

Index - Schema reporter


Table: reporter.materialized_simple_record

reporter.materialized_simple_record Structure
F-Key Name Type Description
id bigint PRIMARY KEY
fingerprint text
quality integer
tcn_source text
tcn_value text
title text
author text
publisher text
pubdate text
isbn text[]
issn text[]

Index - Schema reporter


View: reporter.old_super_simple_record

reporter.old_super_simple_record Structure
F-Key Name Type Description
id bigint
fingerprint text
quality integer
tcn_source text
tcn_value text
title text
author text
publisher text
pubdate text
isbn text[]
issn text[]
SELECT r.id
,
    r.fingerprint
,
    r.quality
,
    r.tcn_source
,
    r.tcn_value
,
    oils_json_to_text
(d.title) AS title
,
    oils_json_to_text
(d.author) AS author
,
    oils_json_to_text
(d.publisher) AS publisher
,
    oils_json_to_text
(d.pubdate) AS pubdate
,
        CASE
            WHEN 
(d.isbn = 'null'::text) THEN NULL::text[]
            ELSE 
(
SELECT ARRAY
     (
      SELECT json_array_elements_text
           (
                 (d.isbn)::json
           ) AS json_array_elements_text
     ) AS "array"
)
        END AS isbn
,
        CASE
            WHEN 
(d.issn = 'null'::text) THEN NULL::text[]
            ELSE 
(
SELECT ARRAY
     (
      SELECT json_array_elements_text
           (
                 (d.issn)::json
           ) AS json_array_elements_text
     ) AS "array"
)
        END AS issn
   
FROM (biblio.record_entry r
     
  JOIN metabib.wide_display_entry d 
    ON (
           (r.id = d.source)
     )
);

Index - Schema reporter


Table: reporter.output_folder

reporter.output_folder Structure
F-Key Name Type Description
id serial PRIMARY KEY
reporter.output_folder.id parent integer
actor.usr.id owner integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
name text NOT NULL
shared boolean NOT NULL DEFAULT false
simple_reporter boolean NOT NULL DEFAULT false
actor.org_unit.id share_with integer

Tables referencing this one via Foreign Key Constraints:

rpt_output_fldr_owner_idx owner

Index - Schema reporter


View: reporter.overdue_circs

reporter.overdue_circs Structure
F-Key Name Type Description
id bigint
usr integer
xact_start timestamp with time zone
xact_finish timestamp with time zone
unrecovered boolean
target_copy bigint
circ_lib integer
circ_staff integer
checkin_staff integer
checkin_lib integer
renewal_remaining integer
grace_period interval
due_date timestamp with time zone
stop_fines_time timestamp with time zone
checkin_time timestamp with time zone
create_time timestamp with time zone
duration interval
fine_interval interval
recurring_fine numeric(6,2)
max_fine numeric(6,2)
phone_renewal boolean
desk_renewal boolean
opac_renewal boolean
duration_rule text
recurring_fine_rule text
max_fine_rule text
stop_fines text
workstation integer
checkin_workstation integer
copy_location integer
checkin_scan_time timestamp with time zone
auto_renewal boolean
auto_renewal_remaining integer
parent_circ bigint
SELECT circulation.id
,
    circulation.usr
,
    circulation.xact_start
,
    circulation.xact_finish
,
    circulation.unrecovered
,
    circulation.target_copy
,
    circulation.circ_lib
,
    circulation.circ_staff
,
    circulation.checkin_staff
,
    circulation.checkin_lib
,
    circulation.renewal_remaining
,
    circulation.grace_period
,
    circulation.due_date
,
    circulation.stop_fines_time
,
    circulation.checkin_time
,
    circulation.create_time
,
    circulation.duration
,
    circulation.fine_interval
,
    circulation.recurring_fine
,
    circulation.max_fine
,
    circulation.phone_renewal
,
    circulation.desk_renewal
,
    circulation.opac_renewal
,
    circulation.duration_rule
,
    circulation.recurring_fine_rule
,
    circulation.max_fine_rule
,
    circulation.stop_fines
,
    circulation.workstation
,
    circulation.checkin_workstation
,
    circulation.copy_location
,
    circulation.checkin_scan_time
,
    circulation.auto_renewal
,
    circulation.auto_renewal_remaining
,
    circulation.parent_circ
   
FROM action.circulation
  
WHERE (
     (circulation.checkin_time IS NULL)
   AND (
           (circulation.stop_fines <> ALL 
                 (ARRAY['LOST'::text
                       ,'CLAIMSRETURNED'::text]
                 )
           )
          OR (circulation.stop_fines IS NULL)
     )
   AND (circulation.due_date < now
           ()
     )
);

Index - Schema reporter


View: reporter.overdue_reports

reporter.overdue_reports Structure
F-Key Name Type Description
id integer
runner_barcode text
name text
run_time timestamp with time zone
scheduled_wait_time interval
SELECT s.id
,
    c.barcode AS runner_barcode
,
    r.name
,
    s.run_time
,
    
(s.run_time - now
     ()
) AS scheduled_wait_time
   
FROM (
     (
           (reporter.schedule s
     
              JOIN reporter.report r 
                ON (
                       (r.id = s.report)
                 )
           )
     
        JOIN actor.usr u 
          ON (
                 (s.runner = u.id)
           )
     )
     
  JOIN actor.card c 
    ON (
           (c.id = u.card)
     )
)
  
WHERE (
     (s.start_time IS NULL)
   AND (s.run_time < now
           ()
     )
);

Index - Schema reporter


View: reporter.pending_reports

reporter.pending_reports Structure
F-Key Name Type Description
id integer
runner_barcode text
name text
run_time timestamp with time zone
scheduled_wait_time interval
SELECT s.id
,
    c.barcode AS runner_barcode
,
    r.name
,
    s.run_time
,
    
(s.run_time - now
     ()
) AS scheduled_wait_time
   
FROM (
     (
           (reporter.schedule s
     
              JOIN reporter.report r 
                ON (
                       (r.id = s.report)
                 )
           )
     
        JOIN actor.usr u 
          ON (
                 (s.runner = u.id)
           )
     )
     
  JOIN actor.card c 
    ON (
           (c.id = u.card)
     )
)
  
WHERE (s.start_time IS NULL);

Index - Schema reporter


Table: reporter.report

reporter.report Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id owner integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
name text NOT NULL DEFAULT ''::text
description text NOT NULL DEFAULT ''::text
reporter.template.id template integer NOT NULL
data text NOT NULL
reporter.report_folder.id folder integer NOT NULL
recur boolean NOT NULL DEFAULT false
recurrence interval

Tables referencing this one via Foreign Key Constraints:

rpt_rpt_fldr_idx folder rpt_rpt_owner_idx owner

Index - Schema reporter


Table: reporter.report_folder

reporter.report_folder Structure
F-Key Name Type Description
id serial PRIMARY KEY
reporter.report_folder.id parent integer
actor.usr.id owner integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
name text NOT NULL
shared boolean NOT NULL DEFAULT false
simple_reporter boolean NOT NULL DEFAULT false
actor.org_unit.id share_with integer

Tables referencing this one via Foreign Key Constraints:

rpt_rpt_fldr_owner_idx owner

Index - Schema reporter


Table: reporter.schedule

reporter.schedule Structure
F-Key Name Type Description
id serial PRIMARY KEY
reporter.report.id report integer NOT NULL
reporter.output_folder.id folder integer NOT NULL
actor.usr.id runner integer NOT NULL
run_time timestamp with time zone NOT NULL DEFAULT now()
start_time timestamp with time zone
complete_time timestamp with time zone
email text
excel_format boolean NOT NULL DEFAULT true
html_format boolean NOT NULL DEFAULT true
csv_format boolean NOT NULL DEFAULT true
chart_pie boolean NOT NULL DEFAULT false
chart_bar boolean NOT NULL DEFAULT false
chart_line boolean NOT NULL DEFAULT false
error_code integer
error_text text
rpt_sched_folder_idx folder rpt_sched_runner_idx runner

Index - Schema reporter


View: reporter.simple_record

reporter.simple_record Structure
F-Key Name Type Description
id bigint
metarecord bigint
fingerprint text
quality integer
tcn_source text
tcn_value text
title text
uniform_title text
author text
publisher text
pubdate text
series_title text
series_statement text
summary text
isbn text[]
issn text[]
topic_subject text[]
geographic_subject text[]
genre text[]
name_subject text[]
corporate_subject text[]
external_uri text[]
SELECT r.id
,
    s.metarecord
,
    r.fingerprint
,
    r.quality
,
    r.tcn_source
,
    r.tcn_value
,
    title.value AS title
,
    uniform_title.value AS uniform_title
,
    author.value AS author
,
    publisher.value AS publisher
,
    "substring"
(pubdate.value
     ,'\d+'::text
) AS pubdate
,
    series_title.value AS series_title
,
    series_statement.value AS series_statement
,
    summary.value AS summary
,
    array_agg
(DISTINCT replace
     ("substring"
           (isbn.value
                 ,'^\S+'::text
           )
           ,'-'::text
           ,''::text
     )
) AS isbn
,
    array_agg
(DISTINCT regexp_replace
     (issn.value
           ,'^\S*(\d{4})[-\s](\d{3,4}x?)'::text
     ,'\1 \2'::text
)
)      AS issn
,     
    ARRAY
(     
SELECT DISTINCT full_rec.value
           
FROM metabib.full_rec
          
WHERE (
     (full_rec.tag = '650'::bpchar)
   AND (full_rec.subfield = 'a'::text)
   AND (full_rec.record = r.id)
)
)      AS topic_subject
,     
    ARRAY
(     
SELECT DISTINCT full_rec.value
           
FROM metabib.full_rec
          
WHERE (
     (full_rec.tag = '651'::bpchar)
   AND (full_rec.subfield = 'a'::text)
   AND (full_rec.record = r.id)
)
)      AS geographic_subject
,     
    ARRAY
(     
SELECT DISTINCT full_rec.value
           
FROM metabib.full_rec
          
WHERE (
     (full_rec.tag = '655'::bpchar)
   AND (full_rec.subfield = 'a'::text)
   AND (full_rec.record = r.id)
)
)      AS genre
,     
    ARRAY
(     
SELECT DISTINCT full_rec.value
           
FROM metabib.full_rec
          
WHERE (
     (full_rec.tag = '600'::bpchar)
   AND (full_rec.subfield = 'a'::text)
   AND (full_rec.record = r.id)
)
)      AS name_subject
,     
    ARRAY
(     
SELECT DISTINCT full_rec.value
           
FROM metabib.full_rec
          
WHERE (
     (full_rec.tag = '610'::bpchar)
   AND (full_rec.subfield = 'a'::text)
   AND (full_rec.record = r.id)
)
)      AS corporate_subject
,     
    ARRAY
(     
SELECT full_rec.value
           
FROM metabib.full_rec
          
WHERE (
     (full_rec.tag = '856'::bpchar)
   AND (full_rec.subfield = ANY 
           (ARRAY['3'::text
                 ,'y'::text
                 ,'u'::text]
           )
     )
   AND (full_rec.record = r.id)
)
          
ORDER BY
                CASE
                    WHEN 
(full_rec.subfield = ANY 
     (ARRAY['3'::text
           ,'y'::text]
     )
) THEN 0
                    ELSE 1
                END
)      AS external_uri
   
FROM   (
(
     (
           (
                 (
                       (
                             (
                                   (
                                         (
                                               (
                                                     (biblio.record_entry r
     
                                                        JOIN metabib.metarecord_source_map s 
                                                          ON (
                                                                 (s.source = r.id)
                                                           )
                                                     )
     
                                             LEFT JOIN metabib.full_rec uniform_title 
                                                    ON (
                                                           (
                                                                 (r.id = uniform_title.record)
                                                               AND (uniform_title.tag = '240'::bpchar)
                                                               AND (uniform_title.subfield = 'a'::text)
                                                           )
                                                     )
                                               )
     
                                       LEFT JOIN metabib.full_rec title 
                                              ON (
                                                     (
                                                           (r.id = title.record)
                                                         AND (title.tag = '245'::bpchar)
                                                         AND (title.subfield = 'a'::text)
                                                     )
                                               )
                                         )
     
                                 LEFT JOIN metabib.full_rec author 
                                        ON (
                                               (
                                                     (r.id = author.record)
                                                   AND (author.tag = '100'::bpchar)
                                                   AND (author.subfield = 'a'::text)
                                               )
                                         )
                                   )
     
                           LEFT JOIN metabib.full_rec publisher 
                                  ON (
                                         (
                                               (r.id = publisher.record)
                                             AND (
                                                     (publisher.tag = '260'::bpchar)
                                                    OR (
                                                           (publisher.tag = '264'::bpchar)
                                                         AND (publisher.ind2 = '1'::text)
                                                     )
                                               )
                                             AND (publisher.subfield = 'b'::text)
                                         )
                                   )
                             )
     
                     LEFT JOIN metabib.full_rec pubdate 
                            ON (
                                   (
                                         (r.id = pubdate.record)
                                       AND (
                                               (pubdate.tag = '260'::bpchar)
                                              OR (
                                                     (pubdate.tag = '264'::bpchar)
                                                   AND (pubdate.ind2 = '1'::text)
                                               )
                                         )
                                       AND (pubdate.subfield = 'c'::text)
                                   )
                             )
                       )
     
               LEFT JOIN metabib.full_rec isbn 
                      ON (
                             (
                                   (r.id = isbn.record)
                                 AND (isbn.tag = ANY 
                                         (ARRAY['024'::bpchar
                                               ,'020'::bpchar]
                                         )
                                   )
                                 AND (isbn.subfield = ANY 
                                         (ARRAY['a'::text
                                               ,'z'::text]
                                         )
                                   )
                             )
                       )
                 )
     
         LEFT JOIN metabib.full_rec issn 
                ON (
                       (
                             (r.id = issn.record)
                           AND (issn.tag = '022'::bpchar)
                           AND (issn.subfield = 'a'::text)
                       )
                 )
           )
     
   LEFT JOIN metabib.full_rec series_title 
          ON (
                 (
                       (r.id = series_title.record)
                     AND (series_title.tag = ANY 
                             (ARRAY['830'::bpchar
                                   ,'440'::bpchar]
                             )
                       )
                     AND (series_title.subfield = 'a'::text)
                 )
           )
     )
     
LEFT JOIN metabib.full_rec series_statement 
    ON (
           (
                 (r.id = series_statement.record)
               AND (series_statement.tag = '490'::bpchar)
               AND (series_statement.subfield = 'a'::text)
           )
     )
)
     
LEFT JOIN metabib.full_rec summary 
ON (
     (
           (r.id = summary.record)
         AND (summary.tag = '520'::bpchar)
         AND (summary.subfield = 'a'::text)
     )
)
)     
  
GROUP BY r.id
,      s.metarecord
,      r.fingerprint
,      r.quality
,      r.tcn_source
,      r.tcn_value
,      title.value
,      uniform_title.value
,      author.value
,      publisher.value
,      ("substring"
(pubdate.value
     ,'\d+'::text
)
)     
,      series_title.value
,      series_statement.value
,      summary.value;

Index - Schema reporter


View: reporter.super_simple_record

reporter.super_simple_record Structure
F-Key Name Type Description
id bigint
fingerprint text
quality integer
tcn_source text
tcn_value text
title text
author text
publisher text
pubdate text
isbn text[]
issn text[]
SELECT materialized_simple_record.id
,
    materialized_simple_record.fingerprint
,
    materialized_simple_record.quality
,
    materialized_simple_record.tcn_source
,
    materialized_simple_record.tcn_value
,
    materialized_simple_record.title
,
    materialized_simple_record.author
,
    materialized_simple_record.publisher
,
    materialized_simple_record.pubdate
,
    materialized_simple_record.isbn
,
    materialized_simple_record.issn
   
FROM reporter.materialized_simple_record;

Index - Schema reporter


Table: reporter.template

reporter.template Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id owner integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
name text NOT NULL
description text NOT NULL DEFAULT ''::text
data text NOT NULL
reporter.template_folder.id folder integer NOT NULL

Tables referencing this one via Foreign Key Constraints:

rpt_tmpl_fldr_idx folder rpt_tmpl_owner_idx owner

Index - Schema reporter


Table: reporter.template_folder

reporter.template_folder Structure
F-Key Name Type Description
id serial PRIMARY KEY
reporter.template_folder.id parent integer
actor.usr.id owner integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
name text NOT NULL
shared boolean NOT NULL DEFAULT false
simple_reporter boolean NOT NULL DEFAULT false
actor.org_unit.id share_with integer

Tables referencing this one via Foreign Key Constraints:

rpt_tmpl_fldr_owner_idx owner

Index - Schema reporter


View: reporter.xact_billing_totals

reporter.xact_billing_totals Structure
F-Key Name Type Description
xact bigint
unvoided numeric
voided numeric
total numeric
SELECT b.xact
,
    sum
(
        CASE
            WHEN b.voided THEN 
     (0)::numeric
            ELSE b.amount
        END
) AS unvoided
,
    sum
(
        CASE
            WHEN b.voided THEN b.amount
            ELSE 
     (0)::numeric
        END
) AS voided
,
    sum
(b.amount) AS total
   
FROM money.billing b
  
GROUP BY b.xact;

Index - Schema reporter


View: reporter.xact_paid_totals

reporter.xact_paid_totals Structure
F-Key Name Type Description
xact bigint
unvoided numeric
voided numeric
total numeric
SELECT b.xact
,
    sum
(
        CASE
            WHEN b.voided THEN 
     (0)::numeric
            ELSE b.amount
        END
) AS unvoided
,
    sum
(
        CASE
            WHEN b.voided THEN b.amount
            ELSE 
     (0)::numeric
        END
) AS voided
,
    sum
(b.amount) AS total
   
FROM money.payment b
  
GROUP BY b.xact;

Index - Schema reporter


Function: reporter.disable_materialized_simple_record_trigger()

Returns: void

Language: SQL

    DROP TRIGGER IF EXISTS bbb_simple_rec_trigger ON biblio.record_entry;

Function: reporter.enable_materialized_simple_record_trigger()

Returns: void

Language: SQL


    TRUNCATE TABLE reporter.materialized_simple_record;

    INSERT INTO reporter.materialized_simple_record
        (id,fingerprint,quality,tcn_source,tcn_value,title,author,publisher,pubdate,isbn,issn)
        SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record;

    CREATE TRIGGER bbb_simple_rec_trigger
        AFTER INSERT OR UPDATE OR DELETE ON biblio.record_entry
        FOR EACH ROW EXECUTE PROCEDURE reporter.simple_rec_trigger();


Function: reporter.hold_request_record_mapper()

Returns: trigger

Language: PLPGSQL

BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO reporter.hold_request_record (id, target, hold_type, bib_record)
        SELECT  NEW.id,
                NEW.target,
                NEW.hold_type,
                CASE
                    WHEN NEW.hold_type = 'T'
                        THEN NEW.target
                    WHEN NEW.hold_type = 'I'
                        THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
                    WHEN NEW.hold_type = 'V'
                        THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
                    WHEN NEW.hold_type IN ('C','R','F')
                        THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
                    WHEN NEW.hold_type = 'M'
                        THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
                    WHEN NEW.hold_type = 'P'
                        THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
                END AS bib_record;
    ELSIF TG_OP = 'UPDATE' AND (OLD.target <> NEW.target OR OLD.hold_type <> NEW.hold_type) THEN
        UPDATE  reporter.hold_request_record
          SET   target = NEW.target,
                hold_type = NEW.hold_type,
                bib_record = CASE
                    WHEN NEW.hold_type = 'T'
                        THEN NEW.target
                    WHEN NEW.hold_type = 'I'
                        THEN (SELECT ssub.record_entry FROM serial.subscription ssub JOIN serial.issuance si ON (si.subscription = ssub.id) WHERE si.id = NEW.target)
                    WHEN NEW.hold_type = 'V'
                        THEN (SELECT cn.record FROM asset.call_number cn WHERE cn.id = NEW.target)
                    WHEN NEW.hold_type IN ('C','R','F')
                        THEN (SELECT cn.record FROM asset.call_number cn JOIN asset.copy cp ON (cn.id = cp.call_number) WHERE cp.id = NEW.target)
                    WHEN NEW.hold_type = 'M'
                        THEN (SELECT mr.master_record FROM metabib.metarecord mr WHERE mr.id = NEW.target)
                    WHEN NEW.hold_type = 'P'
                        THEN (SELECT bmp.record FROM biblio.monograph_part bmp WHERE bmp.id = NEW.target)
                END
         WHERE  id = NEW.id;
    END IF;
    RETURN NEW;
END;

Function: reporter.intersect_user_perm_ou(perm_code bigint, staff_id bigint, context_ou text)

Returns: boolean

Language: SQL

  SELECT CASE WHEN context_ou IN (SELECT * FROM permission.usr_has_perm_at_all(staff_id::INT, perm_code)) THEN TRUE ELSE FALSE END;

Function: reporter.refresh_materialized_simple_record()

Returns: void

Language: SQL

    SELECT reporter.disable_materialized_simple_record_trigger();
    SELECT reporter.enable_materialized_simple_record_trigger();

Function: reporter.simple_rec_delete(r_id bigint)

Returns: boolean

Language: SQL

    SELECT reporter.simple_rec_update($1, TRUE);

Function: reporter.simple_rec_trigger()

Returns: trigger

Language: PLPGSQL

BEGIN
    IF TG_OP = 'DELETE' THEN
        PERFORM reporter.simple_rec_delete(NEW.id);
    ELSE
        PERFORM reporter.simple_rec_update(NEW.id);
    END IF;

    RETURN NEW;
END;

Function: reporter.simple_rec_update(deleted bigint, r_id boolean)

Returns: boolean

Language: PLPGSQL

BEGIN

    DELETE FROM reporter.materialized_simple_record WHERE id = r_id;

    IF NOT deleted THEN
        INSERT INTO reporter.materialized_simple_record SELECT DISTINCT ON (id) * FROM reporter.old_super_simple_record WHERE id = r_id;
    END IF;

    RETURN TRUE;

END;

Function: reporter.simple_rec_update(r_id bigint)

Returns: boolean

Language: SQL

    SELECT reporter.simple_rec_update($1, FALSE);

Schema search


View: search.best_tsconfig

search.best_tsconfig Structure
F-Key Name Type Description
id integer
ts_config text
SELECT m.id
,
    COALESCE
(f.ts_config
     , c.ts_config
     ,'simple'::text
) AS ts_config
   
FROM (
     (config.metabib_field m
     
   LEFT JOIN config.metabib_class_ts_map c 
          ON (
                 (
                       (c.field_class = m.field_class)
                     AND (c.index_weight = 'C'::bpchar)
                 )
           )
     )
     
LEFT JOIN config.metabib_field_ts_map f 
    ON (
           (
                 (f.metabib_field = m.id)
               AND (f.index_weight = 'C'::bpchar)
           )
     )
);

Index - Schema search


Table: search.relevance_adjustment

search.relevance_adjustment Structure
F-Key Name Type Description
id serial PRIMARY KEY
active boolean NOT NULL DEFAULT true
config.metabib_field.id field integer NOT NULL
bump_type text NOT NULL
multiplier numeric NOT NULL DEFAULT 1.0

 

search.relevance_adjustment Constraints
Name Constraint
relevance_adjustment_bump_type_check CHECK ((bump_type = ANY (ARRAY['word_order'::text, 'first_word'::text, 'full_match'::text])))

Index - Schema search


Table: search.symspell_dictionary

search.symspell_dictionary Structure
F-Key Name Type Description
keyword_count integer NOT NULL
title_count integer NOT NULL
author_count integer NOT NULL
subject_count integer NOT NULL
series_count integer NOT NULL
identifier_count integer NOT NULL
prefix_key text PRIMARY KEY
keyword_suggestions text[]
title_suggestions text[]
author_suggestions text[]
subject_suggestions text[]
series_suggestions text[]
identifier_suggestions text[]

Index - Schema search


Table: search.symspell_dictionary_updates

search.symspell_dictionary_updates Structure
F-Key Name Type Description
transaction_id bigint
keyword_count integer NOT NULL
title_count integer NOT NULL
author_count integer NOT NULL
subject_count integer NOT NULL
series_count integer NOT NULL
identifier_count integer NOT NULL
prefix_key text NOT NULL
keyword_suggestions text[]
title_suggestions text[]
author_suggestions text[]
subject_suggestions text[]
series_suggestions text[]
identifier_suggestions text[]
symspell_dictionary_updates_tid_idx transaction_id

Index - Schema search


Function: search.calculate_visibility_attribute(attr integer, value text)

Returns: integer

Language: SQL

SELECT  ((CASE $2

            WHEN 'luri_org'         THEN 0 -- "b" attr
            WHEN 'bib_source'       THEN 1 -- "b" attr

            WHEN 'copy_flags'       THEN 0 -- "c" attr
            WHEN 'owning_lib'       THEN 1 -- "c" attr
            WHEN 'circ_lib'         THEN 2 -- "c" attr
            WHEN 'status'           THEN 3 -- "c" attr
            WHEN 'location'         THEN 4 -- "c" attr
            WHEN 'location_group'   THEN 5 -- "c" attr

        END) << 28 ) | $1;

/* copy_flags bit positions, LSB-first:

 0: asset.copy.opac_visible


   When adding flags, you must update asset.all_visible_flags()

   Because bib and copy values are stored separately, we can reuse
   shifts, saving us some space. We could probably take back a bit
   too, but I'm not sure its worth squeezing that last one out. We'd
   be left with just 2 slots for copy attrs, rather than 10.
*/


Function: search.calculate_visibility_attribute_list(value text, attr integer[])

Returns: integer[]

Language: SQL

    SELECT ARRAY_AGG(search.calculate_visibility_attribute(x, $1)) FROM UNNEST($2) AS X;

Function: search.calculate_visibility_attribute_test(negate text, value integer[], attr boolean)

Returns: text

Language: SQL

    SELECT  CASE WHEN $3 THEN '!' ELSE '' END || '(' || ARRAY_TO_STRING(search.calculate_visibility_attribute_list($1,$2),'|') || ')';

Function: search.disable_symspell_reification()

Returns: void

Language: SQL

    INSERT INTO config.internal_flag (name,enabled)
      VALUES ('ingest.disable_symspell_reification',TRUE)
    ON CONFLICT (name) DO UPDATE SET enabled = TRUE;

Function: search.enable_symspell_reification()

Returns: void

Language: SQL

    UPDATE config.internal_flag SET enabled = FALSE WHERE name = 'ingest.disable_symspell_reification';

Function: search.facets_for_metarecord_set(count text[], value bigint[])

Returns: SET OF record

Language: SQL

    SELECT id, value, count FROM (
        SELECT mfae.field AS id,
               mfae.value,
               COUNT(DISTINCT mmrsm.metarecord),
               row_number() OVER (
                PARTITION BY mfae.field ORDER BY COUNT(distinct mmrsm.metarecord) DESC
               ) AS rownum
        FROM metabib.facet_entry mfae
        JOIN metabib.metarecord_source_map mmrsm ON (mfae.source = mmrsm.source)
        JOIN config.metabib_field cmf ON (cmf.id = mfae.field)
        WHERE mmrsm.metarecord IN (SELECT * FROM unnest($2))
        AND cmf.facet_field
        AND cmf.field_class NOT IN (SELECT * FROM unnest($1))
        GROUP by 1, 2
    ) all_facets
    WHERE rownum <= (SELECT COALESCE((SELECT value::INT FROM config.global_flag WHERE name = 'search.max_facets_per_field' AND enabled), 1000));

Function: search.facets_for_record_set(count text[], value bigint[])

Returns: SET OF record

Language: SQL

    SELECT id, value, count
      FROM (
        SELECT  mfae.field AS id,
                mfae.value,
                COUNT(DISTINCT mfae.source),
                row_number() OVER (
                    PARTITION BY mfae.field ORDER BY COUNT(DISTINCT mfae.source) DESC
                ) AS rownum
          FROM  metabib.facet_entry mfae
                JOIN config.metabib_field cmf ON (cmf.id = mfae.field)
          WHERE mfae.source = ANY ($2)
                AND cmf.facet_field
                AND cmf.field_class NOT IN (SELECT * FROM unnest($1))
          GROUP by 1, 2
      ) all_facets
      WHERE rownum <= (
        SELECT COALESCE(
            (SELECT value::INT FROM config.global_flag WHERE name = 'search.max_facets_per_field' AND enabled),
            1000
        )
      );

Function: search.highlight_display_fields(delimiter bigint, maxfrags text, shortwords text, maxwords boolean, minwords integer, hl_all integer, css_class integer, tsq_map integer, rid text)

Returns: SET OF highlight_result

Language: PLPGSQL

DECLARE
    tsq_hstore  TEXT;
    tsq         TEXT;
    fields      TEXT;
    afields     INT[];
    seen        INT[];
BEGIN
    IF (tsq_map ILIKE 'hstore%') THEN
        EXECUTE 'SELECT ' || tsq_map INTO tsq_hstore;
    ELSE
        tsq_hstore := tsq_map::HSTORE;
    END IF;

    FOR tsq, fields IN SELECT key, value FROM each(tsq_hstore::HSTORE) LOOP
        SELECT  ARRAY_AGG(unnest::INT) INTO afields
          FROM  unnest(regexp_split_to_array(fields,','));
        seen := seen || afields;

        RETURN QUERY
            SELECT * FROM search.highlight_display_fields_impl(
                rid, tsq, afields, css_class, hl_all,minwords,
                maxwords, shortwords, maxfrags, delimiter
            );
    END LOOP;

    RETURN QUERY
        SELECT  id,
                source,
                field,
                evergreen.escape_for_html(value) AS value,
                evergreen.escape_for_html(value) AS highlight
          FROM  metabib.display_entry
          WHERE source = rid
                AND NOT (field = ANY (seen));
END;

Function: search.highlight_display_fields_impl(delimiter bigint, maxfrags text, shortwords integer[], maxwords text, minwords boolean, hl_all integer, css_class integer, field_list integer, tsq integer, rid text)

Returns: SET OF highlight_result

Language: PLPGSQL

DECLARE
    opts            TEXT := '';
    v_css_class     TEXT := css_class;
    v_delimiter     TEXT := delimiter;
    v_field_list    INT[] := field_list;
    hl_query        TEXT;
BEGIN
    IF v_delimiter LIKE $$%'%$$ OR v_delimiter LIKE '%"%' THEN --"
        v_delimiter := ' ... ';
    END IF;

    IF NOT hl_all THEN
        opts := opts || 'MinWords=' || minwords;
        opts := opts || ', MaxWords=' || maxwords;
        opts := opts || ', ShortWords=' || shortwords;
        opts := opts || ', MaxFragments=' || maxfrags;
        opts := opts || ', FragmentDelimiter="' || delimiter || '"';
    ELSE
        opts := opts || 'HighlightAll=TRUE';
    END IF;

    IF v_css_class LIKE $$%'%$$ OR v_css_class LIKE '%"%' THEN -- "
        v_css_class := 'oils_SH';
    END IF;

    opts := opts || $$, StopSel=</b>, StartSel="<b class='$$ || v_css_class; -- "

    IF v_field_list = '{}'::INT[] THEN
        SELECT ARRAY_AGG(id) INTO v_field_list FROM config.metabib_field WHERE display_field;
    END IF;

    hl_query := $$
        SELECT  de.id,
                de.source,
                de.field,
                evergreen.escape_for_html(de.value) AS value,
                ts_headline(
                    ts_config::REGCONFIG,
                    evergreen.escape_for_html(de.value),
                    $$ || quote_literal(tsq) || $$,
                    $1 || ' ' || mf.field_class || ' ' || mf.name || $xx$'>"$xx$ -- "'
                ) AS highlight
          FROM  metabib.display_entry de
                JOIN config.metabib_field mf ON (mf.id = de.field)
                JOIN search.best_tsconfig t ON (t.id = de.field)
          WHERE de.source = $2
                AND field = ANY ($3)
          ORDER BY de.id;$$;

    RETURN QUERY EXECUTE hl_query USING opts, rid, v_field_list;
END;

Function: search.symspell_build_entries(include_phrases text, old_input text, source_class text, full_input boolean)

Returns: SET OF symspell_dictionary

Language: PLPGSQL

DECLARE
    prefix_length   INT;
    maxED           INT;
    word_list   TEXT[];
    input       TEXT;
    word        TEXT;
    entry       search.symspell_dictionary;
BEGIN
    IF full_input IS NOT NULL THEN
        SELECT value::INT INTO prefix_length FROM config.internal_flag WHERE name = 'symspell.prefix_length' AND enabled;
        prefix_length := COALESCE(prefix_length, 6);

        SELECT value::INT INTO maxED FROM config.internal_flag WHERE name = 'symspell.max_edit_distance' AND enabled;
        maxED := COALESCE(maxED, 3);

        input := evergreen.lowercase(full_input);
        word_list := ARRAY_AGG(x) FROM search.symspell_parse_words_distinct(input) x;
        IF word_list IS NULL THEN
            RETURN;
        END IF;
    
        IF CARDINALITY(word_list) > 1 AND include_phrases THEN
            RETURN QUERY SELECT * FROM search.symspell_build_raw_entry(input, source_class, TRUE, prefix_length, maxED);
        END IF;

        FOREACH word IN ARRAY word_list LOOP
            -- Skip words that have runs of 5 or more digits (I'm looking at you, ISxNs)
            CONTINUE WHEN CHARACTER_LENGTH(word) > 4 AND word ~ '\d{5,}';
            RETURN QUERY SELECT * FROM search.symspell_build_raw_entry(word, source_class, FALSE, prefix_length, maxED);
        END LOOP;
    END IF;

    IF old_input IS NOT NULL THEN
        input := evergreen.lowercase(old_input);

        FOR word IN SELECT x FROM search.symspell_parse_words_distinct(input) x LOOP
            -- similarly skip words that have 5 or more digits here to
            -- avoid adding erroneous prefix deletion entries to the dictionary
            CONTINUE WHEN CHARACTER_LENGTH(word) > 4 AND word ~ '\d{5,}';
            entry.prefix_key := word;

            entry.keyword_count := 0;
            entry.title_count := 0;
            entry.author_count := 0;
            entry.subject_count := 0;
            entry.series_count := 0;
            entry.identifier_count := 0;

            entry.keyword_suggestions := '{}';
            entry.title_suggestions := '{}';
            entry.author_suggestions := '{}';
            entry.subject_suggestions := '{}';
            entry.series_suggestions := '{}';
            entry.identifier_suggestions := '{}';

            IF source_class = 'keyword' THEN entry.keyword_count := -1; END IF;
            IF source_class = 'title' THEN entry.title_count := -1; END IF;
            IF source_class = 'author' THEN entry.author_count := -1; END IF;
            IF source_class = 'subject' THEN entry.subject_count := -1; END IF;
            IF source_class = 'series' THEN entry.series_count := -1; END IF;
            IF source_class = 'identifier' THEN entry.identifier_count := -1; END IF;

            RETURN NEXT entry;
        END LOOP;
    END IF;
END;

Function: search.symspell_build_raw_entry(maxed text, prefix_length text, no_limit boolean, source_class integer, raw_input integer)

Returns: SET OF symspell_dictionary

Language: PLPGSQL

DECLARE
    key         TEXT;
    del_key     TEXT;
    key_list    TEXT[];
    entry       search.symspell_dictionary%ROWTYPE;
BEGIN
    key := raw_input;

    IF NOT no_limit AND CHARACTER_LENGTH(raw_input) > prefix_length THEN
        key := SUBSTRING(key FROM 1 FOR prefix_length);
        key_list := ARRAY[raw_input, key];
    ELSE
        key_list := ARRAY[key];
    END IF;

    FOREACH del_key IN ARRAY key_list LOOP
        -- skip empty keys
        CONTINUE WHEN del_key IS NULL OR CHARACTER_LENGTH(del_key) = 0;

        entry.prefix_key := del_key;

        entry.keyword_count := 0;
        entry.title_count := 0;
        entry.author_count := 0;
        entry.subject_count := 0;
        entry.series_count := 0;
        entry.identifier_count := 0;

        entry.keyword_suggestions := '{}';
        entry.title_suggestions := '{}';
        entry.author_suggestions := '{}';
        entry.subject_suggestions := '{}';
        entry.series_suggestions := '{}';
        entry.identifier_suggestions := '{}';

        IF source_class = 'keyword' THEN entry.keyword_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'title' THEN entry.title_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'author' THEN entry.author_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'subject' THEN entry.subject_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'series' THEN entry.series_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'identifier' THEN entry.identifier_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'keyword' THEN entry.keyword_suggestions := ARRAY[raw_input]; END IF;

        IF del_key = raw_input THEN
            IF source_class = 'keyword' THEN entry.keyword_count := 1; END IF;
            IF source_class = 'title' THEN entry.title_count := 1; END IF;
            IF source_class = 'author' THEN entry.author_count := 1; END IF;
            IF source_class = 'subject' THEN entry.subject_count := 1; END IF;
            IF source_class = 'series' THEN entry.series_count := 1; END IF;
            IF source_class = 'identifier' THEN entry.identifier_count := 1; END IF;
        END IF;

        RETURN NEXT entry;
    END LOOP;

    FOR del_key IN SELECT x FROM UNNEST(search.symspell_generate_edits(key, 1, maxED)) x LOOP

        -- skip empty keys
        CONTINUE WHEN del_key IS NULL OR CHARACTER_LENGTH(del_key) = 0;
        -- skip suggestions that are already too long for the prefix key
        CONTINUE WHEN CHARACTER_LENGTH(del_key) <= (prefix_length - maxED) AND CHARACTER_LENGTH(raw_input) > prefix_length;

        entry.keyword_suggestions := '{}';
        entry.title_suggestions := '{}';
        entry.author_suggestions := '{}';
        entry.subject_suggestions := '{}';
        entry.series_suggestions := '{}';
        entry.identifier_suggestions := '{}';

        IF source_class = 'keyword' THEN entry.keyword_count := 0; END IF;
        IF source_class = 'title' THEN entry.title_count := 0; END IF;
        IF source_class = 'author' THEN entry.author_count := 0; END IF;
        IF source_class = 'subject' THEN entry.subject_count := 0; END IF;
        IF source_class = 'series' THEN entry.series_count := 0; END IF;
        IF source_class = 'identifier' THEN entry.identifier_count := 0; END IF;

        entry.prefix_key := del_key;

        IF source_class = 'keyword' THEN entry.keyword_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'title' THEN entry.title_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'author' THEN entry.author_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'subject' THEN entry.subject_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'series' THEN entry.series_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'identifier' THEN entry.identifier_suggestions := ARRAY[raw_input]; END IF;
        IF source_class = 'keyword' THEN entry.keyword_suggestions := ARRAY[raw_input]; END IF;

        RETURN NEXT entry;
    END LOOP;

END;

Function: search.symspell_dictionary_full_reify()

Returns: SET OF symspell_dictionary

Language: SQL

 WITH new_rows AS (
    DELETE FROM search.symspell_dictionary_updates RETURNING *
 ), computed_rows AS ( -- this collapses the rows deleted into the format we need for UPSERT
    SELECT  SUM(keyword_count)    AS keyword_count,
            SUM(title_count)      AS title_count,
            SUM(author_count)     AS author_count,
            SUM(subject_count)    AS subject_count,
            SUM(series_count)     AS series_count,
            SUM(identifier_count) AS identifier_count,

            prefix_key,

            ARRAY_REMOVE(ARRAY_AGG(DISTINCT keyword_suggestions[1]), NULL)    AS keyword_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT title_suggestions[1]), NULL)      AS title_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT author_suggestions[1]), NULL)     AS author_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT subject_suggestions[1]), NULL)    AS subject_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT series_suggestions[1]), NULL)     AS series_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT identifier_suggestions[1]), NULL) AS identifier_suggestions
      FROM  new_rows
      GROUP BY prefix_key
 )
 INSERT INTO search.symspell_dictionary AS d SELECT * FROM computed_rows
 ON CONFLICT (prefix_key) DO UPDATE SET
    keyword_count = GREATEST(0, d.keyword_count + EXCLUDED.keyword_count),
    keyword_suggestions = evergreen.text_array_merge_unique(EXCLUDED.keyword_suggestions,d.keyword_suggestions),

    title_count = GREATEST(0, d.title_count + EXCLUDED.title_count),
    title_suggestions = evergreen.text_array_merge_unique(EXCLUDED.title_suggestions,d.title_suggestions),

    author_count = GREATEST(0, d.author_count + EXCLUDED.author_count),
    author_suggestions = evergreen.text_array_merge_unique(EXCLUDED.author_suggestions,d.author_suggestions),

    subject_count = GREATEST(0, d.subject_count + EXCLUDED.subject_count),
    subject_suggestions = evergreen.text_array_merge_unique(EXCLUDED.subject_suggestions,d.subject_suggestions),

    series_count = GREATEST(0, d.series_count + EXCLUDED.series_count),
    series_suggestions = evergreen.text_array_merge_unique(EXCLUDED.series_suggestions,d.series_suggestions),

    identifier_count = GREATEST(0, d.identifier_count + EXCLUDED.identifier_count),
    identifier_suggestions = evergreen.text_array_merge_unique(EXCLUDED.identifier_suggestions,d.identifier_suggestions)
 RETURNING *;

Function: search.symspell_dictionary_reify()

Returns: SET OF symspell_dictionary

Language: SQL

 WITH new_rows AS (
    DELETE FROM search.symspell_dictionary_updates WHERE transaction_id = txid_current() RETURNING *
 ), computed_rows AS ( -- this collapses the rows deleted into the format we need for UPSERT
    SELECT  SUM(keyword_count)    AS keyword_count,
            SUM(title_count)      AS title_count,
            SUM(author_count)     AS author_count,
            SUM(subject_count)    AS subject_count,
            SUM(series_count)     AS series_count,
            SUM(identifier_count) AS identifier_count,

            prefix_key,

            ARRAY_REMOVE(ARRAY_AGG(DISTINCT keyword_suggestions[1]), NULL)    AS keyword_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT title_suggestions[1]), NULL)      AS title_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT author_suggestions[1]), NULL)     AS author_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT subject_suggestions[1]), NULL)    AS subject_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT series_suggestions[1]), NULL)     AS series_suggestions,
            ARRAY_REMOVE(ARRAY_AGG(DISTINCT identifier_suggestions[1]), NULL) AS identifier_suggestions
      FROM  new_rows
      GROUP BY prefix_key
 )
 INSERT INTO search.symspell_dictionary AS d SELECT * FROM computed_rows
 ON CONFLICT (prefix_key) DO UPDATE SET
    keyword_count = GREATEST(0, d.keyword_count + EXCLUDED.keyword_count),
    keyword_suggestions = evergreen.text_array_merge_unique(EXCLUDED.keyword_suggestions,d.keyword_suggestions),

    title_count = GREATEST(0, d.title_count + EXCLUDED.title_count),
    title_suggestions = evergreen.text_array_merge_unique(EXCLUDED.title_suggestions,d.title_suggestions),

    author_count = GREATEST(0, d.author_count + EXCLUDED.author_count),
    author_suggestions = evergreen.text_array_merge_unique(EXCLUDED.author_suggestions,d.author_suggestions),

    subject_count = GREATEST(0, d.subject_count + EXCLUDED.subject_count),
    subject_suggestions = evergreen.text_array_merge_unique(EXCLUDED.subject_suggestions,d.subject_suggestions),

    series_count = GREATEST(0, d.series_count + EXCLUDED.series_count),
    series_suggestions = evergreen.text_array_merge_unique(EXCLUDED.series_suggestions,d.series_suggestions),

    identifier_count = GREATEST(0, d.identifier_count + EXCLUDED.identifier_count),
    identifier_suggestions = evergreen.text_array_merge_unique(EXCLUDED.identifier_suggestions,d.identifier_suggestions)

    WHERE (
        EXCLUDED.keyword_count <> 0 OR
        EXCLUDED.title_count <> 0 OR
        EXCLUDED.author_count <> 0 OR
        EXCLUDED.subject_count <> 0 OR
        EXCLUDED.series_count <> 0 OR
        EXCLUDED.identifier_count <> 0 OR
        NOT (EXCLUDED.keyword_suggestions <@ d.keyword_suggestions) OR
        NOT (EXCLUDED.title_suggestions <@ d.title_suggestions) OR
        NOT (EXCLUDED.author_suggestions <@ d.author_suggestions) OR
        NOT (EXCLUDED.subject_suggestions <@ d.subject_suggestions) OR
        NOT (EXCLUDED.series_suggestions <@ d.series_suggestions) OR
        NOT (EXCLUDED.identifier_suggestions <@ d.identifier_suggestions)
    )
 RETURNING *;

Function: search.symspell_generate_edits(maxed text, dist integer, raw_word integer)

Returns: text[]

Language: PLPGSQL

DECLARE
    item    TEXT;
    list    TEXT[] := '{}';
    sublist TEXT[] := '{}';
BEGIN
    FOR I IN 1 .. CHARACTER_LENGTH(raw_word) LOOP
        item := SUBSTRING(raw_word FROM 1 FOR I - 1) || SUBSTRING(raw_word FROM I + 1);
        IF NOT list @> ARRAY[item] THEN
            list := item || list;
            IF dist < maxED AND CHARACTER_LENGTH(raw_word) > dist + 1 THEN
                sublist := search.symspell_generate_edits(item, dist + 1, maxED) || sublist;
            END IF;
        END IF;
    END LOOP;

    IF dist = 1 THEN
        RETURN evergreen.text_array_merge_unique(list, sublist);
    ELSE
        RETURN list || sublist;
    END IF;
END;

Function: search.symspell_lookup(kbdist_weight text, pg_trgm_weight text, soundex_weight integer, count_threshold boolean, xfer_case integer, verbosity integer, search_class integer, raw_input integer)

Returns: SET OF symspell_lookup_output

Language: PLPGSQL

DECLARE
    prefix_length INT;
    maxED         INT;
    good_suggs  HSTORE;
    word_list   TEXT[];
    edit_list   TEXT[] := '{}';
    seen_list   TEXT[] := '{}';
    output      search.symspell_lookup_output;
    output_list search.symspell_lookup_output[];
    entry       RECORD;
    entry_key   TEXT;
    prefix_key  TEXT;
    sugg        TEXT;
    input       TEXT;
    word        TEXT;
    w_pos       INT := -1;
    smallest_ed INT := -1;
    global_ed   INT;
    i_len       INT;
    l_maxED     INT;
BEGIN
    SELECT value::INT INTO prefix_length FROM config.internal_flag WHERE name = 'symspell.prefix_length' AND enabled;
    prefix_length := COALESCE(prefix_length, 6);

    SELECT value::INT INTO maxED FROM config.internal_flag WHERE name = 'symspell.max_edit_distance' AND enabled;
    maxED := COALESCE(maxED, 3);

    word_list := ARRAY_AGG(x) FROM search.symspell_parse_words(raw_input) x;

    -- Common case exact match test for preformance
    IF verbosity = 0 AND CARDINALITY(word_list) = 1 AND CHARACTER_LENGTH(word_list[1]) <= prefix_length THEN
        EXECUTE
          'SELECT  '||search_class||'_suggestions AS suggestions,
                   '||search_class||'_count AS count,
                   prefix_key
             FROM  search.symspell_dictionary
             WHERE prefix_key = $1
                   AND '||search_class||'_count >= $2
                   AND '||search_class||'_suggestions @> ARRAY[$1]'
          INTO entry USING evergreen.lowercase(word_list[1]), COALESCE(count_threshold,1);
        IF entry.prefix_key IS NOT NULL THEN
            output.lev_distance := 0; -- definitionally
            output.prefix_key := entry.prefix_key;
            output.prefix_key_count := entry.count;
            output.suggestion_count := entry.count;
            output.input := word_list[1];
            IF xfer_case THEN
                output.suggestion := search.symspell_transfer_casing(output.input, entry.prefix_key);
            ELSE
                output.suggestion := entry.prefix_key;
            END IF;
            output.norm_input := entry.prefix_key;
            output.qwerty_kb_match := 1;
            output.pg_trgm_sim := 1;
            output.soundex_sim := 1;
            RETURN NEXT output;
            RETURN;
        END IF;
    END IF;

    <<word_loop>>
    FOREACH word IN ARRAY word_list LOOP
        w_pos := w_pos + 1;
        input := evergreen.lowercase(word);
        i_len := CHARACTER_LENGTH(input);
        l_maxED := maxED;

        IF CHARACTER_LENGTH(input) > prefix_length THEN
            prefix_key := SUBSTRING(input FROM 1 FOR prefix_length);
            edit_list := ARRAY[input,prefix_key] || search.symspell_generate_edits(prefix_key, 1, l_maxED);
        ELSE
            edit_list := input || search.symspell_generate_edits(input, 1, l_maxED);
        END IF;

        SELECT ARRAY_AGG(x ORDER BY CHARACTER_LENGTH(x) DESC) INTO edit_list FROM UNNEST(edit_list) x;

        output_list := '{}';
        seen_list := '{}';
        global_ed := NULL;

        <<entry_key_loop>>
        FOREACH entry_key IN ARRAY edit_list LOOP
            smallest_ed := -1;
            IF global_ed IS NOT NULL THEN
                smallest_ed := global_ed;
            END IF;

            FOR entry IN EXECUTE
                'SELECT  '||search_class||'_suggestions AS suggestions,
                         '||search_class||'_count AS count,
                         prefix_key
                   FROM  search.symspell_dictionary
                   WHERE prefix_key = $1
                         AND '||search_class||'_suggestions IS NOT NULL'
                USING entry_key
            LOOP

                SELECT  HSTORE(
                            ARRAY_AGG(
                                ARRAY[s, evergreen.levenshtein_damerau_edistance(input,s,l_maxED)::TEXT]
                                    ORDER BY evergreen.levenshtein_damerau_edistance(input,s,l_maxED) DESC
                            )
                        )
                  INTO  good_suggs
                  FROM  UNNEST(entry.suggestions) s
                  WHERE (ABS(CHARACTER_LENGTH(s) - i_len) <= maxEd AND evergreen.levenshtein_damerau_edistance(input,s,l_maxED) BETWEEN 0 AND l_maxED)
                        AND NOT seen_list @> ARRAY[s];

                CONTINUE WHEN good_suggs IS NULL;

                FOR sugg, output.suggestion_count IN EXECUTE
                    'SELECT  prefix_key, '||search_class||'_count
                       FROM  search.symspell_dictionary
                       WHERE prefix_key = ANY ($1)
                             AND '||search_class||'_count >= $2'
                    USING AKEYS(good_suggs), COALESCE(count_threshold,1)
                LOOP

                    output.lev_distance := good_suggs->sugg;
                    seen_list := seen_list || sugg;

                    -- Track the smallest edit distance among suggestions from this prefix key.
                    IF smallest_ed = -1 OR output.lev_distance < smallest_ed THEN
                        smallest_ed := output.lev_distance;
                    END IF;

                    -- Track the smallest edit distance for all prefix keys for this word.
                    IF global_ed IS NULL OR smallest_ed < global_ed THEN
                        global_ed = smallest_ed;
                        -- And if low verbosity, ignore suggs with a larger distance from here on.
                        IF verbosity <= 1 THEN
                            l_maxED := global_ed;
                        END IF;
                    END IF;

                    -- Lev distance is our main similarity measure. While
                    -- trgm or soundex similarity could be the main filter,
                    -- Lev is both language agnostic and faster.
                    --
                    -- Here we will skip suggestions that have a longer edit distance
                    -- than the shortest we've already found. This is simply an
                    -- optimization that allows us to avoid further processing
                    -- of this entry. It would be filtered out later.
                    CONTINUE WHEN output.lev_distance > global_ed AND verbosity <= 1;

                    -- If we have an exact match on the suggestion key we can also avoid
                    -- some function calls.
                    IF output.lev_distance = 0 THEN
                        output.qwerty_kb_match := 1;
                        output.pg_trgm_sim := 1;
                        output.soundex_sim := 1;
                    ELSE
                        IF kbdist_weight THEN
                            output.qwerty_kb_match := evergreen.qwerty_keyboard_distance_match(input, sugg);
                        ELSE
                            output.qwerty_kb_match := 0;
                        END IF;
                        IF pg_trgm_weight THEN
                            output.pg_trgm_sim := similarity(input, sugg);
                        ELSE
                            output.pg_trgm_sim := 0;
                        END IF;
                        IF soundex_weight THEN
                            output.soundex_sim := difference(input, sugg) / 4.0;
                        ELSE
                            output.soundex_sim := 0;
                        END IF;
                    END IF;

                    -- Fill in some fields
                    IF xfer_case AND input <> word THEN
                        output.suggestion := search.symspell_transfer_casing(word, sugg);
                    ELSE
                        output.suggestion := sugg;
                    END IF;
                    output.prefix_key := entry.prefix_key;
                    output.prefix_key_count := entry.count;
                    output.input := word;
                    output.norm_input := input;
                    output.word_pos := w_pos;

                    -- We can't "cache" a set of generated records directly, so
                    -- here we build up an array of search.symspell_lookup_output
                    -- records that we can revivicate later as a table using UNNEST().
                    output_list := output_list || output;

                    EXIT entry_key_loop WHEN smallest_ed = 0 AND verbosity = 0; -- exact match early exit
                    CONTINUE entry_key_loop WHEN smallest_ed = 0 AND verbosity = 1; -- exact match early jump to the next key

                END LOOP; -- loop over suggestions
            END LOOP; -- loop over entries
        END LOOP; -- loop over entry_keys

        -- Now we're done examining this word
        IF verbosity = 0 THEN
            -- Return the "best" suggestion from the smallest edit
            -- distance group.  We define best based on the weighting
            -- of the non-lev similarity measures and use the suggestion
            -- use count to break ties.
            RETURN QUERY
                SELECT * FROM UNNEST(output_list)
                    ORDER BY lev_distance,
                        (soundex_sim * COALESCE(soundex_weight,0))
                            + (pg_trgm_sim * COALESCE(pg_trgm_weight,0))
                            + (qwerty_kb_match * COALESCE(kbdist_weight,0)) DESC,
                        suggestion_count DESC
                        LIMIT 1;
        ELSIF verbosity = 1 THEN
            -- Return all suggestions from the smallest
            -- edit distance group.
            RETURN QUERY
                SELECT * FROM UNNEST(output_list) WHERE lev_distance = smallest_ed
                    ORDER BY (soundex_sim * COALESCE(soundex_weight,0))
                            + (pg_trgm_sim * COALESCE(pg_trgm_weight,0))
                            + (qwerty_kb_match * COALESCE(kbdist_weight,0)) DESC,
                        suggestion_count DESC;
        ELSIF verbosity = 2 THEN
            -- Return everything we find, along with relevant stats
            RETURN QUERY
                SELECT * FROM UNNEST(output_list)
                    ORDER BY lev_distance,
                        (soundex_sim * COALESCE(soundex_weight,0))
                            + (pg_trgm_sim * COALESCE(pg_trgm_weight,0))
                            + (qwerty_kb_match * COALESCE(kbdist_weight,0)) DESC,
                        suggestion_count DESC;
        ELSIF verbosity = 3 THEN
            -- Return everything we find from the two smallest edit distance groups
            RETURN QUERY
                SELECT * FROM UNNEST(output_list)
                    WHERE lev_distance IN (SELECT DISTINCT lev_distance FROM UNNEST(output_list) ORDER BY 1 LIMIT 2)
                    ORDER BY lev_distance,
                        (soundex_sim * COALESCE(soundex_weight,0))
                            + (pg_trgm_sim * COALESCE(pg_trgm_weight,0))
                            + (qwerty_kb_match * COALESCE(kbdist_weight,0)) DESC,
                        suggestion_count DESC;
        ELSIF verbosity = 4 THEN
            -- Return everything we find from the two smallest edit distance groups that are NOT 0 distance
            RETURN QUERY
                SELECT * FROM UNNEST(output_list)
                    WHERE lev_distance IN (SELECT DISTINCT lev_distance FROM UNNEST(output_list) WHERE lev_distance > 0 ORDER BY 1 LIMIT 2)
                    ORDER BY lev_distance,
                        (soundex_sim * COALESCE(soundex_weight,0))
                            + (pg_trgm_sim * COALESCE(pg_trgm_weight,0))
                            + (qwerty_kb_match * COALESCE(kbdist_weight,0)) DESC,
                        suggestion_count DESC;
        END IF;
    END LOOP; -- loop over words
END;

Function: search.symspell_maintain_entries()

Returns: trigger

Language: PLPGSQL

DECLARE
    search_class    TEXT;
    new_value       TEXT := NULL;
    old_value       TEXT := NULL;
BEGIN
    search_class := COALESCE(TG_ARGV[0], SPLIT_PART(TG_TABLE_NAME,'_',1));

    IF TG_OP IN ('INSERT', 'UPDATE') THEN
        new_value := NEW.value;
    END IF;

    IF TG_OP IN ('DELETE', 'UPDATE') THEN
        old_value := OLD.value;
    END IF;

    IF new_value = old_value THEN
        -- same, move along
    ELSE
        INSERT INTO search.symspell_dictionary_updates
            SELECT  txid_current(), *
              FROM  search.symspell_build_entries(
                        new_value,
                        search_class,
                        old_value
                    );
    END IF;

    RETURN NULL; -- always fired AFTER
END;

Function: search.symspell_parse_words(phrase text)

Returns: SET OF text

Language: SQL

    SELECT UNNEST(x) FROM regexp_matches($1, '([[:alnum:]]+''*[[:alnum:]]*)', 'g') x;

Function: search.symspell_parse_words_distinct(phrase text)

Returns: SET OF text

Language: SQL

    SELECT DISTINCT UNNEST(x) FROM regexp_matches($1, '([[:alnum:]]+''*[[:alnum:]]*)', 'g') x;

Function: search.symspell_transfer_casing(withoutcase text, withcase text)

Returns: text

Language: PLPGSQL

DECLARE
    woChars TEXT[];
    curr    TEXT;
    ind     INT := 1;
BEGIN
    woChars := regexp_split_to_array(withoutCase,'');
    FOR curr IN SELECT x FROM regexp_split_to_table(withCase, '') x LOOP
        IF curr = evergreen.uppercase(curr) THEN
            woChars[ind] := evergreen.uppercase(woChars[ind]);
        END IF;
        ind := ind + 1;
    END LOOP;
    RETURN ARRAY_TO_STRING(woChars,'');
END;

Schema serial


View: serial.any_summary

serial.any_summary Structure
F-Key Name Type Description
summary_type text
id integer
distribution integer
generated_coverage text
textual_holdings text
show_generated boolean
SELECT'basic'::text AS summary_type
,
    basic_summary.id
,
    basic_summary.distribution
,
    basic_summary.generated_coverage
,
    basic_summary.textual_holdings
,
    basic_summary.show_generated
   
FROM serial.basic_summary

UNION
 
SELECT'index'::text AS summary_type
,
    index_summary.id
,
    index_summary.distribution
,
    index_summary.generated_coverage
,
    index_summary.textual_holdings
,
    index_summary.show_generated
   
FROM serial.index_summary

UNION
 
SELECT'supplement'::text AS summary_type
,
    supplement_summary.id
,
    supplement_summary.distribution
,
    supplement_summary.generated_coverage
,
    supplement_summary.textual_holdings
,
    supplement_summary.show_generated
   
FROM serial.supplement_summary;

Index - Schema serial


Table: serial.basic_summary

serial.basic_summary Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.distribution.id distribution integer NOT NULL
generated_coverage text NOT NULL
textual_holdings text
show_generated boolean NOT NULL DEFAULT true
serial_basic_summary_dist_idx distribution

Index - Schema serial


Table: serial.caption_and_pattern

serial.caption_and_pattern Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.subscription.id subscription integer NOT NULL
type text NOT NULL
create_date timestamp with time zone NOT NULL DEFAULT now()
start_date timestamp with time zone NOT NULL DEFAULT now()
end_date timestamp with time zone
active boolean NOT NULL DEFAULT false
pattern_code text NOT NULL
enum_1 text
enum_2 text
enum_3 text
enum_4 text
enum_5 text
enum_6 text
chron_1 text
chron_2 text
chron_3 text
chron_4 text
chron_5 text

 

serial.caption_and_pattern Constraints
Name Constraint
cap_type CHECK ((type = ANY (ARRAY['basic'::text, 'supplement'::text, 'index'::text])))

Tables referencing this one via Foreign Key Constraints:

serial_caption_and_pattern_sub_idx subscription

Index - Schema serial


Table: serial.distribution

serial.distribution Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.record_entry.id record_entry bigint
summary_method text
serial.subscription.id subscription integer NOT NULL
actor.org_unit.id holding_lib integer NOT NULL
label text NOT NULL
display_grouping text NOT NULL DEFAULT 'chron'::text
asset.call_number.id receive_call_number bigint
asset.copy_template.id receive_unit_template integer
asset.call_number.id bind_call_number bigint
asset.copy_template.id bind_unit_template integer
unit_label_prefix text
unit_label_suffix text

 

serial.distribution Constraints
Name Constraint
distribution_display_grouping_check CHECK ((display_grouping = ANY (ARRAY['enum'::text, 'chron'::text])))
sdist_summary_method_check CHECK (((summary_method IS NULL) OR (summary_method = ANY (ARRAY['add_to_sre'::text, 'merge_with_sre'::text, 'use_sre_only'::text, 'use_sdist_only'::text]))))

Tables referencing this one via Foreign Key Constraints:

serial_distribution_holding_lib_idx holding_lib serial_distribution_sub_idx subscription

Index - Schema serial


Table: serial.distribution_note

serial.distribution_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.distribution.id distribution integer NOT NULL
actor.usr.id creator integer NOT NULL
create_date timestamp with time zone DEFAULT now()
pub boolean NOT NULL DEFAULT false
alert boolean NOT NULL DEFAULT false
title text NOT NULL
value text NOT NULL
serial_distribution_note_dist_idx distribution

Index - Schema serial


Table: serial.index_summary

serial.index_summary Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.distribution.id distribution integer NOT NULL
generated_coverage text NOT NULL
textual_holdings text
show_generated boolean NOT NULL DEFAULT true
serial_index_summary_dist_idx distribution

Index - Schema serial


Table: serial.issuance

serial.issuance Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id creator integer NOT NULL
actor.usr.id editor integer NOT NULL
create_date timestamp with time zone NOT NULL DEFAULT now()
edit_date timestamp with time zone NOT NULL DEFAULT now()
serial.subscription.id subscription integer NOT NULL
label text
date_published timestamp with time zone
serial.caption_and_pattern.id caption_and_pattern integer
holding_code text
holding_type text
holding_link_id integer

 

serial.issuance Constraints
Name Constraint
issuance_holding_code_check CHECK (((holding_code IS NULL) OR could_be_serial_holding_code(holding_code)))
issuance_holding_code_check1 CHECK (((holding_code IS NULL) OR is_json(holding_code)))
valid_holding_type CHECK (((holding_type IS NULL) OR (holding_type = ANY (ARRAY['basic'::text, 'supplement'::text, 'index'::text]))))

Tables referencing this one via Foreign Key Constraints:

serial_issuance_caption_and_pattern_idx caption_and_pattern serial_issuance_date_published_idx date_published serial_issuance_sub_idx subscription

Index - Schema serial


Table: serial.item

serial.item Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id creator integer NOT NULL
actor.usr.id editor integer NOT NULL
create_date timestamp with time zone NOT NULL DEFAULT now()
edit_date timestamp with time zone NOT NULL DEFAULT now()
serial.issuance.id issuance integer NOT NULL
serial.stream.id stream integer NOT NULL
serial.unit.id unit integer
asset.uri.id uri integer
date_expected timestamp with time zone
date_received timestamp with time zone
status text DEFAULT 'Expected'::text
shadowed boolean NOT NULL DEFAULT false

 

serial.item Constraints
Name Constraint
valid_status CHECK ((status = ANY (ARRAY['Bindery'::text, 'Bound'::text, 'Claimed'::text, 'Discarded'::text, 'Expected'::text, 'Not Held'::text, 'Not Published'::text, 'Received'::text])))

Tables referencing this one via Foreign Key Constraints:

serial_item_date_received_idx date_received serial_item_issuance_idx issuance serial_item_status_idx status serial_item_stream_idx stream serial_item_unit_idx unit serial_item_uri_idx uri

Index - Schema serial


Table: serial.item_note

serial.item_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.item.id item integer NOT NULL
actor.usr.id creator integer NOT NULL
create_date timestamp with time zone DEFAULT now()
pub boolean NOT NULL DEFAULT false
alert boolean NOT NULL DEFAULT false
title text NOT NULL
value text NOT NULL
serial_item_note_item_idx item

Index - Schema serial


Table: serial.materialized_holding_code

serial.materialized_holding_code Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
serial.issuance.id issuance integer NOT NULL
subfield character(1)
value text
assist_holdings_display issuance, subfield

Index - Schema serial


Table: serial.pattern_template

serial.pattern_template Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text NOT NULL
pattern_code text NOT NULL
actor.org_unit.id owning_lib integer
share_depth integer NOT NULL
serial_pattern_template_name_idx lowercase(name)

Index - Schema serial


Table: serial.record_entry

serial.record_entry Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
biblio.record_entry.id record bigint
actor.org_unit.id owning_lib integer NOT NULL DEFAULT 1
creator integer NOT NULL DEFAULT 1
editor integer NOT NULL DEFAULT 1
source integer
create_date timestamp with time zone NOT NULL DEFAULT now()
edit_date timestamp with time zone NOT NULL DEFAULT now()
active boolean NOT NULL DEFAULT true
deleted boolean NOT NULL DEFAULT false
marc text
last_xact_id text NOT NULL

Tables referencing this one via Foreign Key Constraints:

serial_record_entry_creator_idx creator serial_record_entry_editor_idx editor serial_record_entry_owning_lib_idx owning_lib, deleted serial_record_entry_record_idx record

Index - Schema serial


Table: serial.routing_list_user

serial.routing_list_user Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.stream.id stream integer UNIQUE#1 NOT NULL
pos integer UNIQUE#1 NOT NULL DEFAULT 1
actor.usr.id reader integer
department text
note text

 

serial.routing_list_user Constraints
Name Constraint
reader_or_dept CHECK ((((reader IS NOT NULL) AND (department IS NULL)) OR ((reader IS NULL) AND (department IS NOT NULL))))
serial_routing_list_user_reader_idx reader serial_routing_list_user_stream_idx stream

Index - Schema serial


Table: serial.stream

serial.stream Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.distribution.id distribution integer NOT NULL
routing_label text

Tables referencing this one via Foreign Key Constraints:

serial_stream_dist_idx distribution

Index - Schema serial


Table: serial.subscription

serial.subscription Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owning_lib integer NOT NULL DEFAULT 1
start_date timestamp with time zone NOT NULL
end_date timestamp with time zone
biblio.record_entry.id record_entry bigint
expected_date_offset interval

Tables referencing this one via Foreign Key Constraints:

serial_subscription_owner_idx owning_lib serial_subscription_record_idx record_entry

Index - Schema serial


Table: serial.subscription_note

serial.subscription_note Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.subscription.id subscription integer NOT NULL
actor.usr.id creator integer NOT NULL
create_date timestamp with time zone DEFAULT now()
pub boolean NOT NULL DEFAULT false
alert boolean NOT NULL DEFAULT false
title text NOT NULL
value text NOT NULL
serial_subscription_note_sub_idx subscription

Index - Schema serial


Table: serial.supplement_summary

serial.supplement_summary Structure
F-Key Name Type Description
id serial PRIMARY KEY
serial.distribution.id distribution integer NOT NULL
generated_coverage text NOT NULL
textual_holdings text
show_generated boolean NOT NULL DEFAULT true
serial_supplement_summary_dist_idx distribution

Index - Schema serial


Table: serial.unit

serial.unit Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('asset.copy_id_seq'::regclass)
circ_lib integer NOT NULL
actor.usr.id creator bigint NOT NULL
asset.call_number.id call_number bigint NOT NULL
actor.usr.id editor bigint NOT NULL
create_date timestamp with time zone DEFAULT now()
edit_date timestamp with time zone DEFAULT now()
copy_number integer
status integer NOT NULL
location integer NOT NULL DEFAULT 1
loan_duration integer NOT NULL
fine_level integer NOT NULL
age_protect integer
circulate boolean NOT NULL DEFAULT true
deposit boolean NOT NULL DEFAULT false
ref boolean NOT NULL DEFAULT false
holdable boolean NOT NULL DEFAULT true
deposit_amount numeric(6,2) NOT NULL DEFAULT 0.00
price numeric(8,2)
barcode text NOT NULL
circ_modifier text
circ_as_type text
dummy_title text
dummy_author text
alert_message text
opac_visible boolean NOT NULL DEFAULT true
deleted boolean NOT NULL DEFAULT false
floating integer
dummy_isbn text
status_changed_time timestamp with time zone
active_date timestamp with time zone
mint_condition boolean NOT NULL DEFAULT true
cost numeric(8,2)
sort_key text
detailed_contents text NOT NULL
summary_contents text NOT NULL

Table serial.unit Inherits copy,

 

serial.unit Constraints
Name Constraint
copy_fine_level_check CHECK ((fine_level = ANY (ARRAY[1, 2, 3])))
copy_loan_duration_check CHECK ((loan_duration = ANY (ARRAY[1, 2, 3])))

Tables referencing this one via Foreign Key Constraints:

unit_avail_cn_idx call_number unit_cn_idx call_number unit_creator_idx creator unit_editor_idx editor

Index - Schema serial


Function: serial.materialize_holding_code()

Returns: trigger

Language: PLPERLU

 
use strict;

use MARC::Field;
use JSON::XS;

if (not defined $_TD->{new}{holding_code}) {
    elog(WARNING, 'NULL in "holding_code" column of serial.issuance allowed for now, but may not be useful');
    return;
}

# Do nothing if holding_code has not changed...

if ($_TD->{new}{holding_code} eq $_TD->{old}{holding_code}) {
    # ... unless the following internal flag is set.

    my $flag_rv = spi_exec_query(q{
        SELECT * FROM config.internal_flag
        WHERE name = 'serial.rematerialize_on_same_holding_code' AND enabled
    }, 1);
    return unless $flag_rv->{processed};
}


my $holding_code = (new JSON::XS)->decode($_TD->{new}{holding_code});

my $field = new MARC::Field('999', @$holding_code); # tag doesnt matter

my $dstmt = spi_prepare(
    'DELETE FROM serial.materialized_holding_code WHERE issuance = $1',
    'INT'
);
spi_exec_prepared($dstmt, $_TD->{new}{id});

my $istmt = spi_prepare(
    q{
        INSERT INTO serial.materialized_holding_code (
            issuance, subfield, value
        ) VALUES ($1, $2, $3)
    }, qw{INT CHAR TEXT}
);

foreach ($field->subfields) {
    spi_exec_prepared(
        $istmt,
        $_TD->{new}{id},
        $_->[0],
        $_->[1]
    );
}

return;


Function: serial.pattern_templates_visible_to(org_unit integer)

Returns: SET OF pattern_template

Language: PLPGSQL

BEGIN
    RETURN QUERY SELECT *
           FROM serial.pattern_template spt
           WHERE (
             SELECT ARRAY_AGG(id)
             FROM actor.org_unit_descendants(spt.owning_lib, spt.share_depth)
           ) @@ org_unit::TEXT::QUERY_INT;
END;

Schema staging


Table: staging.billing_address_stage

staging.billing_address_stage Structure
F-Key Name Type Description
row_id bigint PRIMARY KEY DEFAULT nextval('staging.mailing_address_stage_row_id_seq'::regclass)
row_date timestamp with time zone DEFAULT now()
usrname text NOT NULL
street1 text
street2 text
city text NOT NULL DEFAULT ''::text
county text
state text
country text NOT NULL DEFAULT 'US'::text
post_code text NOT NULL
complete boolean DEFAULT false

Index - Schema staging


Table: staging.card_stage

staging.card_stage Structure
F-Key Name Type Description
row_id bigserial PRIMARY KEY
row_date timestamp with time zone DEFAULT now()
usrname text NOT NULL
barcode text NOT NULL
complete boolean DEFAULT false

Index - Schema staging


Table: staging.mailing_address_stage

staging.mailing_address_stage Structure
F-Key Name Type Description
row_id bigserial PRIMARY KEY
row_date timestamp with time zone DEFAULT now()
usrname text NOT NULL
street1 text
street2 text
city text NOT NULL DEFAULT ''::text
county text
state text
country text NOT NULL DEFAULT 'US'::text
post_code text NOT NULL
complete boolean DEFAULT false

Index - Schema staging


Table: staging.setting_stage

staging.setting_stage Structure
F-Key Name Type Description
row_id bigserial PRIMARY KEY
row_date timestamp with time zone DEFAULT now()
usrname text NOT NULL
setting text NOT NULL
value text NOT NULL
complete boolean DEFAULT false

Index - Schema staging


Table: staging.statcat_stage

staging.statcat_stage Structure
F-Key Name Type Description
row_id bigserial PRIMARY KEY
row_date timestamp with time zone DEFAULT now()
usrname text NOT NULL
statcat text NOT NULL
value text NOT NULL
complete boolean DEFAULT false

Index - Schema staging


Table: staging.user_stage

staging.user_stage Structure
F-Key Name Type Description
row_id bigserial PRIMARY KEY
row_date timestamp with time zone DEFAULT now()
usrname text NOT NULL
profile text
email text
passwd text
ident_type integer DEFAULT 3
first_given_name text
second_given_name text
family_name text
pref_first_given_name text
pref_second_given_name text
pref_family_name text
day_phone text
evening_phone text
home_ou integer DEFAULT 2
dob text
complete boolean DEFAULT false
actor.usr.id requesting_usr integer

Index - Schema staging


Function: staging.purge_pending_users()

Returns: void

Language: PLPGSQL

DECLARE
    org_id INT;
    intvl TEXT;
BEGIN
    FOR org_id IN SELECT DISTINCT(home_ou) FROM staging.user_stage LOOP

        SELECT INTO intvl value FROM 
            actor.org_unit_ancestor_setting(
                'opac.pending_user_expire_interval', org_id);

        CONTINUE WHEN intvl IS NULL OR intvl ILIKE 'null';

        -- de-JSON-ify the string
        SELECT INTO intvl TRIM(BOTH '"' FROM intvl);

        DELETE FROM staging.user_stage 
            WHERE home_ou = org_id AND row_date + intvl::INTERVAL < NOW();

    END LOOP;
END;

Schema stats


Schema unapi


Table: unapi.bre_output_layout

unapi.bre_output_layout Structure
F-Key Name Type Description
name text PRIMARY KEY
config.xml_transform.name transform text
mime_type text NOT NULL
feed_top text NOT NULL
holdings_element text
title_element text
description_element text
creator_element text
update_ts_element text

Index - Schema unapi


Function: unapi.acl(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
                name location,
                XMLATTRIBUTES(
                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                    id AS ident,
                    holdable,
                    opac_visible,
                    label_prefix AS prefix,
                    label_suffix AS suffix
                ),
                name
            )
      FROM  asset.copy_location
      WHERE id = $1;

Function: unapi.acn(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name volume,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        'tag:open-ils.org:U2@acn/' || acn.id AS id,
                        acn.id AS vol_id, o.shortname AS lib,
                        o.opac_visible AS opac_visible,
                        deleted, label, label_sortkey, label_class, record
                    ),
                    unapi.aou( owning_lib, $2, 'owning_lib', array_remove($4,'acn'), $5, $6, $7, $8),
                    CASE 
                        WHEN ('acp' = ANY ($4)) THEN
                            CASE WHEN $6 IS NOT NULL THEN
                                XMLELEMENT( name copies,
                                    (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'acn'), $5, $6, $7, $8, FALSE),
                                            evergreen.rank_cp(cp) AS rank_avail
                                          FROM  asset.copy cp
                                                JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5), $6) aoud ON (cp.circ_lib = aoud.id)
                                          WHERE cp.call_number = acn.id
                                              AND cp.deleted IS FALSE
                                          ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
                                          LIMIT ($7 -> 'acp')::INT
                                          OFFSET ($8 -> 'acp')::INT
                                    )x)
                                )
                            ELSE
                                XMLELEMENT( name copies,
                                    (SELECT XMLAGG(acp ORDER BY rank_avail) FROM (
                                        SELECT  unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'acn'), $5, $6, $7, $8, FALSE),
                                            evergreen.rank_cp(cp) AS rank_avail
                                          FROM  asset.copy cp
                                                JOIN actor.org_unit_descendants( (SELECT id FROM actor.org_unit WHERE shortname = $5) ) aoud ON (cp.circ_lib = aoud.id)
                                          WHERE cp.call_number = acn.id
                                              AND cp.deleted IS FALSE
                                          ORDER BY rank_avail, COALESCE(cp.copy_number,0), cp.barcode
                                          LIMIT ($7 -> 'acp')::INT
                                          OFFSET ($8 -> 'acp')::INT
                                    )x)
                                )
                            END
                        ELSE NULL
                    END,
                    XMLELEMENT(
                        name uris,
                        (SELECT XMLAGG(auri) FROM (SELECT unapi.auri(uri,'xml','uri', array_remove($4,'acn'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE call_number = acn.id)x)
                    ),
                    unapi.acnp( acn.prefix, 'marcxml', 'prefix', array_remove($4,'acn'), $5, $6, $7, $8, FALSE),
                    unapi.acns( acn.suffix, 'marcxml', 'suffix', array_remove($4,'acn'), $5, $6, $7, $8, FALSE),
                    CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( acn.record, 'marcxml', 'record', array_remove($4,'acn'), $5, $6, $7, $8, FALSE) ELSE NULL END
                ) AS x
          FROM  asset.call_number acn
                JOIN actor.org_unit o ON (o.id = acn.owning_lib)
          WHERE acn.id = $1
              AND acn.deleted IS FALSE
          GROUP BY acn.id, o.shortname, o.opac_visible, deleted, label, label_sortkey, label_class, owning_lib, record, acn.prefix, acn.suffix;

Function: unapi.acnp(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name call_number_prefix,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        id AS ident,
                        label,
                        'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
                        label_sortkey
                    )
                )
          FROM  asset.call_number_prefix
          WHERE id = $1;

Function: unapi.acns(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name call_number_suffix,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        id AS ident,
                        label,
                        'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
                        label_sortkey
                    )
                )
          FROM  asset.call_number_suffix
          WHERE id = $1;

Function: unapi.acp(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name copy,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
                        create_date, edit_date, copy_number, circulate, deposit,
                        ref, holdable, deleted, deposit_amount, price, barcode,
                        circ_modifier, circ_as_type, opac_visible, age_protect
                    ),
                    unapi.ccs( status, $2, 'status', array_remove($4,'acp'), $5, $6, $7, $8, FALSE),
                    unapi.acl( location, $2, 'location', array_remove($4,'acp'), $5, $6, $7, $8, FALSE),
                    unapi.aou( circ_lib, $2, 'circ_lib', array_remove($4,'acp'), $5, $6, $7, $8),
                    unapi.aou( circ_lib, $2, 'circlib', array_remove($4,'acp'), $5, $6, $7, $8),
                    CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
                    CASE 
                        WHEN ('acpn' = ANY ($4)) THEN
                            XMLELEMENT( name copy_notes,
                                (SELECT XMLAGG(acpn) FROM (
                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
                                      FROM  asset.copy_note
                                      WHERE owning_copy = cp.id AND pub
                                )x)
                            )
                        ELSE NULL
                    END,
                    CASE 
                        WHEN ('ascecm' = ANY ($4)) THEN
                            XMLELEMENT( name statcats,
                                (SELECT XMLAGG(ascecm) FROM (
                                    SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
                                      FROM  asset.stat_cat_entry_copy_map
                                      WHERE owning_copy = cp.id
                                )x)
                            )
                        ELSE NULL
                    END,
                    CASE
                        WHEN ('bre' = ANY ($4)) THEN
                            XMLELEMENT( name foreign_records,
                                (SELECT XMLAGG(bre) FROM (
                                    SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
                                      FROM  biblio.peer_bib_copy_map
                                      WHERE target_copy = cp.id
                                )x)

                            )
                        ELSE NULL
                    END,
                    CASE 
                        WHEN ('bmp' = ANY ($4)) THEN
                            XMLELEMENT( name monograph_parts,
                                (SELECT XMLAGG(bmp) FROM (
                                    SELECT  unapi.bmp( part, 'xml', 'monograph_part', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
                                      FROM  asset.copy_part_map
                                      WHERE target_copy = cp.id
                                )x)
                            )
                        ELSE NULL
                    END,
                    CASE 
                        WHEN ('circ' = ANY ($4)) THEN
                            XMLELEMENT( name current_circulation,
                                (SELECT XMLAGG(circ) FROM (
                                    SELECT  unapi.circ( id, 'xml', 'circ', array_remove($4,'circ'), $5, $6, $7, $8, FALSE)
                                      FROM  action.circulation
                                      WHERE target_copy = cp.id
                                            AND checkin_time IS NULL
                                )x)
                            )
                        ELSE NULL
                    END
                )
          FROM  asset.copy cp
          WHERE id = $1
              AND cp.deleted IS FALSE
          GROUP BY id, status, location, circ_lib, call_number, create_date,
              edit_date, copy_number, circulate, deposit, ref, holdable,
              deleted, deposit_amount, price, barcode, circ_modifier,
              circ_as_type, opac_visible, age_protect;

Function: unapi.acpn(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name copy_note,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        create_date AS date,
                        title
                    ),
                    value
                )
          FROM  asset.copy_note
          WHERE id = $1;

Function: unapi.aou(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: PLPGSQL

DECLARE
    output XML;
BEGIN
    IF ename = 'circlib' THEN
        SELECT  XMLELEMENT(
                    name circlib,
                    XMLATTRIBUTES(
                        'http://open-ils.org/spec/actors/v1' AS xmlns,
                        id AS ident
                    ),
                    name
                ) INTO output
          FROM  actor.org_unit aou
          WHERE id = obj_id;
    ELSE
        EXECUTE $$SELECT  XMLELEMENT(
                    name $$ || ename || $$,
                    XMLATTRIBUTES(
                        'http://open-ils.org/spec/actors/v1' AS xmlns,
                        'tag:open-ils.org:U2@aou/' || id AS id,
                        shortname, name, opac_visible
                    )
                )
          FROM  actor.org_unit aou
         WHERE id = $1 $$ INTO output USING obj_id;
    END IF;

    RETURN output;

END;

Function: unapi.ascecm(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name statcat,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        sc.name,
                        sc.opac_visible
                    ),
                    asce.value
                )
          FROM  asset.stat_cat_entry asce
                JOIN asset.stat_cat sc ON (sc.id = asce.stat_cat)
          WHERE asce.id = $1;

Function: unapi.auri(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name uri,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        'tag:open-ils.org:U2@auri/' || uri.id AS id,
                        use_restriction,
                        href,
                        label
                    ),
                    CASE 
                        WHEN ('acn' = ANY ($4)) THEN
                            XMLELEMENT( name copies,
                                (SELECT XMLAGG(acn) FROM (SELECT unapi.acn( call_number, 'xml', 'copy', array_remove($4,'auri'), $5, $6, $7, $8, FALSE) FROM asset.uri_call_number_map WHERE uri = uri.id)x)
                            )
                        ELSE NULL
                    END
                ) AS x
          FROM  asset.uri uri
          WHERE uri.id = $1
          GROUP BY uri.id, use_restriction, href, label;

Function: unapi.biblio_record_entry_feed(pref_lib bigint[], header_xml text, unapi_url text[], update_ts text, creator integer, description public.hstore, title public.hstore, include_xmlns boolean, soffset text, slimit text, depth text, org text, includes text, format xml, id_list integer)

Returns: xml

Language: PLPGSQL

DECLARE
    layout          unapi.bre_output_layout%ROWTYPE;
    transform       config.xml_transform%ROWTYPE;
    item_format     TEXT;
    tmp_xml         TEXT;
    xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
    ouid            INT;
    element_list    TEXT[];
BEGIN

    IF org = '-' OR org IS NULL THEN
        SELECT shortname INTO org FROM evergreen.org_top();
    END IF;

    SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
    SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;

    IF layout.name IS NULL THEN
        RETURN NULL::XML;
    END IF;

    SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
    xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);

    -- Gather the bib xml
    SELECT XMLAGG( unapi.bre(i, format, '', includes, org, depth, slimit, soffset, include_xmlns, pref_lib)) INTO tmp_xml FROM UNNEST( id_list ) i;

    IF layout.title_element IS NOT NULL THEN
        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
    END IF;

    IF layout.description_element IS NOT NULL THEN
        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
    END IF;

    IF layout.creator_element IS NOT NULL THEN
        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
    END IF;

    IF layout.update_ts_element IS NOT NULL THEN
        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts;
    END IF;

    IF unapi_url IS NOT NULL THEN
        EXECUTE $$SELECT XMLCONCAT( XMLELEMENT( name link, XMLATTRIBUTES( 'http://www.w3.org/1999/xhtml' AS xmlns, 'unapi-server' AS rel, $1 AS href, 'unapi' AS title)), $2)$$ INTO tmp_xml USING unapi_url, tmp_xml::XML;
    END IF;

    IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;

    element_list := regexp_split_to_array(layout.feed_top,E'\\.');
    FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
        EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
    END LOOP;

    RETURN tmp_xml::XML;
END;

Function: unapi.bmp(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name monograph_part,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        'tag:open-ils.org:U2@bmp/' || id AS id,
                        id AS ident,
                        label,
                        label_sortkey,
                        'tag:open-ils.org:U2@bre/' || record AS record
                    ),
                    CASE 
                        WHEN ('acp' = ANY ($4)) THEN
                            XMLELEMENT( name copies,
                                (SELECT XMLAGG(acp) FROM (
                                    SELECT  unapi.acp( cp.id, 'xml', 'copy', array_remove($4,'bmp'), $5, $6, $7, $8, FALSE)
                                      FROM  asset.copy cp
                                            JOIN asset.copy_part_map cpm ON (cpm.target_copy = cp.id)
                                      WHERE cpm.part = $1
                                          AND cp.deleted IS FALSE
                                      ORDER BY COALESCE(cp.copy_number,0), cp.barcode
                                      LIMIT ($7 -> 'acp')::INT
                                      OFFSET ($8 -> 'acp')::INT

                                )x)
                            )
                        ELSE NULL
                    END,
                    CASE WHEN ('bre' = ANY ($4)) THEN unapi.bre( record, 'marcxml', 'record', array_remove($4,'bmp'), $5, $6, $7, $8, FALSE) ELSE NULL END
                )
          FROM  biblio.monograph_part
          WHERE NOT deleted AND id = $1
          GROUP BY id, label, label_sortkey, record;

Function: unapi.bre(pref_lib bigint, include_xmlns text, soffset text, slimit text[], depth text, org integer, includes public.hstore, ename public.hstore, format boolean, obj_id integer)

Returns: xml

Language: PLPGSQL

DECLARE
    me      biblio.record_entry%ROWTYPE;
    layout  unapi.bre_output_layout%ROWTYPE;
    xfrm    config.xml_transform%ROWTYPE;
    ouid    INT;
    tmp_xml TEXT;
    top_el  TEXT;
    output  XML;
    hxml    XML;
    axml    XML;
    source  XML;
BEGIN

    IF org = '-' OR org IS NULL THEN
        SELECT shortname INTO org FROM evergreen.org_top();
    END IF;

    SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;

    IF ouid IS NULL THEN
        RETURN NULL::XML;
    END IF;

    IF format = 'holdings_xml' THEN -- the special case
        output := unapi.holdings_xml( obj_id, ouid, org, depth, includes, slimit, soffset, include_xmlns);
        RETURN output;
    END IF;

    SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;

    IF layout.name IS NULL THEN
        RETURN NULL::XML;
    END IF;

    SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;

    SELECT * INTO me FROM biblio.record_entry WHERE id = obj_id;

    -- grab bib_source, if any
    IF ('cbs' = ANY (includes) AND me.source IS NOT NULL) THEN
        source := unapi.cbs(me.source,NULL,NULL,NULL,NULL);
    ELSE
        source := NULL::XML;
    END IF;

    -- grab SVF if we need them
    IF ('mra' = ANY (includes)) THEN 
        axml := unapi.mra(obj_id,NULL,NULL,NULL,NULL);
    ELSE
        axml := NULL::XML;
    END IF;

    -- grab holdings if we need them
    IF ('holdings_xml' = ANY (includes)) THEN 
        hxml := unapi.holdings_xml(obj_id, ouid, org, depth, array_remove(includes,'holdings_xml'), slimit, soffset, include_xmlns, pref_lib);
    ELSE
        hxml := NULL::XML;
    END IF;


    -- generate our item node


    IF format = 'marcxml' THEN
        tmp_xml := me.marc;
        IF tmp_xml !~ E'<marc:' THEN -- If we're not using the prefixed namespace in this record, then remove all declarations of it
           tmp_xml := REGEXP_REPLACE(tmp_xml, ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
        END IF; 
    ELSE
        tmp_xml := oils_xslt_process(me.marc, xfrm.xslt)::XML;
    END IF;

    top_el := REGEXP_REPLACE(tmp_xml, E'^.*?<((?:\\S+:)?' || layout.holdings_element || ').*$', E'\\1');

    IF source IS NOT NULL THEN
        tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', source || '</' || top_el || E'>\\1');
    END IF;

    IF axml IS NOT NULL THEN 
        tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
    END IF;

    IF hxml IS NOT NULL THEN -- XXX how do we configure the holdings position?
        tmp_xml := REGEXP_REPLACE(tmp_xml, '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
    END IF;

    IF ('bre.unapi' = ANY (includes)) THEN 
        output := REGEXP_REPLACE(
            tmp_xml,
            '</' || top_el || '>(.*?)',
            XMLELEMENT(
                name abbr,
                XMLATTRIBUTES(
                    'http://www.w3.org/1999/xhtml' AS xmlns,
                    'unapi-id' AS class,
                    'tag:open-ils.org:U2@bre/' || obj_id || '/' || org AS title
                )
            )::TEXT || '</' || top_el || E'>\\1'
        );
    ELSE
        output := tmp_xml;
    END IF;

    IF ('bre.extern' = ANY (includes)) THEN 
        output := REGEXP_REPLACE(
            tmp_xml,
            '</' || top_el || '>(.*?)',
            XMLELEMENT(
                name extern,
                XMLATTRIBUTES(
                    'http://open-ils.org/spec/biblio/v1' AS xmlns,
                    me.creator AS creator,
                    me.editor AS editor,
                    me.create_date AS create_date,
                    me.edit_date AS edit_date,
                    me.quality AS quality,
                    me.fingerprint AS fingerprint,
                    me.tcn_source AS tcn_source,
                    me.tcn_value AS tcn_value,
                    me.owner AS owner,
                    me.share_depth AS share_depth,
                    me.active AS active,
                    me.deleted AS deleted
                )
            )::TEXT || '</' || top_el || E'>\\1'
        );
    ELSE
        output := tmp_xml;
    END IF;

    output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
    RETURN output;
END;

Function: unapi.cbs(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
                name bib_source,
                XMLATTRIBUTES(
                    NULL AS xmlns, -- TODO needs equivalent to http://open-ils.org/spec/holdings/v1
                    id AS ident,
                    quality,
                    transcendant,
                    can_have_copies
                ),
                source
            )
      FROM  config.bib_source
      WHERE id = $1;

Function: unapi.ccs(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
                name status,
                XMLATTRIBUTES(
                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                    id AS ident,
                    holdable,
                    opac_visible
                ),
                name
            )
      FROM  config.copy_status
      WHERE id = $1;

Function: unapi.circ(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT XMLELEMENT(
        name circ,
        XMLATTRIBUTES(
            CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
            'tag:open-ils.org:U2@circ/' || id AS id,
            xact_start,
            due_date
        ),
        CASE WHEN ('aou' = ANY ($4)) THEN unapi.aou( circ_lib, $2, 'circ_lib', array_remove($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END,
        CASE WHEN ('acp' = ANY ($4)) THEN unapi.acp( circ_lib, $2, 'target_copy', array_remove($4,'circ'), $5, $6, $7, $8, FALSE) ELSE NULL END
    )
    FROM action.circulation
    WHERE id = $1;

Function: unapi.holdings_xml(pref_lib bigint, include_xmlns integer, soffset text, slimit integer, includes text[], depth public.hstore, org public.hstore, ouid boolean, bid integer)

Returns: xml

Language: SQL

     SELECT  XMLELEMENT(
                 name holdings,
                 XMLATTRIBUTES(
                    CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                    CASE WHEN ('bre' = ANY ($5)) THEN 'tag:open-ils.org:U2@bre/' || $1 || '/' || $3 ELSE NULL END AS id,
                    (SELECT record_has_holdable_copy FROM asset.record_has_holdable_copy($1)) AS has_holdable
                 ),
                 XMLELEMENT(
                     name counts,
                     (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
                         SELECT  XMLELEMENT(
                                     name count,
                                     XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
                                 )::text
                           FROM  asset.opac_ou_record_copy_count($2,  $1)
                                     UNION
                         SELECT  XMLELEMENT(
                                     name count,
                                     XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
                                 )::text
                           FROM  asset.staff_ou_record_copy_count($2, $1)
                                     UNION
                         SELECT  XMLELEMENT(
                                     name count,
                                     XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
                                 )::text
                           FROM  asset.opac_ou_record_copy_count($9,  $1)
                                     ORDER BY 1
                     )x)
                 ),
                 CASE 
                     WHEN ('bmp' = ANY ($5)) THEN
                        XMLELEMENT(
                            name monograph_parts,
                            (SELECT XMLAGG(bmp) FROM (
                                SELECT  unapi.bmp( id, 'xml', 'monograph_part', array_remove( array_remove($5,'bre'), 'holdings_xml'), $3, $4, $6, $7, FALSE)
                                  FROM  biblio.monograph_part
                                  WHERE NOT deleted AND record = $1
                            )x)
                        )
                     ELSE NULL
                 END,
                 XMLELEMENT(
                     name volumes,
                     (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
                        -- Physical copies
                        SELECT  unapi.acn(y.id,'xml','volume',array_remove( array_remove($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
                        FROM evergreen.ranked_volumes($1, $2, $4, $6, $7, $9, $5) AS y
                        UNION ALL
                        -- Located URIs
                        SELECT unapi.acn(uris.id,'xml','volume',array_remove( array_remove($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), uris.rank, name, label_sortkey
                        FROM evergreen.located_uris($1, $2, $9) AS uris
                     )x)
                 ),
                 CASE WHEN ('ssub' = ANY ($5)) THEN 
                     XMLELEMENT(
                         name subscriptions,
                         (SELECT XMLAGG(ssub) FROM (
                            SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
                              FROM  serial.subscription
                              WHERE record_entry = $1
                        )x)
                     )
                 ELSE NULL END,
                 CASE WHEN ('acp' = ANY ($5)) THEN 
                     XMLELEMENT(
                         name foreign_copies,
                         (SELECT XMLAGG(acp) FROM (
                            SELECT  unapi.acp(p.target_copy,'xml','copy',array_remove($5,'acp'), $3, $4, $6, $7, FALSE)
                              FROM  biblio.peer_bib_copy_map p
                                    JOIN asset.copy c ON (p.target_copy = c.id)
                              WHERE NOT c.deleted AND p.peer_record = $1
                            LIMIT ($6 -> 'acp')::INT
                            OFFSET ($7 -> 'acp')::INT
                        )x)
                     )
                 ELSE NULL END
             );

Function: unapi.memoize(include_xmlns text, soffset bigint, slimit text, depth text, org text[], includes text, ename integer, format public.hstore, obj_id public.hstore, classname boolean)

Returns: xml

Language: PLPGSQL

DECLARE
    key     TEXT;
    output  XML;
BEGIN
    key :=
        'id'        || COALESCE(obj_id::TEXT,'') ||
        'format'    || COALESCE(format::TEXT,'') ||
        'ename'     || COALESCE(ename::TEXT,'') ||
        'includes'  || COALESCE(includes::TEXT,'{}'::TEXT[]::TEXT) ||
        'org'       || COALESCE(org::TEXT,'') ||
        'depth'     || COALESCE(depth::TEXT,'') ||
        'slimit'    || COALESCE(slimit::TEXT,'') ||
        'soffset'   || COALESCE(soffset::TEXT,'') ||
        'include_xmlns'   || COALESCE(include_xmlns::TEXT,'');
    -- RAISE NOTICE 'memoize key: %', key;

    key := MD5(key);
    -- RAISE NOTICE 'memoize hash: %', key;

    -- XXX cache logic ... memcached? table?

    EXECUTE $$SELECT unapi.$$ || classname || $$( $1, $2, $3, $4, $5, $6, $7, $8, $9);$$ INTO output USING obj_id, format, ename, includes, org, depth, slimit, soffset, include_xmlns;
    RETURN output;
END;

Function: unapi.metabib_virtual_record_feed(pref_lib bigint[], header_xml text, unapi_url text[], update_ts text, creator integer, description public.hstore, title public.hstore, include_xmlns boolean, soffset text, slimit text, depth text, org text, includes text, format xml, id_list integer)

Returns: xml

Language: PLPGSQL

DECLARE
    layout          unapi.bre_output_layout%ROWTYPE;
    transform       config.xml_transform%ROWTYPE;
    item_format     TEXT;
    tmp_xml         TEXT;
    xmlns_uri       TEXT := 'http://open-ils.org/spec/feed-xml/v1';
    ouid            INT;
    element_list    TEXT[];
BEGIN

    IF org = '-' OR org IS NULL THEN
        SELECT shortname INTO org FROM evergreen.org_top();
    END IF;

    SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;
    SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;

    IF layout.name IS NULL THEN
        RETURN NULL::XML;
    END IF;

    SELECT * INTO transform FROM config.xml_transform WHERE name = layout.transform;
    xmlns_uri := COALESCE(transform.namespace_uri,xmlns_uri);

    -- Gather the bib xml
    SELECT XMLAGG( unapi.mmr(i, format, '', includes, org, depth, slimit, soffset, include_xmlns, pref_lib)) INTO tmp_xml FROM UNNEST( id_list ) i;

    IF layout.title_element IS NOT NULL THEN
        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.title_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, title;
    END IF;

    IF layout.description_element IS NOT NULL THEN
        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.description_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, description;
    END IF;

    IF layout.creator_element IS NOT NULL THEN
        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.creator_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, creator;
    END IF;

    IF layout.update_ts_element IS NOT NULL THEN
        EXECUTE 'SELECT XMLCONCAT( XMLELEMENT( name '|| layout.update_ts_element ||', XMLATTRIBUTES( $1 AS xmlns), $3), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML, update_ts;
    END IF;

    IF unapi_url IS NOT NULL THEN
        EXECUTE $$SELECT XMLCONCAT( XMLELEMENT( name link, XMLATTRIBUTES( 'http://www.w3.org/1999/xhtml' AS xmlns, 'unapi-server' AS rel, $1 AS href, 'unapi' AS title)), $2)$$ INTO tmp_xml USING unapi_url, tmp_xml::XML;
    END IF;

    IF header_xml IS NOT NULL THEN tmp_xml := XMLCONCAT(header_xml,tmp_xml::XML); END IF;

    element_list := regexp_split_to_array(layout.feed_top,E'\\.');
    FOR i IN REVERSE ARRAY_UPPER(element_list, 1) .. 1 LOOP
        EXECUTE 'SELECT XMLELEMENT( name '|| quote_ident(element_list[i]) ||', XMLATTRIBUTES( $1 AS xmlns), $2)' INTO tmp_xml USING xmlns_uri, tmp_xml::XML;
    END LOOP;

    RETURN tmp_xml::XML;
END;

Function: unapi.mmr(pref_lib bigint, include_xmlns text, soffset text, slimit text[], depth text, org integer, includes public.hstore, ename public.hstore, format boolean, obj_id integer)

Returns: xml

Language: PLPGSQL

DECLARE
    mmrec   metabib.metarecord%ROWTYPE;
    leadrec biblio.record_entry%ROWTYPE;
    subrec biblio.record_entry%ROWTYPE;
    layout  unapi.bre_output_layout%ROWTYPE;
    xfrm    config.xml_transform%ROWTYPE;
    ouid    INT;
    xml_buf TEXT; -- growing XML document
    tmp_xml TEXT; -- single-use XML string
    xml_frag TEXT; -- single-use XML fragment
    top_el  TEXT;
    output  XML;
    hxml    XML;
    axml    XML;
    subxml  XML; -- subordinate records elements
    sub_xpath TEXT; 
    parts   TEXT[]; 
BEGIN

    -- xpath for extracting bre.marc values from subordinate records 
    -- so they may be appended to the MARC of the master record prior
    -- to XSLT processing.
    -- subjects, isbn, issn, upc -- anything else?
    sub_xpath := 
      '//*[starts-with(@tag, "6") or @tag="020" or @tag="022" or @tag="024"]';

    IF org = '-' OR org IS NULL THEN
        SELECT shortname INTO org FROM evergreen.org_top();
    END IF;

    SELECT id INTO ouid FROM actor.org_unit WHERE shortname = org;

    IF ouid IS NULL THEN
        RETURN NULL::XML;
    END IF;

    SELECT INTO mmrec * FROM metabib.metarecord WHERE id = obj_id;
    IF NOT FOUND THEN
        RETURN NULL::XML;
    END IF;

    -- TODO: aggregate holdings from constituent records
    IF format = 'holdings_xml' THEN -- the special case
        output := unapi.mmr_holdings_xml(
            obj_id, ouid, org, depth,
            array_remove(includes,'holdings_xml'),
            slimit, soffset, include_xmlns, pref_lib);
        RETURN output;
    END IF;

    SELECT * INTO layout FROM unapi.bre_output_layout WHERE name = format;

    IF layout.name IS NULL THEN
        RETURN NULL::XML;
    END IF;

    SELECT * INTO xfrm FROM config.xml_transform WHERE name = layout.transform;

    SELECT INTO leadrec * FROM biblio.record_entry WHERE id = mmrec.master_record;

    -- Grab distinct MVF for all records if requested
    IF ('mra' = ANY (includes)) THEN 
        axml := unapi.mmr_mra(obj_id,NULL,NULL,NULL,org,depth,NULL,NULL,TRUE,pref_lib);
    ELSE
        axml := NULL::XML;
    END IF;

    xml_buf = leadrec.marc;

    hxml := NULL::XML;
    IF ('holdings_xml' = ANY (includes)) THEN
        hxml := unapi.mmr_holdings_xml(
                    obj_id, ouid, org, depth,
                    array_remove(includes,'holdings_xml'),
                    slimit, soffset, include_xmlns, pref_lib);
    END IF;

    subxml := NULL::XML;
    parts := '{}'::TEXT[];
    FOR subrec IN SELECT bre.* FROM biblio.record_entry bre
         JOIN metabib.metarecord_source_map mmsm ON (mmsm.source = bre.id)
         JOIN metabib.metarecord mmr ON (mmr.id = mmsm.metarecord)
         WHERE mmr.id = obj_id AND NOT bre.deleted
         ORDER BY CASE WHEN bre.id = mmr.master_record THEN 0 ELSE bre.id END
         LIMIT COALESCE((slimit->'bre')::INT, 5) LOOP

        IF subrec.id = leadrec.id THEN CONTINUE; END IF;
        -- Append choice data from the the non-lead records to the 
        -- the lead record document

        parts := parts || xpath(sub_xpath, subrec.marc::XML)::TEXT[];
    END LOOP;

    SELECT STRING_AGG( DISTINCT p , '' )::XML INTO subxml FROM UNNEST(parts) p;

    -- append data from the subordinate records to the 
    -- main record document before applying the XSLT

    IF subxml IS NOT NULL THEN 
        xml_buf := REGEXP_REPLACE(xml_buf, 
            '</record>(.*?)$', subxml || '</record>' || E'\\1');
    END IF;

    IF format = 'marcxml' THEN
         -- If we're not using the prefixed namespace in 
         -- this record, then remove all declarations of it
        IF xml_buf !~ E'<marc:' THEN
           xml_buf := REGEXP_REPLACE(xml_buf, 
            ' xmlns:marc="http://www.loc.gov/MARC21/slim"', '', 'g');
        END IF; 
    ELSE
        xml_buf := oils_xslt_process(xml_buf, xfrm.xslt)::XML;
    END IF;

    -- update top_el to reflect the change in xml_buf, which may
    -- now be a different type of document (e.g. record -> mods)
    top_el := REGEXP_REPLACE(xml_buf, E'^.*?<((?:\\S+:)?' || 
        layout.holdings_element || ').*$', E'\\1');

    IF axml IS NOT NULL THEN 
        xml_buf := REGEXP_REPLACE(xml_buf, 
            '</' || top_el || '>(.*?)$', axml || '</' || top_el || E'>\\1');
    END IF;

    IF hxml IS NOT NULL THEN
        xml_buf := REGEXP_REPLACE(xml_buf, 
            '</' || top_el || '>(.*?)$', hxml || '</' || top_el || E'>\\1');
    END IF;

    IF ('mmr.unapi' = ANY (includes)) THEN 
        output := REGEXP_REPLACE(
            xml_buf,
            '</' || top_el || '>(.*?)',
            XMLELEMENT(
                name abbr,
                XMLATTRIBUTES(
                    'http://www.w3.org/1999/xhtml' AS xmlns,
                    'unapi-id' AS class,
                    'tag:open-ils.org:U2@mmr/' || obj_id || '/' || org AS title
                )
            )::TEXT || '</' || top_el || E'>\\1'
        );
    ELSE
        output := xml_buf;
    END IF;

    -- remove ignorable whitesace
    output := REGEXP_REPLACE(output::TEXT,E'>\\s+<','><','gs')::XML;
    RETURN output;
END;

Function: unapi.mmr_holdings_xml(pref_lib bigint, include_xmlns integer, soffset text, slimit integer, includes text[], depth public.hstore, org public.hstore, ouid boolean, mid integer)

Returns: xml

Language: SQL

     SELECT  XMLELEMENT(
                 name holdings,
                 XMLATTRIBUTES(
                    CASE WHEN $8 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                    CASE WHEN ('mmr' = ANY ($5)) THEN 'tag:open-ils.org:U2@mmr/' || $1 || '/' || $3 ELSE NULL END AS id,
                    (SELECT metarecord_has_holdable_copy FROM asset.metarecord_has_holdable_copy($1)) AS has_holdable
                 ),
                 XMLELEMENT(
                     name counts,
                     (SELECT  XMLAGG(XMLELEMENT::XML) FROM (
                         SELECT  XMLELEMENT(
                                     name count,
                                     XMLATTRIBUTES('public' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
                                 )::text
                           FROM  asset.opac_ou_metarecord_copy_count($2,  $1)
                                     UNION
                         SELECT  XMLELEMENT(
                                     name count,
                                     XMLATTRIBUTES('staff' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
                                 )::text
                           FROM  asset.staff_ou_metarecord_copy_count($2, $1)
                                     UNION
                         SELECT  XMLELEMENT(
                                     name count,
                                     XMLATTRIBUTES('pref_lib' as type, depth, org_unit, coalesce(transcendant,0) as transcendant, available, visible as count, unshadow)
                                 )::text
                           FROM  asset.opac_ou_metarecord_copy_count($9,  $1)
                                     ORDER BY 1
                     )x)
                 ),
                 -- XXX monograph_parts and foreign_copies are skipped in MRs ... put them back some day?
                 XMLELEMENT(
                     name volumes,
                     (SELECT XMLAGG(acn ORDER BY rank, name, label_sortkey) FROM (
                        -- Physical copies
                        SELECT  unapi.acn(y.id,'xml','volume',array_remove( array_remove($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), y.rank, name, label_sortkey
                        FROM evergreen.ranked_volumes((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $4, $6, $7, $9, $5) AS y
                        UNION ALL
                        -- Located URIs
                        SELECT unapi.acn(uris.id,'xml','volume',array_remove( array_remove($5,'holdings_xml'),'bre'), $3, $4, $6, $7, FALSE), uris.rank, name, label_sortkey
                        FROM evergreen.located_uris((SELECT ARRAY_AGG(source) FROM metabib.metarecord_source_map WHERE metarecord = $1), $2, $9) AS uris
                     )x)
                 ),
                 CASE WHEN ('ssub' = ANY ($5)) THEN
                     XMLELEMENT(
                         name subscriptions,
                         (SELECT XMLAGG(ssub) FROM (
                            SELECT  unapi.ssub(id,'xml','subscription','{}'::TEXT[], $3, $4, $6, $7, FALSE)
                              FROM  serial.subscription
                              WHERE record_entry IN (SELECT source FROM metabib.metarecord_source_map WHERE metarecord = $1)
                        )x)
                     )
                 ELSE NULL END
             );

Function: unapi.mmr_mra(pref_lib bigint, include_xmlns text, soffset text, slimit text[], depth text, org integer, includes public.hstore, ename public.hstore, format boolean, obj_id integer)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
        name attributes,
        XMLATTRIBUTES(
            CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
            'tag:open-ils.org:U2@mmr/' || $1 AS metarecord
        ),
        (SELECT XMLAGG(foo.y)
          FROM (
            WITH sourcelist AS (
                WITH aou AS (SELECT COALESCE(id, (evergreen.org_top()).id) AS id FROM actor.org_unit WHERE shortname = $5 LIMIT 1),
                     basevm AS (SELECT c_attrs FROM  asset.patron_default_visibility_mask()),
                     circvm AS (SELECT search.calculate_visibility_attribute_test('circ_lib', ARRAY_AGG(aoud.id)) AS mask
                                  FROM aou, LATERAL actor.org_unit_descendants(aou.id, $6) aoud)
                SELECT  source
                  FROM  aou, circvm, basevm, metabib.metarecord_source_map mmsm
                  WHERE mmsm.metarecord = $1 AND (
                    EXISTS (
                        SELECT  1
                          FROM  circvm, basevm, asset.copy_vis_attr_cache acvac
                          WHERE acvac.vis_attr_vector @@ (basevm.c_attrs || '&' || circvm.mask)::query_int
                                AND acvac.record = mmsm.source
                    )
                    OR EXISTS (SELECT 1 FROM evergreen.located_uris(source, aou.id, $10) LIMIT 1)
                    OR EXISTS (SELECT 1 FROM biblio.record_entry b JOIN config.bib_source src ON (b.source = src.id) WHERE src.transcendant AND b.id = mmsm.source)
                )
            )
            SELECT  cmra.aid,
                    XMLELEMENT(
                        name field,
                        XMLATTRIBUTES(
                            cmra.attr AS name,
                            cmra.value AS "coded-value",
                            cmra.aid AS "cvmid",
                            rad.composite,
                            rad.multi,
                            rad.filter,
                            rad.sorter,
                            cmra.source_list
                        ),
                        cmra.value
                    )
              FROM  (
                SELECT DISTINCT aid, attr, value, STRING_AGG(x.id::TEXT, ',') AS source_list
                  FROM (
                    SELECT  v.source AS id,
                            c.id AS aid,
                            c.ctype AS attr,
                            c.code AS value
                      FROM  metabib.record_attr_vector_list v
                            JOIN config.coded_value_map c ON ( c.id = ANY( v.vlist ) )
                    ) AS x
                    JOIN sourcelist ON (x.id = sourcelist.source)
                    GROUP BY 1, 2, 3
                ) AS cmra
                JOIN config.record_attr_definition rad ON (cmra.attr = rad.name)
                UNION ALL
            SELECT  umra.aid,
                    XMLELEMENT(
                        name field,
                        XMLATTRIBUTES(
                            umra.attr AS name,
                            rad.composite,
                            rad.multi,
                            rad.filter,
                            rad.sorter
                        ),
                        umra.value
                    )
              FROM  (
                SELECT DISTINCT aid, attr, value
                  FROM (
                    SELECT  v.source AS id,
                            m.id AS aid,
                            m.attr AS attr,
                            m.value AS value
                      FROM  metabib.record_attr_vector_list v
                            JOIN metabib.uncontrolled_record_attr_value m ON ( m.id = ANY( v.vlist ) )
                    ) AS x
                    JOIN sourcelist ON (x.id = sourcelist.source)
                ) AS umra
                JOIN config.record_attr_definition rad ON (umra.attr = rad.name)
                ORDER BY 1

            )foo(id,y)
        )
    )

Function: unapi.mra(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
        name attributes,
        XMLATTRIBUTES(
            CASE WHEN $9 THEN 'http://open-ils.org/spec/indexing/v1' ELSE NULL END AS xmlns,
            'tag:open-ils.org:U2@mra/' || $1 AS id, 
            'tag:open-ils.org:U2@bre/' || $1 AS record 
        ),  
        (SELECT XMLAGG(foo.y)
          FROM (
            SELECT  XMLELEMENT(
                        name field,
                        XMLATTRIBUTES(
                            mra.attr AS name,
                            cvm.value AS "coded-value",
                            cvm.id AS "cvmid",
                            rad.composite,
                            rad.multi,
                            rad.filter,
                            rad.sorter
                        ),
                        mra.value
                    )
              FROM  metabib.record_attr_flat mra
                    JOIN config.record_attr_definition rad ON (mra.attr = rad.name)
                    LEFT JOIN config.coded_value_map cvm ON (cvm.ctype = mra.attr AND code = mra.value)
              WHERE mra.id = $1
            )foo(y)
        )   
    )   

Function: unapi.sbsum(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
                name serial_summary,
                XMLATTRIBUTES(
                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                    'tag:open-ils.org:U2@sbsum/' || id AS id,
                    'sbsum' AS type, generated_coverage, textual_holdings, show_generated
                ),
                CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
            )
      FROM  serial.basic_summary ssum
      WHERE id = $1
      GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;

Function: unapi.sdist(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name distribution,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        'tag:open-ils.org:U2@sdist/' || id AS id,
            			'tag:open-ils.org:U2@acn/' || receive_call_number AS receive_call_number,
			            'tag:open-ils.org:U2@acn/' || bind_call_number AS bind_call_number,
                        unit_label_prefix, label, unit_label_suffix, summary_method
                    ),
                    unapi.aou( holding_lib, $2, 'holding_lib', array_remove($4,'sdist'), $5, $6, $7, $8),
                    CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE) ELSE NULL END,
                    CASE 
                        WHEN ('sstr' = ANY ($4)) THEN
                            XMLELEMENT( name streams,
                                (SELECT XMLAGG(sstr) FROM (
                                    SELECT  unapi.sstr( id, 'xml', 'stream', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE)
                                      FROM  serial.stream
                                      WHERE distribution = sdist.id
                                )x)
                            )
                        ELSE NULL
                    END,
                    XMLELEMENT( name summaries,
                        CASE 
                            WHEN ('sbsum' = ANY ($4)) THEN
                                (SELECT XMLAGG(sbsum) FROM (
                                    SELECT  unapi.sbsum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE)
                                      FROM  serial.basic_summary
                                      WHERE distribution = sdist.id
                                )x)
                            ELSE NULL
                        END,
                        CASE 
                            WHEN ('sisum' = ANY ($4)) THEN
                                (SELECT XMLAGG(sisum) FROM (
                                    SELECT  unapi.sisum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE)
                                      FROM  serial.index_summary
                                      WHERE distribution = sdist.id
                                )x)
                            ELSE NULL
                        END,
                        CASE 
                            WHEN ('sssum' = ANY ($4)) THEN
                                (SELECT XMLAGG(sssum) FROM (
                                    SELECT  unapi.sssum( id, 'xml', 'serial_summary', array_remove($4,'sdist'), $5, $6, $7, $8, FALSE)
                                      FROM  serial.supplement_summary
                                      WHERE distribution = sdist.id
                                )x)
                            ELSE NULL
                        END
                    )
                )
          FROM  serial.distribution sdist
          WHERE id = $1
          GROUP BY id, label, unit_label_prefix, unit_label_suffix, holding_lib, summary_method, subscription, receive_call_number, bind_call_number;

Function: unapi.siss(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
                name issuance,
                XMLATTRIBUTES(
                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                    'tag:open-ils.org:U2@siss/' || id AS id,
                    create_date, edit_date, label, date_published,
                    holding_code, holding_type, holding_link_id
                ),
                CASE WHEN subscription IS NOT NULL AND ('ssub' = ANY ($4)) THEN unapi.ssub( subscription, 'xml', 'subscription', array_remove($4,'siss'), $5, $6, $7, $8, FALSE) ELSE NULL END,
                CASE 
                    WHEN ('sitem' = ANY ($4)) THEN
                        XMLELEMENT( name items,
                            (SELECT XMLAGG(sitem) FROM (
                                SELECT  unapi.sitem( id, 'xml', 'serial_item', array_remove($4,'siss'), $5, $6, $7, $8, FALSE)
                                  FROM  serial.item
                                  WHERE issuance = sstr.id
                            )x)
                        )
                    ELSE NULL
                END
            )
      FROM  serial.issuance sstr
      WHERE id = $1
      GROUP BY id, create_date, edit_date, label, date_published, holding_code, holding_type, holding_link_id, subscription;

Function: unapi.sisum(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
                name serial_summary,
                XMLATTRIBUTES(
                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                    'tag:open-ils.org:U2@sbsum/' || id AS id,
                    'sisum' AS type, generated_coverage, textual_holdings, show_generated
                ),
                CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
            )
      FROM  serial.index_summary ssum
      WHERE id = $1
      GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;

Function: unapi.sitem(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name serial_item,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        'tag:open-ils.org:U2@sitem/' || id AS id,
                        'tag:open-ils.org:U2@siss/' || issuance AS issuance,
                        date_expected, date_received
                    ),
                    CASE WHEN issuance IS NOT NULL AND ('siss' = ANY ($4)) THEN unapi.siss( issuance, $2, 'issuance', array_remove($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
                    CASE WHEN stream IS NOT NULL AND ('sstr' = ANY ($4)) THEN unapi.sstr( stream, $2, 'stream', array_remove($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
                    CASE WHEN unit IS NOT NULL AND ('sunit' = ANY ($4)) THEN unapi.sunit( unit, $2, 'serial_unit', array_remove($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END,
                    CASE WHEN uri IS NOT NULL AND ('auri' = ANY ($4)) THEN unapi.auri( uri, $2, 'uri', array_remove($4,'sitem'), $5, $6, $7, $8, FALSE) ELSE NULL END
--                    XMLELEMENT( name notes,
--                        CASE 
--                            WHEN ('acpn' = ANY ($4)) THEN
--                                (SELECT XMLAGG(acpn) FROM (
--                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', array_remove($4,'acp'), $5, $6, $7, $8)
--                                      FROM  asset.copy_note
--                                      WHERE owning_copy = cp.id AND pub
--                                )x)
--                            ELSE NULL
--                        END
--                    )
                )
          FROM  serial.item sitem
          WHERE id = $1;

Function: unapi.sssum(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
                name serial_summary,
                XMLATTRIBUTES(
                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                    'tag:open-ils.org:U2@sbsum/' || id AS id,
                    'sssum' AS type, generated_coverage, textual_holdings, show_generated
                ),
                CASE WHEN ('sdist' = ANY ($4)) THEN unapi.sdist( distribution, 'xml', 'distribtion', array_remove($4,'ssum'), $5, $6, $7, $8, FALSE) ELSE NULL END
            )
      FROM  serial.supplement_summary ssum
      WHERE id = $1
      GROUP BY id, generated_coverage, textual_holdings, distribution, show_generated;

Function: unapi.sstr(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

    SELECT  XMLELEMENT(
                name stream,
                XMLATTRIBUTES(
                    CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                    'tag:open-ils.org:U2@sstr/' || id AS id,
                    routing_label
                ),
                CASE WHEN distribution IS NOT NULL AND ('sdist' = ANY ($4)) THEN unapi.sssum( distribution, 'xml', 'distribtion', array_remove($4,'sstr'), $5, $6, $7, $8, FALSE) ELSE NULL END,
                CASE 
                    WHEN ('sitem' = ANY ($4)) THEN
                        XMLELEMENT( name items,
                            (SELECT XMLAGG(sitem) FROM (
                                SELECT  unapi.sitem( id, 'xml', 'serial_item', array_remove($4,'sstr'), $5, $6, $7, $8, FALSE)
                                  FROM  serial.item
                                  WHERE stream = sstr.id
                            )x)
                        )
                    ELSE NULL
                END
            )
      FROM  serial.stream sstr
      WHERE id = $1
      GROUP BY id, routing_label, distribution;

Function: unapi.ssub(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name subscription,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        'tag:open-ils.org:U2@ssub/' || id AS id,
                        'tag:open-ils.org:U2@aou/' || owning_lib AS owning_lib,
                        start_date AS start, end_date AS end, expected_date_offset
                    ),
                    CASE 
                        WHEN ('sdist' = ANY ($4)) THEN
                            XMLELEMENT( name distributions,
                                (SELECT XMLAGG(sdist) FROM (
                                    SELECT  unapi.sdist( id, 'xml', 'distribution', array_remove($4,'ssub'), $5, $6, $7, $8, FALSE)
                                      FROM  serial.distribution
                                      WHERE subscription = ssub.id
                                )x)
                            )
                        ELSE NULL
                    END
                )
          FROM  serial.subscription ssub
          WHERE id = $1
          GROUP BY id, start_date, end_date, expected_date_offset, owning_lib;

Function: unapi.sunit(include_xmlns bigint, soffset text, slimit text, depth text[], org text, includes integer, ename public.hstore, format public.hstore, obj_id boolean)

Returns: xml

Language: SQL

        SELECT  XMLELEMENT(
                    name serial_unit,
                    XMLATTRIBUTES(
                        CASE WHEN $9 THEN 'http://open-ils.org/spec/holdings/v1' ELSE NULL END AS xmlns,
                        'tag:open-ils.org:U2@acp/' || id AS id, id AS copy_id,
                        create_date, edit_date, copy_number, circulate, deposit,
                        ref, holdable, deleted, deposit_amount, price, barcode,
                        circ_modifier, circ_as_type, opac_visible, age_protect,
                        status_changed_time, floating, mint_condition,
                        detailed_contents, sort_key, summary_contents, cost 
                    ),
                    unapi.ccs( status, $2, 'status', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
                    unapi.acl( location, $2, 'location', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE),
                    unapi.aou( circ_lib, $2, 'circ_lib', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8),
                    unapi.aou( circ_lib, $2, 'circlib', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8),
                    CASE WHEN ('acn' = ANY ($4)) THEN unapi.acn( call_number, $2, 'call_number', array_remove($4,'acp'), $5, $6, $7, $8, FALSE) ELSE NULL END,
                    XMLELEMENT( name copy_notes,
                        CASE 
                            WHEN ('acpn' = ANY ($4)) THEN
                                (SELECT XMLAGG(acpn) FROM (
                                    SELECT  unapi.acpn( id, 'xml', 'copy_note', array_remove( array_remove($4,'acp'),'sunit'), $5, $6, $7, $8, FALSE)
                                      FROM  asset.copy_note
                                      WHERE owning_copy = cp.id AND pub
                                )x)
                            ELSE NULL
                        END
                    ),
                    XMLELEMENT( name statcats,
                        CASE 
                            WHEN ('ascecm' = ANY ($4)) THEN
                                (SELECT XMLAGG(ascecm) FROM (
                                    SELECT  unapi.ascecm( stat_cat_entry, 'xml', 'statcat', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
                                      FROM  asset.stat_cat_entry_copy_map
                                      WHERE owning_copy = cp.id
                                )x)
                            ELSE NULL
                        END
                    ),
                    XMLELEMENT( name foreign_records,
                        CASE
                            WHEN ('bre' = ANY ($4)) THEN
                                (SELECT XMLAGG(bre) FROM (
                                    SELECT  unapi.bre(peer_record,'marcxml','record','{}'::TEXT[], $5, $6, $7, $8, FALSE)
                                      FROM  biblio.peer_bib_copy_map
                                      WHERE target_copy = cp.id
                                )x)
                            ELSE NULL
                        END
                    ),
                    CASE 
                        WHEN ('bmp' = ANY ($4)) THEN
                            XMLELEMENT( name monograph_parts,
                                (SELECT XMLAGG(bmp) FROM (
                                    SELECT  unapi.bmp( part, 'xml', 'monograph_part', array_remove($4,'acp'), $5, $6, $7, $8, FALSE)
                                      FROM  asset.copy_part_map
                                      WHERE target_copy = cp.id
                                )x)
                            )
                        ELSE NULL
                    END,
                    CASE 
                        WHEN ('circ' = ANY ($4)) THEN
                            XMLELEMENT( name current_circulation,
                                (SELECT XMLAGG(circ) FROM (
                                    SELECT  unapi.circ( id, 'xml', 'circ', array_remove($4,'circ'), $5, $6, $7, $8, FALSE)
                                      FROM  action.circulation
                                      WHERE target_copy = cp.id
                                            AND checkin_time IS NULL
                                )x)
                            )
                        ELSE NULL
                    END
                )
          FROM  serial.unit cp
          WHERE id = $1
              AND cp.deleted IS FALSE
          GROUP BY id, status, location, circ_lib, call_number, create_date,
              edit_date, copy_number, circulate, floating, mint_condition,
              deposit, ref, holdable, deleted, deposit_amount, price,
              barcode, circ_modifier, circ_as_type, opac_visible,
              status_changed_time, detailed_contents, sort_key,
              summary_contents, cost, age_protect;

Schema url_verify


Table: url_verify.session

url_verify.session Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
actor.org_unit.id owning_lib integer UNIQUE#1 NOT NULL
actor.usr.id creator integer NOT NULL
container.biblio_record_entry_bucket.id container integer NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
search text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema url_verify


Table: url_verify.url

url_verify.url Structure
F-Key Name Type Description
id serial PRIMARY KEY
url_verify.url.id redirect_from integer
container.biblio_record_entry_bucket_item.id item integer
url_verify.session.id session integer
url_verify.url_selector.id url_selector integer
tag text
subfield text
ord integer
full_url text NOT NULL
scheme text
username text
password text
host text
domain text
tld text
port text
path text
page text
query text
fragment text

 

url_verify.url Constraints
Name Constraint
redirect_or_from_item CHECK (((redirect_from IS NOT NULL) OR ((item IS NOT NULL) AND (url_selector IS NOT NULL) AND (tag IS NOT NULL) AND (subfield IS NOT NULL) AND (ord IS NOT NULL))))

Tables referencing this one via Foreign Key Constraints:

Index - Schema url_verify


Table: url_verify.url_selector

url_verify.url_selector Structure
F-Key Name Type Description
id serial PRIMARY KEY
xpath text UNIQUE#1 NOT NULL
url_verify.session.id session integer UNIQUE#1 NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema url_verify


Table: url_verify.url_verification

url_verify.url_verification Structure
F-Key Name Type Description
id serial PRIMARY KEY
url_verify.url.id url integer NOT NULL
url_verify.verification_attempt.id attempt integer NOT NULL
req_time timestamp with time zone NOT NULL DEFAULT now()
res_time timestamp with time zone
res_code integer
res_text text
url_verify.url.id redirect_to integer

 

url_verify.url_verification Constraints
Name Constraint
url_verification_res_code_check CHECK (((res_code >= 100) AND (res_code <= 999)))

Index - Schema url_verify


Table: url_verify.verification_attempt

url_verify.verification_attempt Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.usr.id usr integer NOT NULL
url_verify.session.id session integer NOT NULL
start_time timestamp with time zone NOT NULL DEFAULT now()
finish_time timestamp with time zone

Tables referencing this one via Foreign Key Constraints:

Index - Schema url_verify


Function: url_verify.extract_urls(item_id integer, session_id integer)

Returns: integer

Language: PLPGSQL

DECLARE
    last_seen_tag TEXT;
    current_tag TEXT;
    current_sf TEXT;
    current_url TEXT;
    current_ord INT;
    current_url_pos INT;
    current_selector url_verify.url_selector%ROWTYPE;
BEGIN
    current_ord := 1;

    FOR current_selector IN SELECT * FROM url_verify.url_selector s WHERE s.session = session_id LOOP
        current_url_pos := 1;
        LOOP
            SELECT  (oils_xpath(current_selector.xpath || '/text()', b.marc))[current_url_pos] INTO current_url
              FROM  biblio.record_entry b
                    JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id)
              WHERE c.id = item_id;

            EXIT WHEN current_url IS NULL;

            SELECT  (oils_xpath(current_selector.xpath || '/../@tag', b.marc))[current_url_pos] INTO current_tag
              FROM  biblio.record_entry b
                    JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id)
              WHERE c.id = item_id;

            IF current_tag IS NULL THEN
                current_tag := last_seen_tag;
            ELSE
                last_seen_tag := current_tag;
            END IF;

            SELECT  (oils_xpath(current_selector.xpath || '/@code', b.marc))[current_url_pos] INTO current_sf
              FROM  biblio.record_entry b
                    JOIN container.biblio_record_entry_bucket_item c ON (c.target_biblio_record_entry = b.id)
              WHERE c.id = item_id;

            INSERT INTO url_verify.url (session, item, url_selector, tag, subfield, ord, full_url)
              VALUES ( session_id, item_id, current_selector.id, current_tag, current_sf, current_ord, current_url);

            current_url_pos := current_url_pos + 1;
            current_ord := current_ord + 1;
        END LOOP;
    END LOOP;

    RETURN current_ord - 1;
END;

Function: url_verify.ingest_url()

Returns: trigger

Language: PLPGSQL

DECLARE
    tmp_row url_verify.url%ROWTYPE;
BEGIN
    SELECT * INTO tmp_row FROM url_verify.parse_url(NEW.full_url);

    NEW.scheme          := tmp_row.scheme;
    NEW.username        := tmp_row.username;
    NEW.password        := tmp_row.password;
    NEW.host            := tmp_row.host;
    NEW.domain          := tmp_row.domain;
    NEW.tld             := tmp_row.tld;
    NEW.port            := tmp_row.port;
    NEW.path            := tmp_row.path;
    NEW.page            := tmp_row.page;
    NEW.query           := tmp_row.query;
    NEW.fragment        := tmp_row.fragment;

    RETURN NEW;
END;

Function: url_verify.parse_url(url_in text)

Returns: url

Language: PLPERLU


use Rose::URI;

my $url_in = shift;
my $url = Rose::URI->new($url_in);

my %parts = map { $_ => $url->$_ } qw/scheme username password host port path query fragment/;

$parts{full_url} = $url_in;
($parts{domain} = $parts{host}) =~ s/^[^.]+\.//;
($parts{tld} = $parts{domain}) =~ s/(?:[^.]+\.)+//;
($parts{page} = $parts{path}) =~ s#(?:[^/]*/)+##;

return \%parts;


Schema vandelay


Table: vandelay.authority_attr_definition

vandelay.authority_attr_definition Structure
F-Key Name Type Description
id serial PRIMARY KEY
code text UNIQUE NOT NULL
description text
xpath text NOT NULL
remove text NOT NULL DEFAULT ''::text

Tables referencing this one via Foreign Key Constraints:

Index - Schema vandelay


Table: vandelay.authority_match

vandelay.authority_match Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
vandelay.queued_authority_record.id queued_record bigint
authority.record_entry.id eg_record bigint
quality integer NOT NULL
match_score integer NOT NULL

Index - Schema vandelay


Table: vandelay.authority_queue

vandelay.authority_queue Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('vandelay.queue_id_seq'::regclass)
owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
complete boolean NOT NULL DEFAULT false
match_set integer
queue_type vandelay.authority_queue_queue_type UNIQUE#1 NOT NULL DEFAULT 'authority'::vandelay.authority_queue_queue_type

Table vandelay.authority_queue Inherits queue,

Tables referencing this one via Foreign Key Constraints:

Index - Schema vandelay


Table: vandelay.bib_attr_definition

vandelay.bib_attr_definition Structure
F-Key Name Type Description
id serial PRIMARY KEY
code text UNIQUE NOT NULL
description text
xpath text NOT NULL
remove text NOT NULL DEFAULT ''::text

Tables referencing this one via Foreign Key Constraints:

Index - Schema vandelay


Table: vandelay.bib_match

vandelay.bib_match Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
vandelay.queued_bib_record.id queued_record bigint
biblio.record_entry.id eg_record bigint
quality integer NOT NULL DEFAULT 1
match_score integer NOT NULL
bib_match_queued_record_idx queued_record

Index - Schema vandelay


Table: vandelay.bib_queue

vandelay.bib_queue Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('vandelay.queue_id_seq'::regclass)
owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
complete boolean NOT NULL DEFAULT false
match_set integer
queue_type vandelay.bib_queue_queue_type UNIQUE#1 NOT NULL DEFAULT 'bib'::vandelay.bib_queue_queue_type
vandelay.import_item_attr_definition.id item_attr_def bigint
container.biblio_record_entry_bucket.id match_bucket integer

Table vandelay.bib_queue Inherits queue,

Tables referencing this one via Foreign Key Constraints:

Index - Schema vandelay


Table: vandelay.import_bib_trash_fields

vandelay.import_bib_trash_fields Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
vandelay.import_bib_trash_group.id grp integer UNIQUE#1 NOT NULL
field text UNIQUE#1 NOT NULL

Index - Schema vandelay


Table: vandelay.import_bib_trash_group

vandelay.import_bib_trash_group Structure
F-Key Name Type Description
id serial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
label text UNIQUE#1 NOT NULL
always_apply boolean NOT NULL DEFAULT false

Tables referencing this one via Foreign Key Constraints:

Index - Schema vandelay


Table: vandelay.import_error

vandelay.import_error Structure
F-Key Name Type Description
code text PRIMARY KEY
description text NOT NULL

Tables referencing this one via Foreign Key Constraints:

Index - Schema vandelay


Table: vandelay.import_item

vandelay.import_item Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
vandelay.queued_bib_record.id record bigint NOT NULL
vandelay.import_item_attr_definition.id definition bigint NOT NULL
vandelay.import_error.code import_error text
error_detail text
imported_as bigint
import_time timestamp with time zone
owning_lib integer
circ_lib integer
call_number text
copy_number integer
status integer
location integer
circulate boolean
deposit boolean
deposit_amount numeric(8,2)
ref boolean
holdable boolean
price numeric(8,2)
barcode text
circ_modifier text
circ_as_type text
alert_message text
pub_note text
priv_note text
stat_cat_data text
parts_data text
opac_visible boolean
internal_id bigint
import_item_record_idx record

Index - Schema vandelay


Table: vandelay.import_item_attr_definition

vandelay.import_item_attr_definition Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
tag text NOT NULL
keep boolean NOT NULL DEFAULT false
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_title text
pub_note text
priv_note_title text
priv_note text
internal_id text
stat_cat_data text
parts_data text

Tables referencing this one via Foreign Key Constraints:

Index - Schema vandelay


Table: vandelay.match_set

vandelay.match_set Structure
F-Key Name Type Description
id serial PRIMARY KEY
name text UNIQUE#1 NOT NULL
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
mtype text UNIQUE#1 NOT NULL DEFAULT 'biblio'::text

Tables referencing this one via Foreign Key Constraints:

Index - Schema vandelay


Table: vandelay.match_set_point

vandelay.match_set_point Structure
F-Key Name Type Description
id serial PRIMARY KEY
vandelay.match_set.id match_set integer
vandelay.match_set_point.id parent integer
bool_op text
config.record_attr_definition.name svf text
tag text
subfield text
negate boolean DEFAULT false
quality integer NOT NULL DEFAULT 1
heading boolean NOT NULL DEFAULT false

 

vandelay.match_set_point Constraints
Name Constraint
match_set_point_bool_op_check CHECK (((bool_op IS NULL) OR (bool_op = ANY (ARRAY['AND'::text, 'OR'::text, 'NOT'::text]))))
vmsp_need_a_subfield_with_a_tag CHECK ((((tag IS NOT NULL) AND (subfield IS NOT NULL)) OR (tag IS NULL)))
vmsp_need_a_tag_or_a_ff_or_a_bo CHECK ((((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))))

Tables referencing this one via Foreign Key Constraints:

Index - Schema vandelay


Table: vandelay.match_set_quality

vandelay.match_set_quality Structure
F-Key Name Type Description
id serial PRIMARY KEY
vandelay.match_set.id match_set integer NOT NULL
config.record_attr_definition.name svf text
tag text
subfield text
value text NOT NULL
quality integer NOT NULL DEFAULT 1

 

vandelay.match_set_quality Constraints
Name Constraint
vmsq_need_a_subfield_with_a_tag CHECK ((((tag IS NOT NULL) AND (subfield IS NOT NULL)) OR (tag IS NULL)))
vmsq_need_a_tag_or_a_ff CHECK ((((tag IS NOT NULL) AND (svf IS NULL)) OR ((tag IS NULL) AND (svf IS NOT NULL))))

Index - Schema vandelay


Table: vandelay.merge_profile

vandelay.merge_profile Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.org_unit.id owner integer UNIQUE#1 NOT NULL
name text UNIQUE#1 NOT NULL
add_spec text
replace_spec text
strip_spec text
preserve_spec text
update_bib_source boolean NOT NULL DEFAULT false
update_bib_editor boolean NOT NULL DEFAULT false
lwm_ratio numeric

 

vandelay.merge_profile Constraints
Name Constraint
add_replace_strip_or_preserve CHECK (((preserve_spec IS NOT NULL) OR (replace_spec IS NOT NULL) OR ((preserve_spec IS NULL) AND (replace_spec IS NULL))))

Index - Schema vandelay


Table: vandelay.queue

vandelay.queue Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
actor.usr.id owner integer NOT NULL
name text NOT NULL
complete boolean NOT NULL DEFAULT false
vandelay.match_set.id match_set integer

Index - Schema vandelay


Table: vandelay.queued_authority_record

vandelay.queued_authority_record Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('vandelay.queued_record_id_seq'::regclass)
create_time timestamp with time zone NOT NULL DEFAULT now()
import_time timestamp with time zone
purpose text NOT NULL DEFAULT 'import'::text
marc text NOT NULL
quality integer NOT NULL
vandelay.authority_queue.id queue integer NOT NULL
authority.record_entry.id imported_as integer
vandelay.import_error.code import_error text
error_detail text

Table vandelay.queued_authority_record Inherits queued_record,

 

vandelay.queued_authority_record Constraints
Name Constraint
queued_record_purpose_check CHECK ((purpose = ANY (ARRAY['import'::text, 'overlay'::text])))

Tables referencing this one via Foreign Key Constraints:

queued_authority_record_queue_idx queue

Index - Schema vandelay


Table: vandelay.queued_authority_record_attr

vandelay.queued_authority_record_attr Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
vandelay.queued_authority_record.id record bigint NOT NULL
vandelay.authority_attr_definition.id field integer NOT NULL
attr_value text NOT NULL
queued_authority_record_attr_record_idx record

Index - Schema vandelay


Table: vandelay.queued_bib_record

vandelay.queued_bib_record Structure
F-Key Name Type Description
id bigint PRIMARY KEY DEFAULT nextval('vandelay.queued_record_id_seq'::regclass)
create_time timestamp with time zone NOT NULL DEFAULT now()
import_time timestamp with time zone
purpose text NOT NULL DEFAULT 'import'::text
marc text NOT NULL
quality integer NOT NULL
vandelay.bib_queue.id queue integer NOT NULL
config.bib_source.id bib_source integer
biblio.record_entry.id imported_as bigint
vandelay.import_error.code import_error text
error_detail text

Table vandelay.queued_bib_record Inherits queued_record,

 

vandelay.queued_bib_record Constraints
Name Constraint
queued_record_purpose_check CHECK ((purpose = ANY (ARRAY['import'::text, 'overlay'::text])))

Tables referencing this one via Foreign Key Constraints:

queued_bib_record_queue_idx queue

Index - Schema vandelay


Table: vandelay.queued_bib_record_attr

vandelay.queued_bib_record_attr Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
vandelay.queued_bib_record.id record bigint NOT NULL
vandelay.bib_attr_definition.id field integer NOT NULL
attr_value text NOT NULL
queued_bib_record_attr_record_idx record

Index - Schema vandelay


Table: vandelay.queued_record

vandelay.queued_record Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
create_time timestamp with time zone NOT NULL DEFAULT now()
import_time timestamp with time zone
purpose text NOT NULL DEFAULT 'import'::text
marc text NOT NULL
quality integer NOT NULL

 

vandelay.queued_record Constraints
Name Constraint
queued_record_purpose_check CHECK ((purpose = ANY (ARRAY['import'::text, 'overlay'::text])))

Index - Schema vandelay


Table: vandelay.session_tracker

vandelay.session_tracker Structure
F-Key Name Type Description
id bigserial PRIMARY KEY
session_key text NOT NULL
name text NOT NULL
actor.usr.id usr integer NOT NULL
actor.workstation.id workstation integer NOT NULL
record_type text NOT NULL DEFAULT 'bib'::text
queue bigint NOT NULL
create_time timestamp with time zone NOT NULL DEFAULT now()
update_time timestamp with time zone NOT NULL DEFAULT now()
state text NOT NULL DEFAULT 'active'::text
action_type text NOT NULL DEFAULT 'enqueue'::text
total_actions integer NOT NULL
actions_performed integer NOT NULL

 

vandelay.session_tracker Constraints
Name Constraint
vand_tracker_valid_action_type CHECK ((action_type = ANY (ARRAY['upload'::text, 'enqueue'::text, 'import'::text])))
vand_tracker_valid_record_type CHECK ((record_type = ANY (ARRAY['bib'::text, 'authority'::text])))
vand_tracker_valid_state CHECK ((state = ANY (ARRAY['active'::text, 'error'::text, 'complete'::text])))

Index - Schema vandelay


Function: vandelay._get_expr_push_jrow(auth_heading vandelay.match_set_point, tags_rstore public.hstore, node text)

Returns: void

Language: PLPGSQL

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;

Function: vandelay._get_expr_push_qrow(node vandelay.match_set_point)

Returns: void

Language: PLPGSQL

DECLARE
BEGIN
    INSERT INTO _vandelay_tmp_qrows (q) VALUES (node.id);
END;

Function: vandelay._get_expr_render_one(node vandelay.match_set_point)

Returns: text

Language: PLPGSQL

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;

Function: vandelay._node_tag_comparisons(tagkey boolean, tags_rstore text, op public.hstore, caseless text)

Returns: text

Language: PLPGSQL

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;

Function: vandelay.add_field(field text, source_xml text, target_xml text)

Returns: text

Language: SQL

    SELECT vandelay.add_field( $1, $2, $3, 0 );

Function: vandelay.add_field(force_add text, field text, source_xml text, target_xml integer)

Returns: text

Language: PLPERLU


    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;


Function: vandelay.auto_overlay_authority_queue(merge_profile_id bigint, queue_id integer)

Returns: SET OF bigint

Language: PLPGSQL

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;

Function: vandelay.auto_overlay_authority_queue(queue_id bigint)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM vandelay.auto_overlay_authority_queue( $1, NULL );

Function: vandelay.auto_overlay_authority_record(merge_profile_id bigint, import_id integer)

Returns: boolean

Language: PLPGSQL

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;

Function: vandelay.auto_overlay_authority_record_with_best(lwm_ratio_value_p bigint, merge_profile_id integer, import_id numeric)

Returns: boolean

Language: PLPGSQL

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;

Function: vandelay.auto_overlay_bib_queue(merge_profile_id bigint, queue_id integer)

Returns: SET OF bigint

Language: PLPGSQL

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;

Function: vandelay.auto_overlay_bib_queue(queue_id bigint)

Returns: SET OF bigint

Language: SQL

    SELECT * FROM vandelay.auto_overlay_bib_queue( $1, NULL );

Function: vandelay.auto_overlay_bib_queue_with_best(lwm_ratio_value bigint, merge_profile_id integer, queue_id numeric)

Returns: SET OF bigint

Language: PLPGSQL

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;

Function: vandelay.auto_overlay_bib_queue_with_best(merge_profile_id bigint, import_id integer)

Returns: SET OF bigint

Language: SQL

    SELECT vandelay.auto_overlay_bib_queue_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;

Function: vandelay.auto_overlay_bib_record(merge_profile_id bigint, import_id integer)

Returns: boolean

Language: PLPGSQL

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;

Function: vandelay.auto_overlay_bib_record_with_best(lwm_ratio_value_p bigint, merge_profile_id integer, import_id numeric)

Returns: boolean

Language: PLPGSQL

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;

Function: vandelay.auto_overlay_bib_record_with_best(merge_profile_id bigint, import_id integer)

Returns: boolean

Language: SQL

    SELECT vandelay.auto_overlay_bib_record_with_best( $1, $2, p.lwm_ratio ) FROM vandelay.merge_profile p WHERE id = $2;

Function: vandelay.auto_overlay_org_unit_copies(lwm_ratio_value_p bigint, merge_profile_id integer, import_id numeric)

Returns: boolean

Language: PLPGSQL

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;

Function: vandelay.cleanup_authority_marc()

Returns: trigger

Language: PLPGSQL

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;

Function: vandelay.cleanup_bib_marc()

Returns: trigger

Language: PLPGSQL

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;

Function: vandelay.compile_profile(incoming_xml text)

Returns: compile_profile

Language: PLPGSQL

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;

Function: vandelay.extract_rec_attrs(attr_defs text, xml text[])

Returns: hstore

Language: PLPGSQL

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;

Function: vandelay.extract_rec_attrs(xml text)

Returns: hstore

Language: SQL

    SELECT vandelay.extract_rec_attrs( $1, (SELECT ARRAY_AGG(name) FROM config.record_attr_definition));

Function: vandelay.find_bib_tcn_data(xml text)

Returns: SET OF tcn_data

Language: PLPGSQL

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;

Function: vandelay.flatten_marc(marc text)

Returns: SET OF flat_marc

Language: PLPGSQL

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;

Function: vandelay.flatten_marc_hstore(record_xml text)

Returns: hstore

Language: PLPGSQL

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;

Function: vandelay.flay_marc(text)

Returns: SET OF flat_marc

Language: PLPERLU


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;


Function: vandelay.get_expr_from_match_set(auth_heading integer, tags_rstore public.hstore, match_set_id text)

Returns: text

Language: PLPGSQL

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;

Function: vandelay.get_expr_from_match_set(tags_rstore integer, match_set_id public.hstore)

Returns: text

Language: PLPGSQL

BEGIN
    RETURN vandelay.get_expr_from_match_set(
        match_set_id, tags_rstore, NULL);
END;

Function: vandelay.get_expr_from_match_set_point(auth_heading vandelay.match_set_point, tags_rstore public.hstore, node text)

Returns: text

Language: PLPGSQL

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;

Function: vandelay.ingest_authority_marc()

Returns: trigger

Language: PLPGSQL

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;

Function: vandelay.ingest_bib_items()

Returns: trigger

Language: PLPGSQL

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;

Function: vandelay.ingest_bib_marc()

Returns: trigger

Language: PLPGSQL

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;

Function: vandelay.ingest_items(attr_def_id bigint, import_id bigint)

Returns: SET OF import_item

Language: PLPGSQL

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;

Function: vandelay.marc21_extract_all_fixed_fields(use_default text, marc boolean)

Returns: SET OF record_ff_map

Language: PLPGSQL

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;

Function: vandelay.marc21_extract_fixed_field(use_default text, ff text, marc boolean)

Returns: text

Language: PLPGSQL

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;

Function: vandelay.marc21_extract_fixed_field_list(use_default text, ff text, marc boolean)

Returns: text[]

Language: PLPGSQL

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;

Function: vandelay.marc21_physical_characteristics(marc text)

Returns: SET OF marc21_physical_characteristics

Language: PLPGSQL

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;

Function: vandelay.marc21_record_type(marc text)

Returns: marc21_rec_type_map

Language: PLPGSQL

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;

Function: vandelay.match_authority_record()

Returns: trigger

Language: PLPGSQL

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;

Function: vandelay.match_bib_record()

Returns: trigger

Language: PLPGSQL

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;

Function: vandelay.match_set_test_authxml(record_xml integer, match_set_id text)

Returns: SET OF match_set_test_result

Language: PLPGSQL

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;

Function: vandelay.match_set_test_marcxml(bucket_id integer, record_xml text, match_set_id integer)

Returns: SET OF match_set_test_result

Language: PLPGSQL

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;

Function: vandelay.measure_auth_record_quality(match_set_id text, xml integer)

Returns: integer

Language: PLPGSQL

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;

Function: vandelay.measure_record_quality(match_set_id text, xml integer)

Returns: integer

Language: PLPGSQL

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;

Function: vandelay.merge_record_xml(strip_rule text, replace_preserve_rule text, add_rule text, source_xml text, target_xml text)

Returns: text

Language: SQL

    SELECT vandelay.replace_field( vandelay.add_field( vandelay.strip_field( $1, $5) , $2, $3 ), $2, $4);

Function: vandelay.merge_record_xml(template_marc text, target_marc text)

Returns: text

Language: PLPGSQL

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;

Function: vandelay.merge_record_xml_using_profile(merge_profile_id text, existing_marc text, incoming_marc bigint)

Returns: text

Language: PLPGSQL

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;

Function: vandelay.overlay_authority_record(merge_profile_id bigint, eg_id bigint, import_id integer)

Returns: boolean

Language: PLPGSQL

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;

Function: vandelay.overlay_bib_record(merge_profile_id bigint, eg_id bigint, import_id integer)

Returns: boolean

Language: PLPGSQL

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;

Function: vandelay.replace_field(field text, source_xml text, target_xml text)

Returns: text

Language: PLPERLU


    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;


Function: vandelay.strip_field(field text, xml text)

Returns: text

Language: PLPERLU


    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;


Function: vandelay.template_overlay_bib_record(eg_id text, v_marc bigint)

Returns: boolean

Language: SQL

    SELECT vandelay.template_overlay_bib_record( $1, $2, NULL);

Function: vandelay.template_overlay_bib_record(merge_profile_id text, eg_id bigint, v_marc integer)

Returns: boolean

Language: PLPGSQL

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;

Generated by PostgreSQL Autodoc

W3C HTML 4.01 Strict

newdevs/db/schemas/3.10_schma.txt · Last modified: 2023/05/01 15:50 by mmorgan

Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 4.0 International
CC Attribution-Share Alike 4.0 International Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki

© 2008-2022 GPLS and others. Evergreen is open source software, freely licensed under GNU GPLv2 or later.
The Evergreen Project is a U.S. 501(c)3 non-profit organization.