]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/011.schema.authority.sql
LP#1447168: add an index on authority.simple_heading.record
[Evergreen.git] / Open-ILS / src / sql / Pg / 011.schema.authority.sql
1 /*
2  * Copyright (C) 2004-2008  Georgia Public Library Service
3  * Copyright (C) 2008  Equinox Software, Inc.
4  * Copyright (C) 2010  Laurentian University
5  * Mike Rylander <miker@esilibrary.com> 
6  * Dan Scott <dscott@laurentian.ca>
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  */
19
20 DROP SCHEMA IF EXISTS authority CASCADE;
21
22 BEGIN;
23 CREATE SCHEMA authority;
24
25 CREATE TABLE authority.control_set (
26     id          SERIAL  PRIMARY KEY,
27     name        TEXT    NOT NULL UNIQUE, -- i18n
28     description TEXT                     -- i18n
29 );
30
31 CREATE TABLE authority.control_set_authority_field (
32     id          SERIAL  PRIMARY KEY,
33     main_entry  INT     REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
34     control_set INT     NOT NULL REFERENCES authority.control_set (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
35     tag         CHAR(3) NOT NULL,
36     nfi         CHAR(1),          -- non-filing indicator
37     sf_list     TEXT    NOT NULL,
38     display_sf_list     TEXT NOT NULL,
39     name        TEXT    NOT NULL, -- i18n
40     description TEXT,             -- i18n
41     joiner      TEXT,
42     linking_subfield CHAR(1)
43 );
44
45 CREATE TABLE authority.control_set_bib_field (
46     id              SERIAL  PRIMARY KEY,
47     authority_field INT     NOT NULL REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
48     tag             CHAR(3) NOT NULL
49 );
50
51 -- Seed data will be generated from class <-> axis mapping
52 CREATE TABLE authority.control_set_bib_field_metabib_field_map (
53     id              SERIAL  PRIMARY KEY,
54     bib_field       INT     NOT NULL REFERENCES authority.control_set_bib_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
55     metabib_field   INT     NOT NULL REFERENCES config.metabib_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
56     CONSTRAINT a_bf_mf_map_once UNIQUE (bib_field, metabib_field)
57 );
58
59 CREATE VIEW authority.control_set_auth_field_metabib_field_map_main AS
60     SELECT  DISTINCT b.authority_field, m.metabib_field
61       FROM  authority.control_set_bib_field_metabib_field_map m JOIN authority.control_set_bib_field b ON (b.id = m.bib_field);
62 COMMENT ON VIEW authority.control_set_auth_field_metabib_field_map_main IS $$metabib fields for main entry auth fields$$;
63
64 CREATE VIEW authority.control_set_auth_field_metabib_field_map_refs_only AS
65     SELECT  DISTINCT a.id AS authority_field, m.metabib_field
66       FROM  authority.control_set_authority_field a
67             JOIN authority.control_set_authority_field ame ON (a.main_entry = ame.id)
68             JOIN authority.control_set_bib_field b ON (b.authority_field = ame.id)
69             JOIN authority.control_set_bib_field_metabib_field_map mf ON (mf.bib_field = b.id)
70             JOIN authority.control_set_auth_field_metabib_field_map_main m ON (ame.id = m.authority_field);
71 COMMENT ON VIEW authority.control_set_auth_field_metabib_field_map_refs_only IS $$metabib fields for NON-main entry auth fields$$;
72
73 CREATE VIEW authority.control_set_auth_field_metabib_field_map_refs AS
74     SELECT * FROM authority.control_set_auth_field_metabib_field_map_main
75         UNION
76     SELECT * FROM authority.control_set_auth_field_metabib_field_map_refs_only;
77 COMMENT ON VIEW authority.control_set_auth_field_metabib_field_map_refs IS $$metabib fields for all auth fields$$;
78
79
80 -- blind refs only is probably what we want for lookup in bib/auth browse
81 CREATE VIEW authority.control_set_auth_field_metabib_field_map_blind_refs_only AS
82     SELECT  r.*
83       FROM  authority.control_set_auth_field_metabib_field_map_refs_only r
84             JOIN authority.control_set_authority_field a ON (r.authority_field = a.id)
85       WHERE linking_subfield IS NULL;
86 COMMENT ON VIEW authority.control_set_auth_field_metabib_field_map_blind_refs_only IS $$metabib fields for NON-main entry auth fields that can't be linked to other records$$; -- '
87
88 CREATE VIEW authority.control_set_auth_field_metabib_field_map_blind_refs AS
89     SELECT  r.*
90       FROM  authority.control_set_auth_field_metabib_field_map_refs r
91             JOIN authority.control_set_authority_field a ON (r.authority_field = a.id)
92       WHERE linking_subfield IS NULL;
93 COMMENT ON VIEW authority.control_set_auth_field_metabib_field_map_blind_refs IS $$metabib fields for all auth fields that can't be linked to other records$$; -- '
94
95 CREATE VIEW authority.control_set_auth_field_metabib_field_map_blind_main AS
96     SELECT  r.*
97       FROM  authority.control_set_auth_field_metabib_field_map_main r
98             JOIN authority.control_set_authority_field a ON (r.authority_field = a.id)
99       WHERE linking_subfield IS NULL;
100 COMMENT ON VIEW authority.control_set_auth_field_metabib_field_map_blind_main IS $$metabib fields for main entry auth fields that can't be linked to other records$$; -- '
101
102 CREATE TABLE authority.thesaurus (
103     code        TEXT    PRIMARY KEY,     -- MARC21 thesaurus code
104     control_set INT     REFERENCES authority.control_set (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
105     name        TEXT    NOT NULL UNIQUE, -- i18n
106     description TEXT                     -- i18n
107 );
108
109 CREATE TABLE authority.browse_axis (
110     code        TEXT    PRIMARY KEY,
111     name        TEXT    UNIQUE NOT NULL, -- i18n
112     sorter      TEXT    REFERENCES config.record_attr_definition (name) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
113     description TEXT
114 );
115
116 CREATE TABLE authority.browse_axis_authority_field_map (
117     id          SERIAL  PRIMARY KEY,
118     axis        TEXT    NOT NULL REFERENCES authority.browse_axis (code) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
119     field       INT     NOT NULL REFERENCES authority.control_set_authority_field (id) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED
120 );
121
122 CREATE TABLE authority.record_entry (
123     id              BIGSERIAL    PRIMARY KEY,
124     create_date     TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT now(),
125     edit_date       TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT now(),
126     creator         INT     NOT NULL DEFAULT 1,
127     editor          INT     NOT NULL DEFAULT 1,
128     active          BOOL    NOT NULL DEFAULT TRUE,
129     deleted         BOOL    NOT NULL DEFAULT FALSE,
130     source          INT,
131     control_set     INT     REFERENCES authority.control_set (id) ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED,
132     marc            TEXT    NOT NULL,
133     last_xact_id    TEXT    NOT NULL,
134     owner           INT,
135     heading         TEXT,
136     simple_heading  TEXT
137 );
138 CREATE INDEX authority_record_entry_creator_idx ON authority.record_entry ( creator );
139 CREATE INDEX authority_record_entry_editor_idx ON authority.record_entry ( editor );
140 CREATE INDEX authority_record_entry_create_date_idx ON authority.record_entry ( create_date );
141 CREATE INDEX authority_record_entry_edit_date_idx ON authority.record_entry ( edit_date );
142 CREATE INDEX authority_record_deleted_idx ON authority.record_entry(deleted) WHERE deleted IS FALSE OR deleted = false;
143 CREATE TRIGGER a_marcxml_is_well_formed BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE biblio.check_marcxml_well_formed();
144 CREATE TRIGGER b_maintain_901 BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE evergreen.maintain_901();
145 CREATE TRIGGER c_maintain_control_numbers BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE maintain_control_numbers();
146
147 CREATE TABLE authority.authority_linking (
148     id      BIGSERIAL PRIMARY KEY,
149     source  BIGINT REFERENCES authority.record_entry (id) NOT NULL,
150     target  BIGINT REFERENCES authority.record_entry (id) NOT NULL,
151     field   INT REFERENCES authority.control_set_authority_field (id) NOT NULL
152 );
153
154 CREATE TABLE authority.bib_linking (
155     id          BIGSERIAL   PRIMARY KEY,
156     bib         BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
157     authority   BIGINT      NOT NULL REFERENCES authority.record_entry (id)
158 );
159 CREATE INDEX authority_bl_bib_idx ON authority.bib_linking ( bib );
160 CREATE UNIQUE INDEX authority_bl_bib_authority_once_idx ON authority.bib_linking ( authority, bib );
161
162 CREATE TABLE authority.record_note (
163     id          BIGSERIAL   PRIMARY KEY,
164     record      BIGINT      NOT NULL REFERENCES authority.record_entry (id) DEFERRABLE INITIALLY DEFERRED,
165     value       TEXT        NOT NULL,
166     creator     INT         NOT NULL DEFAULT 1,
167     editor      INT         NOT NULL DEFAULT 1,
168     create_date TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT now(),
169     edit_date   TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT now()
170 );
171 CREATE INDEX authority_record_note_record_idx ON authority.record_note ( record );
172 CREATE INDEX authority_record_note_creator_idx ON authority.record_note ( creator );
173 CREATE INDEX authority_record_note_editor_idx ON authority.record_note ( editor );
174
175 CREATE TABLE authority.rec_descriptor (
176     id              BIGSERIAL PRIMARY KEY,
177     record          BIGINT,
178     record_status   TEXT,
179     encoding_level  TEXT,
180     thesaurus       TEXT
181 );
182 CREATE INDEX authority_rec_descriptor_record_idx ON authority.rec_descriptor (record);
183
184 CREATE TABLE authority.full_rec (
185     id              BIGSERIAL   PRIMARY KEY,
186     record          BIGINT      NOT NULL,
187     tag             CHAR(3)     NOT NULL,
188     ind1            TEXT,
189     ind2            TEXT,
190     subfield        TEXT,
191     value           TEXT        NOT NULL,
192     index_vector    tsvector    NOT NULL
193 );
194 CREATE INDEX authority_full_rec_record_idx ON authority.full_rec (record);
195 CREATE INDEX authority_full_rec_tag_subfield_idx ON authority.full_rec (tag, subfield);
196 CREATE INDEX authority_full_rec_tag_part_idx ON authority.full_rec (SUBSTRING(tag FROM 2));
197 CREATE INDEX authority_full_rec_subfield_a_idx ON authority.full_rec (value) WHERE subfield = 'a';
198 CREATE TRIGGER authority_full_rec_fti_trigger
199     BEFORE UPDATE OR INSERT ON authority.full_rec
200     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
201
202 CREATE INDEX authority_full_rec_index_vector_idx ON authority.full_rec USING GIST (index_vector);
203 /* Enable LIKE to use an index for database clusters with locales other than C or POSIX */
204 CREATE INDEX authority_full_rec_value_tpo_index ON authority.full_rec (value text_pattern_ops);
205 /* But we still need this (boooo) for paging using >, <, etc */
206 CREATE INDEX authority_full_rec_value_index ON authority.full_rec (value);
207
208 CREATE RULE protect_authority_rec_delete AS ON DELETE TO authority.record_entry DO INSTEAD (UPDATE authority.record_entry SET deleted = TRUE WHERE OLD.id = authority.record_entry.id; DELETE FROM authority.full_rec WHERE record = OLD.id);
209
210 -- Intended to be used in a unique index on authority.record_entry like so:
211 -- CREATE UNIQUE INDEX unique_by_heading_and_thesaurus
212 --   ON authority.record_entry (heading)
213 --   WHERE deleted IS FALSE or deleted = FALSE;
214 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT, no_thesaurus BOOL ) RETURNS TEXT AS $func$
215 DECLARE
216     acsaf           authority.control_set_authority_field%ROWTYPE;
217     tag_used        TEXT;
218     nfi_used        TEXT;
219     sf              TEXT;
220     sf_node         TEXT;
221     tag_node        TEXT;
222     thes_code       TEXT;
223     cset            INT;
224     heading_text    TEXT;
225     tmp_text        TEXT;
226     first_sf        BOOL;
227     auth_id         INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT; 
228 BEGIN
229     SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
230
231     IF cset IS NULL THEN
232         SELECT  control_set INTO cset
233           FROM  authority.control_set_authority_field
234           WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
235           LIMIT 1;
236     END IF;
237
238     thes_code := vandelay.marc21_extract_fixed_field(marcxml,'Subj');
239     IF thes_code IS NULL THEN
240         thes_code := '|';
241     ELSIF thes_code = 'z' THEN
242         thes_code := COALESCE( oils_xpath_string('//*[@tag="040"]/*[@code="f"][1]', marcxml), '' );
243     END IF;
244
245     heading_text := '';
246     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset AND main_entry IS NULL LOOP
247         tag_used := acsaf.tag;
248         nfi_used := acsaf.nfi;
249         first_sf := TRUE;
250
251         FOR tag_node IN SELECT unnest(oils_xpath('//*[@tag="'||tag_used||'"]',marcxml)) LOOP
252             FOR sf_node IN SELECT unnest(oils_xpath('./*[contains("'||acsaf.sf_list||'",@code)]',tag_node)) LOOP
253
254                 tmp_text := oils_xpath_string('.', sf_node);
255                 sf := oils_xpath_string('./@code', sf_node);
256
257                 IF first_sf AND tmp_text IS NOT NULL AND nfi_used IS NOT NULL THEN
258
259                     tmp_text := SUBSTRING(
260                         tmp_text FROM
261                         COALESCE(
262                             NULLIF(
263                                 REGEXP_REPLACE(
264                                     oils_xpath_string('./@ind'||nfi_used, tag_node),
265                                     $$\D+$$,
266                                     '',
267                                     'g'
268                                 ),
269                                 ''
270                             )::INT,
271                             0
272                         ) + 1
273                     );
274
275                 END IF;
276
277                 first_sf := FALSE;
278
279                 IF tmp_text IS NOT NULL AND tmp_text <> '' THEN
280                     heading_text := heading_text || E'\u2021' || sf || ' ' || tmp_text;
281                 END IF;
282             END LOOP;
283
284             EXIT WHEN heading_text <> '';
285         END LOOP;
286
287         EXIT WHEN heading_text <> '';
288     END LOOP;
289
290     IF heading_text <> '' THEN
291         IF no_thesaurus IS TRUE THEN
292             heading_text := tag_used || ' ' || public.naco_normalize(heading_text);
293         ELSE
294             heading_text := tag_used || '_' || COALESCE(nfi_used,'-') || '_' || thes_code || ' ' || public.naco_normalize(heading_text);
295         END IF;
296     ELSE
297         heading_text := 'NOHEADING_' || thes_code || ' ' || MD5(marcxml);
298     END IF;
299
300     RETURN heading_text;
301 END;
302 $func$ LANGUAGE PLPGSQL STABLE STRICT;
303
304 CREATE TABLE authority.simple_heading (
305     id              BIGSERIAL   PRIMARY KEY,
306     record          BIGINT      NOT NULL REFERENCES authority.record_entry (id),
307     atag            INT         NOT NULL REFERENCES authority.control_set_authority_field (id),
308     value           TEXT        NOT NULL,
309     sort_value      TEXT        NOT NULL,
310     index_vector    tsvector    NOT NULL
311 );
312 CREATE TRIGGER authority_simple_heading_fti_trigger
313     BEFORE UPDATE OR INSERT ON authority.simple_heading
314     FOR EACH ROW EXECUTE PROCEDURE oils_tsearch2('keyword');
315
316 CREATE INDEX authority_simple_heading_index_vector_idx ON authority.simple_heading USING GIST (index_vector);
317 CREATE INDEX authority_simple_heading_value_idx ON authority.simple_heading (value);
318 CREATE INDEX authority_simple_heading_sort_value_idx ON authority.simple_heading (sort_value);
319 CREATE INDEX authority_simple_heading_record_idx ON authority.simple_heading (record);
320
321 CREATE OR REPLACE FUNCTION authority.simple_heading_set( marcxml TEXT ) RETURNS SETOF authority.simple_heading AS $func$
322 DECLARE
323     res             authority.simple_heading%ROWTYPE;
324     acsaf           authority.control_set_authority_field%ROWTYPE;
325     tag_used        TEXT;
326     nfi_used        TEXT;
327     sf              TEXT;
328     cset            INT;
329     heading_text    TEXT;
330     joiner_text     TEXT;
331     sort_text       TEXT;
332     tmp_text        TEXT;
333     tmp_xml         TEXT;
334     first_sf        BOOL;
335     auth_id         INT DEFAULT COALESCE(NULLIF(oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', marcxml), ''), '0')::INT; 
336 BEGIN
337
338     SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
339
340     IF cset IS NULL THEN
341         SELECT  control_set INTO cset
342           FROM  authority.control_set_authority_field
343           WHERE tag IN ( SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marcxml::XML)::TEXT[]))
344           LIMIT 1;
345     END IF;
346
347     res.record := auth_id;
348
349     FOR acsaf IN SELECT * FROM authority.control_set_authority_field WHERE control_set = cset LOOP
350
351         res.atag := acsaf.id;
352         tag_used := acsaf.tag;
353         nfi_used := acsaf.nfi;
354         joiner_text := COALESCE(acsaf.joiner, ' ');
355
356         FOR tmp_xml IN SELECT UNNEST(XPATH('//*[@tag="'||tag_used||'"]', marcxml::XML)::TEXT[]) LOOP
357
358             heading_text := COALESCE(
359                 oils_xpath_string('./*[contains("'||acsaf.display_sf_list||'",@code)]', tmp_xml, joiner_text),
360                 ''
361             );
362
363             IF nfi_used IS NOT NULL THEN
364
365                 sort_text := SUBSTRING(
366                     heading_text FROM
367                     COALESCE(
368                         NULLIF(
369                             REGEXP_REPLACE(
370                                 oils_xpath_string('./@ind'||nfi_used, tmp_xml::TEXT),
371                                 $$\D+$$,
372                                 '',
373                                 'g'
374                             ),
375                             ''
376                         )::INT,
377                         0
378                     ) + 1
379                 );
380
381             ELSE
382                 sort_text := heading_text;
383             END IF;
384
385             IF heading_text IS NOT NULL AND heading_text <> '' THEN
386                 res.value := heading_text;
387                 res.sort_value := public.naco_normalize(sort_text);
388                 res.index_vector = to_tsvector('keyword'::regconfig, res.sort_value);
389                 RETURN NEXT res;
390             END IF;
391
392         END LOOP;
393
394     END LOOP;
395
396     RETURN;
397 END;
398 $func$ LANGUAGE PLPGSQL STABLE STRICT;
399
400 CREATE OR REPLACE FUNCTION authority.simple_normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
401     SELECT authority.normalize_heading($1, TRUE);
402 $func$ LANGUAGE SQL STABLE STRICT;
403
404 CREATE OR REPLACE FUNCTION authority.normalize_heading( marcxml TEXT ) RETURNS TEXT AS $func$
405     SELECT authority.normalize_heading($1, FALSE);
406 $func$ LANGUAGE SQL STABLE STRICT;
407
408 COMMENT ON FUNCTION authority.normalize_heading( TEXT ) IS $$
409 Extract the authority heading, thesaurus, and NACO-normalized values
410 from an authority record. The primary purpose is to build a unique
411 index to defend against duplicated authority records from the same
412 thesaurus.
413 $$;
414
415 -- Store these in line with the MARC for easier indexing
416 CREATE OR REPLACE FUNCTION authority.normalize_heading_for_upsert () RETURNS TRIGGER AS $f$
417 BEGIN
418     NEW.heading := authority.normalize_heading( NEW.marc );
419     NEW.simple_heading := authority.simple_normalize_heading( NEW.marc );
420     RETURN NEW;
421 END;
422 $f$ LANGUAGE PLPGSQL;
423
424 CREATE TRIGGER update_headings_tgr BEFORE INSERT OR UPDATE ON authority.record_entry FOR EACH ROW EXECUTE PROCEDURE authority.normalize_heading_for_upsert();
425
426 -- Adding indexes using oils_xpath_string() for the main entry tags described in
427 -- authority.control_set_authority_field would speed this up, if we ever want to use it, though
428 -- the existing index on authority.normalize_heading() helps already with a record in hand
429 CREATE OR REPLACE VIEW authority.tracing_links AS
430     SELECT  main.record AS record,
431             main.id AS main_id,
432             main.tag AS main_tag,
433             oils_xpath_string('//*[@tag="'||main.tag||'"]/*[local-name()="subfield"]', are.marc) AS main_value,
434             substr(link.value,1,1) AS relationship,
435             substr(link.value,2,1) AS use_restriction,
436             substr(link.value,3,1) AS deprecation,
437             substr(link.value,4,1) AS display_restriction,
438             link.id AS link_id,
439             link.tag AS link_tag,
440             oils_xpath_string('//*[@tag="'||link.tag||'"]/*[local-name()="subfield"]', are.marc) AS link_value,
441             are.heading AS normalized_main_value
442       FROM  authority.full_rec main
443             JOIN authority.record_entry are ON (main.record = are.id)
444             JOIN authority.control_set_authority_field main_entry
445                 ON (main_entry.tag = main.tag
446                     AND main_entry.main_entry IS NULL
447                     AND main.subfield = 'a' )
448             JOIN authority.control_set_authority_field sub_entry
449                 ON (main_entry.id = sub_entry.main_entry)
450             JOIN authority.full_rec link
451                 ON (link.record = main.record
452                     AND link.tag = sub_entry.tag
453                     AND link.subfield = 'w' );
454
455 -- Function to generate an ephemeral overlay template from an authority record
456 CREATE OR REPLACE FUNCTION authority.generate_overlay_template (source_xml TEXT) RETURNS TEXT AS $f$
457 DECLARE
458     cset                INT;
459     main_entry          authority.control_set_authority_field%ROWTYPE;
460     bib_field           authority.control_set_bib_field%ROWTYPE;
461     auth_id             INT DEFAULT oils_xpath_string('//*[@tag="901"]/*[local-name()="subfield" and @code="c"]', source_xml)::INT;
462     tmp_data            XML;
463     replace_data        XML[] DEFAULT '{}'::XML[];
464     replace_rules       TEXT[] DEFAULT '{}'::TEXT[];
465     auth_field          XML[];
466     auth_i1             TEXT;
467     auth_i2             TEXT;
468 BEGIN
469     IF auth_id IS NULL THEN
470         RETURN NULL;
471     END IF;
472
473     -- Default to the LoC controll set
474     SELECT control_set INTO cset FROM authority.record_entry WHERE id = auth_id;
475
476     -- if none, make a best guess
477     IF cset IS NULL THEN
478         SELECT  control_set INTO cset
479           FROM  authority.control_set_authority_field
480           WHERE tag IN (
481                     SELECT  UNNEST(XPATH('//*[starts-with(@tag,"1")]/@tag',marc::XML)::TEXT[])
482                       FROM  authority.record_entry
483                       WHERE id = auth_id
484                 )
485           LIMIT 1;
486     END IF;
487
488     -- if STILL none, no-op change
489     IF cset IS NULL THEN
490         RETURN XMLELEMENT(
491             name record,
492             XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
493             XMLELEMENT( name leader, '00881nam a2200193   4500'),
494             XMLELEMENT(
495                 name datafield,
496                 XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
497                 XMLELEMENT(
498                     name subfield,
499                     XMLATTRIBUTES('d' AS code),
500                     '901c'
501                 )
502             )
503         )::TEXT;
504     END IF;
505
506     FOR main_entry IN SELECT * FROM authority.control_set_authority_field acsaf WHERE acsaf.control_set = cset AND acsaf.main_entry IS NULL LOOP
507         auth_field := XPATH('//*[@tag="'||main_entry.tag||'"][1]',source_xml::XML);
508         auth_i1 = (XPATH('@ind1',auth_field[1]))[1];
509         auth_i2 = (XPATH('@ind2',auth_field[1]))[1];
510         IF ARRAY_LENGTH(auth_field,1) > 0 THEN
511             FOR bib_field IN SELECT * FROM authority.control_set_bib_field WHERE authority_field = main_entry.id LOOP
512                 SELECT XMLELEMENT( -- XMLAGG avoids magical <element> creation, but requires unnest subquery
513                     name datafield,
514                     XMLATTRIBUTES(bib_field.tag AS tag, auth_i1 AS ind1, auth_i2 AS ind2),
515                     XMLAGG(UNNEST)
516                 ) INTO tmp_data FROM UNNEST(XPATH('//*[local-name()="subfield"]', auth_field[1]));
517                 replace_data := replace_data || tmp_data;
518                 replace_rules := replace_rules || ( bib_field.tag || main_entry.sf_list || E'[0~\\)' || auth_id || '$]' );
519                 tmp_data = NULL;
520             END LOOP;
521             EXIT;
522         END IF;
523     END LOOP;
524
525     SELECT XMLAGG(UNNEST) INTO tmp_data FROM UNNEST(replace_data);
526
527     RETURN XMLELEMENT(
528         name record,
529         XMLATTRIBUTES('http://www.loc.gov/MARC21/slim' AS xmlns),
530         XMLELEMENT( name leader, '00881nam a2200193   4500'),
531         tmp_data,
532         XMLELEMENT(
533             name datafield,
534             XMLATTRIBUTES( '905' AS tag, ' ' AS ind1, ' ' AS ind2),
535             XMLELEMENT(
536                 name subfield,
537                 XMLATTRIBUTES('r' AS code),
538                 ARRAY_TO_STRING(replace_rules,',')
539             )
540         )
541     )::TEXT;
542 END;
543 $f$ STABLE LANGUAGE PLPGSQL;
544
545 CREATE OR REPLACE FUNCTION authority.generate_overlay_template ( BIGINT ) RETURNS TEXT AS $func$
546     SELECT authority.generate_overlay_template( marc ) FROM authority.record_entry WHERE id = $1;
547 $func$ LANGUAGE SQL;
548
549 CREATE OR REPLACE FUNCTION authority.merge_records ( target_record BIGINT, source_record BIGINT ) RETURNS INT AS $func$
550 DECLARE
551     moved_objects INT := 0;
552     bib_id        INT := 0;
553     bib_rec       biblio.record_entry%ROWTYPE;
554     auth_link     authority.bib_linking%ROWTYPE;
555     ingest_same   boolean;
556 BEGIN
557
558     -- Defining our terms:
559     -- "target record" = the record that will survive the merge
560     -- "source record" = the record that is sacrifing its existence and being
561     --   replaced by the target record
562
563     -- 1. Update all bib records with the ID from target_record in their $0
564     FOR bib_rec IN
565             SELECT  bre.*
566               FROM  biblio.record_entry bre 
567                     JOIN authority.bib_linking abl ON abl.bib = bre.id
568               WHERE abl.authority = source_record
569         LOOP
570
571         UPDATE  biblio.record_entry
572           SET   marc = REGEXP_REPLACE(
573                     marc,
574                     E'(<subfield\\s+code="0"\\s*>[^<]*?\\))' || source_record || '<',
575                     E'\\1' || target_record || '<',
576                     'g'
577                 )
578           WHERE id = bib_rec.id;
579
580           moved_objects := moved_objects + 1;
581     END LOOP;
582
583     -- 2. Grab the current value of reingest on same MARC flag
584     SELECT  enabled INTO ingest_same
585       FROM  config.internal_flag
586       WHERE name = 'ingest.reingest.force_on_same_marc'
587     ;
588
589     -- 3. Temporarily set reingest on same to TRUE
590     UPDATE  config.internal_flag
591       SET   enabled = TRUE
592       WHERE name = 'ingest.reingest.force_on_same_marc'
593     ;
594
595     -- 4. Make a harmless update to target_record to trigger auto-update
596     --    in linked bibliographic records
597     UPDATE  authority.record_entry
598       SET   deleted = FALSE
599       WHERE id = target_record;
600
601     -- 5. "Delete" source_record
602     DELETE FROM authority.record_entry WHERE id = source_record;
603
604     -- 6. Set "reingest on same MARC" flag back to initial value
605     UPDATE  config.internal_flag
606       SET   enabled = ingest_same
607       WHERE name = 'ingest.reingest.force_on_same_marc'
608     ;
609
610     RETURN moved_objects;
611 END;
612 $func$ LANGUAGE plpgsql;
613
614
615 -- Support function used to find the pivot for alpha-heading-browse style searching
616 CREATE OR REPLACE FUNCTION authority.simple_heading_find_pivot( a INT[], q TEXT ) RETURNS TEXT AS $$
617 DECLARE
618     sort_value_row  RECORD;
619     value_row       RECORD;
620     t_term          TEXT;
621 BEGIN
622
623     t_term := public.naco_normalize(q);
624
625     SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
626                 + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
627             ash.sort_value
628       INTO  sort_value_row
629       FROM  authority.simple_heading ash
630       WHERE ash.atag = ANY (a)
631             AND ash.sort_value >= t_term
632       ORDER BY rank DESC, ash.sort_value
633       LIMIT 1;
634
635     SELECT  CASE WHEN ash.sort_value LIKE t_term || '%' THEN 1 ELSE 0 END
636                 + CASE WHEN ash.value LIKE t_term || '%' THEN 1 ELSE 0 END AS rank,
637             ash.sort_value
638       INTO  value_row
639       FROM  authority.simple_heading ash
640       WHERE ash.atag = ANY (a)
641             AND ash.value >= t_term
642       ORDER BY rank DESC, ash.sort_value
643       LIMIT 1;
644
645     IF value_row.rank > sort_value_row.rank THEN
646         RETURN value_row.sort_value;
647     ELSE
648         RETURN sort_value_row.sort_value;
649     END IF;
650 END;
651 $$ LANGUAGE PLPGSQL;
652
653 CREATE OR REPLACE FUNCTION authority.simple_heading_browse_center( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
654 DECLARE
655     pivot_sort_value    TEXT;
656     boffset             INT DEFAULT 0;
657     aoffset             INT DEFAULT 0;
658     blimit              INT DEFAULT 0;
659     alimit              INT DEFAULT 0;
660 BEGIN
661
662     pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q);
663
664     IF page = 0 THEN
665         blimit := pagesize / 2;
666         alimit := blimit;
667
668         IF pagesize % 2 <> 0 THEN
669             alimit := alimit + 1;
670         END IF;
671     ELSE
672         blimit := pagesize;
673         alimit := blimit;
674
675         boffset := pagesize / 2;
676         aoffset := boffset;
677
678         IF pagesize % 2 <> 0 THEN
679             boffset := boffset + 1;
680         END IF;
681     END IF;
682
683     IF page <= 0 THEN
684         RETURN QUERY
685             -- "bottom" half of the browse results
686             SELECT id FROM (
687                 SELECT  ash.id,
688                         row_number() over ()
689                   FROM  authority.simple_heading ash
690                   WHERE ash.atag = ANY (atag_list)
691                         AND ash.sort_value < pivot_sort_value
692                   ORDER BY ash.sort_value DESC
693                   LIMIT blimit
694                   OFFSET ABS(page) * pagesize - boffset
695             ) x ORDER BY row_number DESC;
696     END IF;
697
698     IF page >= 0 THEN
699         RETURN QUERY
700             -- "bottom" half of the browse results
701             SELECT  ash.id
702               FROM  authority.simple_heading ash
703               WHERE ash.atag = ANY (atag_list)
704                     AND ash.sort_value >= pivot_sort_value
705               ORDER BY ash.sort_value
706               LIMIT alimit
707               OFFSET ABS(page) * pagesize - aoffset;
708     END IF;
709 END;
710 $$ LANGUAGE PLPGSQL ROWS 10;
711
712 CREATE OR REPLACE FUNCTION authority.axis_authority_tags(a TEXT) RETURNS INT[] AS $$
713     SELECT ARRAY_AGG(field) FROM authority.browse_axis_authority_field_map WHERE axis = $1;
714 $$ LANGUAGE SQL;
715
716
717 CREATE OR REPLACE FUNCTION authority.axis_authority_tags_refs(a TEXT) RETURNS INT[] AS $$
718     SELECT ARRAY_AGG(y) from (
719        SELECT  unnest(ARRAY_CAT(
720                  ARRAY[a.field],
721                  (SELECT ARRAY_AGG(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.field)
722              )) y
723        FROM  authority.browse_axis_authority_field_map a
724        WHERE axis = $1) x
725 $$ LANGUAGE SQL;
726
727
728 CREATE OR REPLACE FUNCTION authority.btag_authority_tags(btag TEXT) RETURNS INT[] AS $$
729     SELECT ARRAY_AGG(authority_field) FROM authority.control_set_bib_field WHERE tag = $1
730 $$ LANGUAGE SQL;
731
732
733 CREATE OR REPLACE FUNCTION authority.btag_authority_tags_refs(btag TEXT) RETURNS INT[] AS $$
734     SELECT ARRAY_AGG(y) from (
735         SELECT  unnest(ARRAY_CAT(
736                     ARRAY[a.authority_field],
737                     (SELECT ARRAY_AGG(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.authority_field)
738                 )) y
739       FROM  authority.control_set_bib_field a
740       WHERE a.tag = $1) x
741 $$ LANGUAGE SQL;
742
743
744 CREATE OR REPLACE FUNCTION authority.atag_authority_tags(atag TEXT) RETURNS INT[] AS $$
745     SELECT ARRAY_AGG(id) FROM authority.control_set_authority_field WHERE tag = $1
746 $$ LANGUAGE SQL;
747
748 CREATE OR REPLACE FUNCTION authority.atag_authority_tags_refs(atag TEXT) RETURNS INT[] AS $$
749     SELECT ARRAY_AGG(y) from (
750         SELECT  unnest(ARRAY_CAT(
751                     ARRAY[a.id],
752                     (SELECT ARRAY_AGG(x.id) FROM authority.control_set_authority_field x WHERE x.main_entry = a.id)
753                 )) y
754       FROM  authority.control_set_authority_field a
755       WHERE a.tag = $1) x
756 $$ LANGUAGE SQL;
757
758
759 CREATE OR REPLACE FUNCTION authority.axis_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
760     SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags($1), $2, $3, $4)
761 $$ LANGUAGE SQL ROWS 10;
762
763 CREATE OR REPLACE FUNCTION authority.btag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
764     SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags($1), $2, $3, $4)
765 $$ LANGUAGE SQL ROWS 10;
766
767 CREATE OR REPLACE FUNCTION authority.atag_browse_center( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
768     SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags($1), $2, $3, $4)
769 $$ LANGUAGE SQL ROWS 10;
770
771 CREATE OR REPLACE FUNCTION authority.axis_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
772     SELECT * FROM authority.simple_heading_browse_center(authority.axis_authority_tags_refs($1), $2, $3, $4)
773 $$ LANGUAGE SQL ROWS 10;
774
775 CREATE OR REPLACE FUNCTION authority.btag_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
776     SELECT * FROM authority.simple_heading_browse_center(authority.btag_authority_tags_refs($1), $2, $3, $4)
777 $$ LANGUAGE SQL ROWS 10;
778
779 CREATE OR REPLACE FUNCTION authority.atag_browse_center_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 9 ) RETURNS SETOF BIGINT AS $$
780     SELECT * FROM authority.simple_heading_browse_center(authority.atag_authority_tags_refs($1), $2, $3, $4)
781 $$ LANGUAGE SQL ROWS 10;
782
783
784 CREATE OR REPLACE FUNCTION authority.simple_heading_browse_top( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
785 DECLARE
786     pivot_sort_value    TEXT;
787 BEGIN
788
789     pivot_sort_value := authority.simple_heading_find_pivot(atag_list,q);
790
791     IF page < 0 THEN
792         RETURN QUERY
793             -- "bottom" half of the browse results
794             SELECT id FROM (
795                 SELECT  ash.id,
796                         row_number() over ()
797                   FROM  authority.simple_heading ash
798                   WHERE ash.atag = ANY (atag_list)
799                         AND ash.sort_value < pivot_sort_value
800                   ORDER BY ash.sort_value DESC
801                   LIMIT pagesize
802                   OFFSET (ABS(page) - 1) * pagesize
803             ) x ORDER BY row_number DESC;
804     END IF;
805
806     IF page >= 0 THEN
807         RETURN QUERY
808             -- "bottom" half of the browse results
809             SELECT  ash.id
810               FROM  authority.simple_heading ash
811               WHERE ash.atag = ANY (atag_list)
812                     AND ash.sort_value >= pivot_sort_value
813               ORDER BY ash.sort_value
814               LIMIT pagesize
815               OFFSET ABS(page) * pagesize ;
816     END IF;
817 END;
818 $$ LANGUAGE PLPGSQL ROWS 10;
819
820 CREATE OR REPLACE FUNCTION authority.axis_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
821     SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags($1), $2, $3, $4)
822 $$ LANGUAGE SQL ROWS 10;
823
824 CREATE OR REPLACE FUNCTION authority.btag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
825     SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags($1), $2, $3, $4)
826 $$ LANGUAGE SQL ROWS 10;
827
828 CREATE OR REPLACE FUNCTION authority.atag_browse_top( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
829     SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags($1), $2, $3, $4)
830 $$ LANGUAGE SQL ROWS 10;
831
832 CREATE OR REPLACE FUNCTION authority.axis_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
833     SELECT * FROM authority.simple_heading_browse_top(authority.axis_authority_tags_refs($1), $2, $3, $4)
834 $$ LANGUAGE SQL ROWS 10;
835
836 CREATE OR REPLACE FUNCTION authority.btag_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
837     SELECT * FROM authority.simple_heading_browse_top(authority.btag_authority_tags_refs($1), $2, $3, $4)
838 $$ LANGUAGE SQL ROWS 10;
839
840 CREATE OR REPLACE FUNCTION authority.atag_browse_top_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
841     SELECT * FROM authority.simple_heading_browse_top(authority.atag_authority_tags_refs($1), $2, $3, $4)
842 $$ LANGUAGE SQL ROWS 10;
843
844
845 CREATE OR REPLACE FUNCTION authority.simple_heading_search_rank( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
846     SELECT  ash.id
847       FROM  authority.simple_heading ash,
848             public.naco_normalize($2) t(term),
849             plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
850       WHERE ash.atag = ANY ($1)
851             AND ash.index_vector @@ ptsq.term
852       ORDER BY ts_rank_cd(ash.index_vector,ptsq.term,14)::numeric
853                     + CASE WHEN ash.sort_value LIKE t.term || '%' THEN 2 ELSE 0 END
854                     + CASE WHEN ash.value LIKE t.term || '%' THEN 1 ELSE 0 END DESC
855       LIMIT $4
856       OFFSET $4 * $3;
857 $$ LANGUAGE SQL ROWS 10;
858
859 CREATE OR REPLACE FUNCTION authority.axis_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
860     SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags($1), $2, $3, $4)
861 $$ LANGUAGE SQL ROWS 10;
862
863 CREATE OR REPLACE FUNCTION authority.btag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
864     SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags($1), $2, $3, $4)
865 $$ LANGUAGE SQL ROWS 10;
866
867 CREATE OR REPLACE FUNCTION authority.atag_search_rank( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
868     SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags($1), $2, $3, $4)
869 $$ LANGUAGE SQL ROWS 10;
870
871 CREATE OR REPLACE FUNCTION authority.axis_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
872     SELECT * FROM authority.simple_heading_search_rank(authority.axis_authority_tags_refs($1), $2, $3, $4)
873 $$ LANGUAGE SQL ROWS 10;
874
875 CREATE OR REPLACE FUNCTION authority.btag_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
876     SELECT * FROM authority.simple_heading_search_rank(authority.btag_authority_tags_refs($1), $2, $3, $4)
877 $$ LANGUAGE SQL ROWS 10;
878
879 CREATE OR REPLACE FUNCTION authority.atag_search_rank_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
880     SELECT * FROM authority.simple_heading_search_rank(authority.atag_authority_tags_refs($1), $2, $3, $4)
881 $$ LANGUAGE SQL ROWS 10;
882
883
884 CREATE OR REPLACE FUNCTION authority.simple_heading_search_heading( atag_list INT[], q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
885     SELECT  ash.id
886       FROM  authority.simple_heading ash,
887             public.naco_normalize($2) t(term),
888             plainto_tsquery('keyword'::regconfig,$2) ptsq(term)
889       WHERE ash.atag = ANY ($1)
890             AND ash.index_vector @@ ptsq.term
891       ORDER BY ash.sort_value
892       LIMIT $4
893       OFFSET $4 * $3;
894 $$ LANGUAGE SQL ROWS 10;
895
896 CREATE OR REPLACE FUNCTION authority.axis_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
897     SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags($1), $2, $3, $4)
898 $$ LANGUAGE SQL ROWS 10;
899
900 CREATE OR REPLACE FUNCTION authority.btag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
901     SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags($1), $2, $3, $4)
902 $$ LANGUAGE SQL ROWS 10;
903
904 CREATE OR REPLACE FUNCTION authority.atag_search_heading( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
905     SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags($1), $2, $3, $4)
906 $$ LANGUAGE SQL ROWS 10;
907
908 CREATE OR REPLACE FUNCTION authority.axis_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
909     SELECT * FROM authority.simple_heading_search_heading(authority.axis_authority_tags_refs($1), $2, $3, $4)
910 $$ LANGUAGE SQL ROWS 10;
911
912 CREATE OR REPLACE FUNCTION authority.btag_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
913     SELECT * FROM authority.simple_heading_search_heading(authority.btag_authority_tags_refs($1), $2, $3, $4)
914 $$ LANGUAGE SQL ROWS 10;
915
916 CREATE OR REPLACE FUNCTION authority.atag_search_heading_refs( a TEXT, q TEXT, page INT DEFAULT 0, pagesize INT DEFAULT 10 ) RETURNS SETOF BIGINT AS $$
917     SELECT * FROM authority.simple_heading_search_heading(authority.atag_authority_tags_refs($1), $2, $3, $4)
918 $$ LANGUAGE SQL ROWS 10;
919
920
921 COMMIT;
922