]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/sql/Pg/upgrade/XXXX.schema.metabib-display-field.sql
LP#1251394 Compressed and wide display entry VIEWs
[Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / XXXX.schema.metabib-display-field.sql
1
2 BEGIN;
3
4 ALTER TABLE config.metabib_field 
5     ADD COLUMN display_xpath TEXT, 
6     ADD COLUMN display_field BOOL NOT NULL DEFAULT FALSE;
7
8 CREATE TABLE config.display_field_map (
9     name    TEXT   PRIMARY KEY,
10     field   INTEGER REFERENCES config.metabib_field (id),
11     multi   BOOLEAN DEFAULT FALSE
12 );
13
14 CREATE TABLE metabib.display_entry (
15     id      BIGSERIAL  PRIMARY KEY,
16     source  BIGINT     NOT NULL REFERENCES biblio.record_entry (id),
17     field   INT        NOT NULL REFERENCES config.metabib_field (id),
18     value   TEXT       NOT NULL
19 );
20
21 CREATE INDEX metabib_display_entry_field_idx ON metabib.display_entry (field);
22 CREATE INDEX metabib_display_entry_source_idx ON metabib.display_entry (source);
23
24 -- one row per display entry fleshed with field info
25 CREATE VIEW metabib.flat_display_entry AS
26     SELECT
27         mde.source,
28         cdfm.name,
29         cdfm.multi,
30         cmf.label,
31         cmf.id AS field,
32         mde.value
33     FROM metabib.display_entry mde
34     JOIN config.metabib_field cmf ON (cmf.id = mde.field)
35     JOIN config.display_field_map cdfm ON (cdfm.field = mde.field)
36 ;
37
38 -- like flat_display_entry except values are compressed 
39 -- into one row per display_field_map and JSON-ified.
40 CREATE VIEW metabib.compressed_display_entry AS
41     SELECT 
42         source,
43         name,
44         multi,
45         label,
46         field,
47         CASE WHEN multi THEN
48             TO_JSON(ARRAY_AGG(value))
49         ELSE
50             TO_JSON(MIN(value))
51         END AS value
52     FROM metabib.flat_display_entry
53     GROUP BY 1, 2, 3, 4, 5
54 ;
55
56 -- TODO: expand to encompass all well-known fields
57 CREATE VIEW metabib.wide_display_entry AS
58     SELECT 
59         bre.id AS source,
60         COALESCE(mcde_title.value, 'null') AS title,
61         COALESCE(mcde_author.value, 'null') AS author,
62         COALESCE(mcde_subject.value, 'null') AS subject,
63         COALESCE(mcde_topic_subject.value, 'null') AS topic_subject,
64         COALESCE(mcde_isbn.value, 'null') AS isbn
65     -- ensure one row per bre regardless of any display fields
66     FROM biblio.record_entry bre 
67     LEFT JOIN metabib.compressed_display_entry mcde_title 
68         ON (bre.id = mcde_title.source AND mcde_title.name = 'title')
69     LEFT JOIN metabib.compressed_display_entry mcde_author 
70         ON (bre.id = mcde_author.source AND mcde_author.name = 'author')
71     LEFT JOIN metabib.compressed_display_entry mcde_subject 
72         ON (bre.id = mcde_subject.source AND mcde_subject.name = 'subject')
73     LEFT JOIN metabib.compressed_display_entry mcde_topic_subject 
74         ON (bre.id = mcde_topic_subject.source AND mcde_topic_subject.name = 'topic_subject')
75     LEFT JOIN metabib.compressed_display_entry mcde_isbn 
76         ON (bre.id = mcde_isbn.source AND mcde_isbn.name = 'isbn')
77 ;
78
79
80 CREATE OR REPLACE FUNCTION metabib.display_field_normalize_trigger () 
81     RETURNS TRIGGER AS $$
82 DECLARE
83     normalizer  RECORD;
84     display_field_text  TEXT;
85 BEGIN
86     display_field_text := NEW.value;
87
88     FOR normalizer IN
89         SELECT  n.func AS func,
90                 n.param_count AS param_count,
91                 m.params AS params
92           FROM  config.index_normalizer n
93                 JOIN config.metabib_field_index_norm_map m ON (m.norm = n.id)
94           WHERE m.field = NEW.field AND m.pos < 0
95           ORDER BY m.pos LOOP
96
97             EXECUTE 'SELECT ' || normalizer.func || '(' ||
98                 quote_literal( display_field_text ) ||
99                 CASE
100                     WHEN normalizer.param_count > 0
101                         THEN ',' || REPLACE(REPLACE(BTRIM(
102                             normalizer.params,'[]'),E'\'',E'\\\''),E'"',E'\'')
103                         ELSE ''
104                     END ||
105                 ')' INTO display_field_text;
106
107     END LOOP;
108
109     NEW.value = display_field_text;
110
111     RETURN NEW;
112 END;
113 $$ LANGUAGE PLPGSQL;
114
115 CREATE TRIGGER display_field_normalize_tgr
116         BEFORE UPDATE OR INSERT ON metabib.display_entry
117         FOR EACH ROW EXECUTE PROCEDURE metabib.display_field_normalize_trigger();
118
119 CREATE OR REPLACE FUNCTION evergreen.display_field_force_nfc() 
120     RETURNS TRIGGER AS $$
121 BEGIN
122     NEW.value := force_unicode_normal_form(NEW.value,'NFC');
123     RETURN NEW;
124 END;
125 $$ LANGUAGE PLPGSQL;
126
127 CREATE TRIGGER display_field_force_nfc_tgr
128         BEFORE UPDATE OR INSERT ON metabib.display_entry
129         FOR EACH ROW EXECUTE PROCEDURE evergreen.display_field_force_nfc();
130
131 ALTER TYPE metabib.field_entry_template ADD ATTRIBUTE display_field BOOL;
132
133 CREATE OR REPLACE FUNCTION biblio.extract_metabib_field_entry ( rid BIGINT, default_joiner TEXT ) RETURNS SETOF metabib.field_entry_template AS $func$
134 DECLARE
135     bib     biblio.record_entry%ROWTYPE;
136     idx     config.metabib_field%ROWTYPE;
137     xfrm        config.xml_transform%ROWTYPE;
138     prev_xfrm   TEXT;
139     transformed_xml TEXT;
140     xml_node    TEXT;
141     xml_node_list   TEXT[];
142     facet_text  TEXT;
143     display_text TEXT;
144     browse_text TEXT;
145     sort_value  TEXT;
146     raw_text    TEXT;
147     curr_text   TEXT;
148     joiner      TEXT := default_joiner; -- XXX will index defs supply a joiner?
149     authority_text TEXT;
150     authority_link BIGINT;
151     output_row  metabib.field_entry_template%ROWTYPE;
152 BEGIN
153
154     -- Start out with no field-use bools set
155     output_row.browse_field = FALSE;
156     output_row.facet_field = FALSE;
157     output_row.display_field = FALSE;
158     output_row.search_field = FALSE;
159
160     -- Get the record
161     SELECT INTO bib * FROM biblio.record_entry WHERE id = rid;
162
163     -- Loop over the indexing entries
164     FOR idx IN SELECT * FROM config.metabib_field ORDER BY format LOOP
165
166         joiner := COALESCE(idx.joiner, default_joiner);
167
168         SELECT INTO xfrm * from config.xml_transform WHERE name = idx.format;
169
170         -- See if we can skip the XSLT ... it's expensive
171         IF prev_xfrm IS NULL OR prev_xfrm <> xfrm.name THEN
172             -- Can't skip the transform
173             IF xfrm.xslt <> '---' THEN
174                 transformed_xml := oils_xslt_process(bib.marc,xfrm.xslt);
175             ELSE
176                 transformed_xml := bib.marc;
177             END IF;
178
179             prev_xfrm := xfrm.name;
180         END IF;
181
182         xml_node_list := oils_xpath( idx.xpath, transformed_xml, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
183
184         raw_text := NULL;
185         FOR xml_node IN SELECT x FROM unnest(xml_node_list) AS x LOOP
186             CONTINUE WHEN xml_node !~ E'^\\s*<';
187
188             -- XXX much of this should be moved into oils_xpath_string...
189             curr_text := ARRAY_TO_STRING(evergreen.array_remove_item_by_value(evergreen.array_remove_item_by_value(
190                 oils_xpath( '//text()', -- get the content of all the nodes within the main selected node
191                     REGEXP_REPLACE( xml_node, E'\\s+', ' ', 'g' ) -- Translate adjacent whitespace to a single space
192                 ), ' '), ''),  -- throw away morally empty (bankrupt?) strings
193                 joiner
194             );
195
196             CONTINUE WHEN curr_text IS NULL OR curr_text = '';
197
198             IF raw_text IS NOT NULL THEN
199                 raw_text := raw_text || joiner;
200             END IF;
201
202             raw_text := COALESCE(raw_text,'') || curr_text;
203
204             -- autosuggest/metabib.browse_entry
205             IF idx.browse_field THEN
206
207                 IF idx.browse_xpath IS NOT NULL AND idx.browse_xpath <> '' THEN
208                     browse_text := oils_xpath_string( idx.browse_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
209                 ELSE
210                     browse_text := curr_text;
211                 END IF;
212
213                 IF idx.browse_sort_xpath IS NOT NULL AND
214                     idx.browse_sort_xpath <> '' THEN
215
216                     sort_value := oils_xpath_string(
217                         idx.browse_sort_xpath, xml_node, joiner,
218                         ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]]
219                     );
220                 ELSE
221                     sort_value := browse_text;
222                 END IF;
223
224                 output_row.field_class = idx.field_class;
225                 output_row.field = idx.id;
226                 output_row.source = rid;
227                 output_row.value = BTRIM(REGEXP_REPLACE(browse_text, E'\\s+', ' ', 'g'));
228                 output_row.sort_value :=
229                     public.naco_normalize(sort_value);
230
231                 output_row.authority := NULL;
232
233                 IF idx.authority_xpath IS NOT NULL AND idx.authority_xpath <> '' THEN
234                     authority_text := oils_xpath_string(
235                         idx.authority_xpath, xml_node, joiner,
236                         ARRAY[
237                             ARRAY[xfrm.prefix, xfrm.namespace_uri],
238                             ARRAY['xlink','http://www.w3.org/1999/xlink']
239                         ]
240                     );
241
242                     IF authority_text ~ '^\d+$' THEN
243                         authority_link := authority_text::BIGINT;
244                         PERFORM * FROM authority.record_entry WHERE id = authority_link;
245                         IF FOUND THEN
246                             output_row.authority := authority_link;
247                         END IF;
248                     END IF;
249
250                 END IF;
251
252                 output_row.browse_field = TRUE;
253                 -- Returning browse rows with search_field = true for search+browse
254                 -- configs allows us to retain granularity of being able to search
255                 -- browse fields with "starts with" type operators (for example, for
256                 -- titles of songs in music albums)
257                 IF idx.search_field THEN
258                     output_row.search_field = TRUE;
259                 END IF;
260                 RETURN NEXT output_row;
261                 output_row.browse_field = FALSE;
262                 output_row.search_field = FALSE;
263                 output_row.sort_value := NULL;
264             END IF;
265
266             -- insert raw node text for faceting
267             IF idx.facet_field THEN
268
269                 IF idx.facet_xpath IS NOT NULL AND idx.facet_xpath <> '' THEN
270                     facet_text := oils_xpath_string( idx.facet_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
271                 ELSE
272                     facet_text := curr_text;
273                 END IF;
274
275                 output_row.field_class = idx.field_class;
276                 output_row.field = -1 * idx.id;
277                 output_row.source = rid;
278                 output_row.value = BTRIM(REGEXP_REPLACE(facet_text, E'\\s+', ' ', 'g'));
279
280                 output_row.facet_field = TRUE;
281                 RETURN NEXT output_row;
282                 output_row.facet_field = FALSE;
283             END IF;
284
285             -- insert raw node text for display
286             IF idx.display_field THEN
287
288                 IF idx.display_xpath IS NOT NULL AND idx.display_xpath <> '' THEN
289                     display_text := oils_xpath_string( idx.display_xpath, xml_node, joiner, ARRAY[ARRAY[xfrm.prefix, xfrm.namespace_uri]] );
290                 ELSE
291                     display_text := curr_text;
292                 END IF;
293
294                 output_row.field_class = idx.field_class;
295                 output_row.field = -1 * idx.id;
296                 output_row.source = rid;
297                 output_row.value = BTRIM(REGEXP_REPLACE(display_text, E'\\s+', ' ', 'g'));
298
299                 output_row.display_field = TRUE;
300                 RETURN NEXT output_row;
301                 output_row.display_field = FALSE;
302             END IF;
303
304         END LOOP;
305
306         CONTINUE WHEN raw_text IS NULL OR raw_text = '';
307
308         -- insert combined node text for searching
309         IF idx.search_field THEN
310             output_row.field_class = idx.field_class;
311             output_row.field = idx.id;
312             output_row.source = rid;
313             output_row.value = BTRIM(REGEXP_REPLACE(raw_text, E'\\s+', ' ', 'g'));
314
315             output_row.search_field = TRUE;
316             RETURN NEXT output_row;
317             output_row.search_field = FALSE;
318         END IF;
319
320     END LOOP;
321
322 END;
323
324 $func$ LANGUAGE PLPGSQL;
325
326 DROP FUNCTION metabib.reingest_metabib_field_entries(BIGINT, BOOL, BOOL, BOOL);
327
328 CREATE OR REPLACE FUNCTION metabib.reingest_metabib_field_entries( 
329     bib_id BIGINT, skip_facet BOOL DEFAULT FALSE, 
330     skip_display BOOL DEFAULT FALSE, skip_browse BOOL DEFAULT FALSE, 
331     skip_search BOOL DEFAULT FALSE ) RETURNS VOID AS $func$
332 DECLARE
333     fclass          RECORD;
334     ind_data        metabib.field_entry_template%ROWTYPE;
335     mbe_row         metabib.browse_entry%ROWTYPE;
336     mbe_id          BIGINT;
337     b_skip_facet    BOOL;
338     b_skip_display    BOOL;
339     b_skip_browse   BOOL;
340     b_skip_search   BOOL;
341     value_prepped   TEXT;
342 BEGIN
343
344     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;
345     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;
346     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;
347     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;
348
349     PERFORM * FROM config.internal_flag WHERE name = 'ingest.assume_inserts_only' AND enabled;
350     IF NOT FOUND THEN
351         IF NOT b_skip_search THEN
352             FOR fclass IN SELECT * FROM config.metabib_class LOOP
353                 -- RAISE NOTICE 'Emptying out %', fclass.name;
354                 EXECUTE $$DELETE FROM metabib.$$ || fclass.name || $$_field_entry WHERE source = $$ || bib_id;
355             END LOOP;
356         END IF;
357         IF NOT b_skip_facet THEN
358             DELETE FROM metabib.facet_entry WHERE source = bib_id;
359         END IF;
360         IF NOT b_skip_display THEN
361             DELETE FROM metabib.display_entry WHERE source = bib_id;
362         END IF;
363         IF NOT b_skip_browse THEN
364             DELETE FROM metabib.browse_entry_def_map WHERE source = bib_id;
365         END IF;
366     END IF;
367
368     FOR ind_data IN SELECT * FROM biblio.extract_metabib_field_entry( bib_id ) LOOP
369
370         -- don't store what has been normalized away
371         CONTINUE WHEN ind_data.value IS NULL;
372
373         IF ind_data.field < 0 THEN
374             ind_data.field = -1 * ind_data.field;
375         END IF;
376
377         IF ind_data.facet_field AND NOT b_skip_facet THEN
378             INSERT INTO metabib.facet_entry (field, source, value)
379                 VALUES (ind_data.field, ind_data.source, ind_data.value);
380         END IF;
381
382         IF ind_data.display_field AND NOT b_skip_display THEN
383             INSERT INTO metabib.display_entry (field, source, value)
384                 VALUES (ind_data.field, ind_data.source, ind_data.value);
385         END IF;
386
387
388         IF ind_data.browse_field AND NOT b_skip_browse THEN
389             -- A caveat about this SELECT: this should take care of replacing
390             -- old mbe rows when data changes, but not if normalization (by
391             -- which I mean specifically the output of
392             -- evergreen.oils_tsearch2()) changes.  It may or may not be
393             -- expensive to add a comparison of index_vector to index_vector
394             -- to the WHERE clause below.
395
396             CONTINUE WHEN ind_data.sort_value IS NULL;
397
398             value_prepped := metabib.browse_normalize(ind_data.value, ind_data.field);
399             SELECT INTO mbe_row * FROM metabib.browse_entry
400                 WHERE value = value_prepped AND sort_value = ind_data.sort_value;
401
402             IF FOUND THEN
403                 mbe_id := mbe_row.id;
404             ELSE
405                 INSERT INTO metabib.browse_entry
406                     ( value, sort_value ) VALUES
407                     ( value_prepped, ind_data.sort_value );
408
409                 mbe_id := CURRVAL('metabib.browse_entry_id_seq'::REGCLASS);
410             END IF;
411
412             INSERT INTO metabib.browse_entry_def_map (entry, def, source, authority)
413                 VALUES (mbe_id, ind_data.field, ind_data.source, ind_data.authority);
414         END IF;
415
416         IF ind_data.search_field AND NOT b_skip_search THEN
417             -- Avoid inserting duplicate rows
418             EXECUTE 'SELECT 1 FROM metabib.' || ind_data.field_class ||
419                 '_field_entry WHERE field = $1 AND source = $2 AND value = $3'
420                 INTO mbe_id USING ind_data.field, ind_data.source, ind_data.value;
421                 -- RAISE NOTICE 'Search for an already matching row returned %', mbe_id;
422             IF mbe_id IS NULL THEN
423                 EXECUTE $$
424                 INSERT INTO metabib.$$ || ind_data.field_class || $$_field_entry (field, source, value)
425                     VALUES ($$ ||
426                         quote_literal(ind_data.field) || $$, $$ ||
427                         quote_literal(ind_data.source) || $$, $$ ||
428                         quote_literal(ind_data.value) ||
429                     $$);$$;
430             END IF;
431         END IF;
432
433     END LOOP;
434
435     IF NOT b_skip_search THEN
436         PERFORM metabib.update_combined_index_vectors(bib_id);
437     END IF;
438
439     RETURN;
440 END;
441 $func$ LANGUAGE PLPGSQL;
442
443 COMMIT;
444
445
446