LP#1638299: rename schema update scripts to clarify their proper order
[working/Evergreen.git] / Open-ILS / src / sql / Pg / upgrade / XXX4.schema.browse_uses_ahf.sql
1 BEGIN;
2
3 CREATE OR REPLACE FUNCTION metabib.staged_browse(query text, fields integer[], context_org integer, context_locations integer[], staff boolean, browse_superpage_size integer, count_up_from_zero boolean, result_limit integer, next_pivot_pos integer)
4  RETURNS SETOF metabib.flat_browse_entry_appearance
5 AS $f$
6 DECLARE
7     curs                    REFCURSOR;
8     rec                     RECORD;
9     qpfts_query             TEXT;
10     aqpfts_query            TEXT;
11     afields                 INT[];
12     bfields                 INT[];
13     result_row              metabib.flat_browse_entry_appearance%ROWTYPE;
14     results_skipped         INT := 0;
15     row_counter             INT := 0;
16     row_number              INT;
17     slice_start             INT;
18     slice_end               INT;
19     full_end                INT;
20     all_records             BIGINT[];
21     all_brecords             BIGINT[];
22     all_arecords            BIGINT[];
23     superpage_of_records    BIGINT[];
24     superpage_size          INT;
25     c_tests                 TEXT := '';
26     b_tests                 TEXT := '';
27     c_orgs                  INT[];
28     unauthorized_entry      RECORD;
29 BEGIN
30     IF count_up_from_zero THEN
31         row_number := 0;
32     ELSE
33         row_number := -1;
34     END IF;
35
36     IF NOT staff THEN
37         SELECT x.c_attrs, x.b_attrs INTO c_tests, b_tests FROM asset.patron_default_visibility_mask() x;
38     END IF;
39
40     IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF;
41     IF b_tests <> '' THEN b_tests := b_tests || '&'; END IF;
42
43     SELECT ARRAY_AGG(id) INTO c_orgs FROM actor.org_unit_descendants(context_org);
44
45     c_tests := c_tests || search.calculate_visibility_attribute_test('circ_lib',c_orgs)
46                || '&' || search.calculate_visibility_attribute_test('owning_lib',c_orgs);
47
48     PERFORM 1 FROM config.internal_flag WHERE enabled AND name = 'opac.located_uri.act_as_copy';
49     IF FOUND THEN
50         b_tests := b_tests || search.calculate_visibility_attribute_test(
51             'luri_org',
52             (SELECT ARRAY_AGG(id) FROM actor.org_unit_full_path(context_org) x)
53         );
54     ELSE
55         b_tests := b_tests || search.calculate_visibility_attribute_test(
56             'luri_org',
57             (SELECT ARRAY_AGG(id) FROM actor.org_unit_ancestors(context_org) x)
58         );
59     END IF;
60
61     IF context_locations THEN
62         IF c_tests <> '' THEN c_tests := c_tests || '&'; END IF;
63         c_tests := c_tests || search.calculate_visibility_attribute_test('location',context_locations);
64     END IF;
65
66     OPEN curs NO SCROLL FOR EXECUTE query;
67
68     LOOP
69         FETCH curs INTO rec;
70         IF NOT FOUND THEN
71             IF result_row.pivot_point IS NOT NULL THEN
72                 RETURN NEXT result_row;
73             END IF;
74             RETURN;
75         END IF;
76
77         --Is unauthorized?
78         SELECT INTO unauthorized_entry *
79         FROM metabib.browse_entry_simple_heading_map mbeshm
80         INNER JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
81         INNER JOIN authority.control_set_authority_field acsaf ON ( acsaf.id = ash.atag )
82         JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field)
83         WHERE mbeshm.entry = rec.id
84         AND   ahf.heading_purpose = 'variant';
85
86         -- Gather aggregate data based on the MBE row we're looking at now, authority axis
87         IF (unauthorized_entry.record IS NOT NULL) THEN
88             --unauthorized term belongs to an auth linked to a bib?
89             SELECT INTO all_arecords, result_row.sees, afields
90                     ARRAY_AGG(DISTINCT abl.bib),
91                     STRING_AGG(DISTINCT abl.authority::TEXT, $$,$$),
92                     ARRAY_AGG(DISTINCT map.metabib_field)
93             FROM authority.bib_linking abl
94             INNER JOIN authority.control_set_auth_field_metabib_field_map_refs map ON (
95                     map.authority_field = unauthorized_entry.atag
96                     AND map.metabib_field = ANY(fields)
97             )
98             WHERE abl.authority = unauthorized_entry.record;
99         ELSE
100             --do usual procedure
101             SELECT INTO all_arecords, result_row.sees, afields
102                     ARRAY_AGG(DISTINCT abl.bib), -- bibs to check for visibility
103                     STRING_AGG(DISTINCT aal.source::TEXT, $$,$$), -- authority record ids
104                     ARRAY_AGG(DISTINCT map.metabib_field) -- authority-tag-linked CMF rows
105
106             FROM  metabib.browse_entry_simple_heading_map mbeshm
107                     JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
108                     JOIN authority.authority_linking aal ON ( ash.record = aal.source )
109                     JOIN authority.bib_linking abl ON ( aal.target = abl.authority )
110                     JOIN authority.control_set_auth_field_metabib_field_map_refs map ON (
111                         ash.atag = map.authority_field
112                         AND map.metabib_field = ANY(fields)
113                     )
114                     JOIN authority.control_set_authority_field acsaf ON (
115                         map.authority_field = acsaf.id
116                     )
117                     JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field)
118               WHERE mbeshm.entry = rec.id
119               AND   ahf.heading_purpose = 'variant';
120
121         END IF;
122
123         -- Gather aggregate data based on the MBE row we're looking at now, bib axis
124         SELECT INTO all_brecords, result_row.authorities, bfields
125                 ARRAY_AGG(DISTINCT source),
126                 STRING_AGG(DISTINCT authority::TEXT, $$,$$),
127                 ARRAY_AGG(DISTINCT def)
128           FROM  metabib.browse_entry_def_map
129           WHERE entry = rec.id
130                 AND def = ANY(fields);
131
132         SELECT INTO result_row.fields STRING_AGG(DISTINCT x::TEXT, $$,$$) FROM UNNEST(afields || bfields) x;
133
134         result_row.sources := 0;
135         result_row.asources := 0;
136
137         -- Bib-linked vis checking
138         IF ARRAY_UPPER(all_brecords,1) IS NOT NULL THEN
139
140             SELECT  INTO result_row.sources COUNT(DISTINCT b.id)
141               FROM  biblio.record_entry b
142                     JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id)
143               WHERE b.id = ANY(all_brecords[1:browse_superpage_size])
144                     AND (
145                         acvac.vis_attr_vector @@ c_tests::query_int
146                         OR b.vis_attr_vector @@ b_tests::query_int
147                     );
148
149             result_row.accurate := TRUE;
150
151         END IF;
152
153         -- Authority-linked vis checking
154         IF ARRAY_UPPER(all_arecords,1) IS NOT NULL THEN
155
156             SELECT  INTO result_row.asources COUNT(DISTINCT b.id)
157               FROM  biblio.record_entry b
158                     JOIN asset.copy_vis_attr_cache acvac ON (acvac.record = b.id)
159               WHERE b.id = ANY(all_arecords[1:browse_superpage_size])
160                     AND (
161                         acvac.vis_attr_vector @@ c_tests::query_int
162                         OR b.vis_attr_vector @@ b_tests::query_int
163                     );
164
165             result_row.aaccurate := TRUE;
166
167         END IF;
168
169         IF result_row.sources > 0 OR result_row.asources > 0 THEN
170
171             -- The function that calls this function needs row_number in order
172             -- to correctly order results from two different runs of this
173             -- functions.
174             result_row.row_number := row_number;
175
176             -- Now, if row_counter is still less than limit, return a row.  If
177             -- not, but it is less than next_pivot_pos, continue on without
178             -- returning actual result rows until we find
179             -- that next pivot, and return it.
180
181             IF row_counter < result_limit THEN
182                 result_row.browse_entry := rec.id;
183                 result_row.value := rec.value;
184
185                 RETURN NEXT result_row;
186             ELSE
187                 result_row.browse_entry := NULL;
188                 result_row.authorities := NULL;
189                 result_row.fields := NULL;
190                 result_row.value := NULL;
191                 result_row.sources := NULL;
192                 result_row.sees := NULL;
193                 result_row.accurate := NULL;
194                 result_row.aaccurate := NULL;
195                 result_row.pivot_point := rec.id;
196
197                 IF row_counter >= next_pivot_pos THEN
198                     RETURN NEXT result_row;
199                     RETURN;
200                 END IF;
201             END IF;
202
203             IF count_up_from_zero THEN
204                 row_number := row_number + 1;
205             ELSE
206                 row_number := row_number - 1;
207             END IF;
208
209             -- row_counter is different from row_number.
210             -- It simply counts up from zero so that we know when
211             -- we've reached our limit.
212             row_counter := row_counter + 1;
213         END IF;
214     END LOOP;
215 END;
216 $f$ LANGUAGE plpgsql ROWS 10;
217
218 CREATE OR REPLACE FUNCTION metabib.browse(search_field integer[], browse_term text, context_org integer DEFAULT NULL::integer, context_loc_group integer DEFAULT NULL::integer, staff boolean DEFAULT false, pivot_id bigint DEFAULT NULL::bigint, result_limit integer DEFAULT 10)
219  RETURNS SETOF metabib.flat_browse_entry_appearance
220 AS $f$
221 DECLARE
222     core_query              TEXT;
223     back_query              TEXT;
224     forward_query           TEXT;
225     pivot_sort_value        TEXT;
226     pivot_sort_fallback     TEXT;
227     context_locations       INT[];
228     browse_superpage_size   INT;
229     results_skipped         INT := 0;
230     back_limit              INT;
231     back_to_pivot           INT;
232     forward_limit           INT;
233     forward_to_pivot        INT;
234 BEGIN
235     -- First, find the pivot if we were given a browse term but not a pivot.
236     IF pivot_id IS NULL THEN
237         pivot_id := metabib.browse_pivot(search_field, browse_term);
238     END IF;
239
240     SELECT INTO pivot_sort_value, pivot_sort_fallback
241         sort_value, value FROM metabib.browse_entry WHERE id = pivot_id;
242
243     -- Bail if we couldn't find a pivot.
244     IF pivot_sort_value IS NULL THEN
245         RETURN;
246     END IF;
247
248     -- Transform the context_loc_group argument (if any) (logc at the
249     -- TPAC layer) into a form we'll be able to use.
250     IF context_loc_group IS NOT NULL THEN
251         SELECT INTO context_locations ARRAY_AGG(location)
252             FROM asset.copy_location_group_map
253             WHERE lgroup = context_loc_group;
254     END IF;
255
256     -- Get the configured size of browse superpages.
257     SELECT INTO browse_superpage_size COALESCE(value::INT,100)     -- NULL ok
258         FROM config.global_flag
259         WHERE enabled AND name = 'opac.browse.holdings_visibility_test_limit';
260
261     -- First we're going to search backward from the pivot, then we're going
262     -- to search forward.  In each direction, we need two limits.  At the
263     -- lesser of the two limits, we delineate the edge of the result set
264     -- we're going to return.  At the greater of the two limits, we find the
265     -- pivot value that would represent an offset from the current pivot
266     -- at a distance of one "page" in either direction, where a "page" is a
267     -- result set of the size specified in the "result_limit" argument.
268     --
269     -- The two limits in each direction make four derived values in total,
270     -- and we calculate them now.
271     back_limit := CEIL(result_limit::FLOAT / 2);
272     back_to_pivot := result_limit;
273     forward_limit := result_limit / 2;
274     forward_to_pivot := result_limit - 1;
275
276     -- This is the meat of the SQL query that finds browse entries.  We'll
277     -- pass this to a function which uses it with a cursor, so that individual
278     -- rows may be fetched in a loop until some condition is satisfied, without
279     -- waiting for a result set of fixed size to be collected all at once.
280     core_query := '
281 SELECT  mbe.id,
282         mbe.value,
283         mbe.sort_value
284   FROM  metabib.browse_entry mbe
285   WHERE (
286             EXISTS ( -- are there any bibs using this mbe via the requested fields?
287                 SELECT  1
288                   FROM  metabib.browse_entry_def_map mbedm
289                   WHERE mbedm.entry = mbe.id AND mbedm.def = ANY(' || quote_literal(search_field) || ')
290             ) OR EXISTS ( -- are there any authorities using this mbe via the requested fields?
291                 SELECT  1
292                   FROM  metabib.browse_entry_simple_heading_map mbeshm
293                         JOIN authority.simple_heading ash ON ( mbeshm.simple_heading = ash.id )
294                         JOIN authority.control_set_auth_field_metabib_field_map_refs map ON (
295                             ash.atag = map.authority_field
296                             AND map.metabib_field = ANY(' || quote_literal(search_field) || ')
297                         )
298                         JOIN authority.control_set_authority_field acsaf ON (
299                             map.authority_field = acsaf.id
300                         )
301                         JOIN authority.heading_field ahf ON (ahf.id = acsaf.heading_field)
302                   WHERE mbeshm.entry = mbe.id
303                     AND ahf.heading_purpose IN (' || $$'variant'$$ || ')
304                     -- and authority that variant is coming from is linked to a bib
305                     AND EXISTS (
306                         SELECT  1
307                         FROM  metabib.browse_entry_def_map mbedm2
308                         WHERE mbedm2.authority = ash.record AND mbedm2.def = ANY(' || quote_literal(search_field) || ')
309                     )
310             )
311         ) AND ';
312
313     -- This is the variant of the query for browsing backward.
314     back_query := core_query ||
315         ' mbe.sort_value <= ' || quote_literal(pivot_sort_value) ||
316     ' ORDER BY mbe.sort_value DESC, mbe.value DESC LIMIT 1000';
317
318     -- This variant browses forward.
319     forward_query := core_query ||
320         ' mbe.sort_value > ' || quote_literal(pivot_sort_value) ||
321     ' ORDER BY mbe.sort_value, mbe.value LIMIT 1000';
322
323     -- We now call the function which applies a cursor to the provided
324     -- queries, stopping at the appropriate limits and also giving us
325     -- the next page's pivot.
326     RETURN QUERY
327         SELECT * FROM metabib.staged_browse(
328             back_query, search_field, context_org, context_locations,
329             staff, browse_superpage_size, TRUE, back_limit, back_to_pivot
330         ) UNION
331         SELECT * FROM metabib.staged_browse(
332             forward_query, search_field, context_org, context_locations,
333             staff, browse_superpage_size, FALSE, forward_limit, forward_to_pivot
334         ) ORDER BY row_number DESC;
335
336 END;
337 $f$ LANGUAGE plpgsql ROWS 10;
338
339 COMMIT;