Skip to content

Commit f04c279

Browse files
committed
fix: ensure tsquery config usages are consistent
1 parent 12bcdd5 commit f04c279

File tree

9 files changed

+418
-22
lines changed

9 files changed

+418
-22
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
SET client_min_messages TO WARNING;
2+
SET SEARCH_PATH to pgstac, public;
3+
RESET ROLE;
4+
DO $$
5+
DECLARE
6+
BEGIN
7+
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname='postgis') THEN
8+
CREATE EXTENSION IF NOT EXISTS postgis;
9+
END IF;
10+
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname='btree_gist') THEN
11+
CREATE EXTENSION IF NOT EXISTS btree_gist;
12+
END IF;
13+
IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname='unaccent') THEN
14+
CREATE EXTENSION IF NOT EXISTS unaccent;
15+
END IF;
16+
END;
17+
$$ LANGUAGE PLPGSQL;
18+
19+
DO $$
20+
BEGIN
21+
CREATE ROLE pgstac_admin;
22+
EXCEPTION WHEN duplicate_object THEN
23+
RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
24+
END
25+
$$;
26+
27+
DO $$
28+
BEGIN
29+
CREATE ROLE pgstac_read;
30+
EXCEPTION WHEN duplicate_object THEN
31+
RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
32+
END
33+
$$;
34+
35+
DO $$
36+
BEGIN
37+
CREATE ROLE pgstac_ingest;
38+
EXCEPTION WHEN duplicate_object THEN
39+
RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
40+
END
41+
$$;
42+
43+
44+
GRANT pgstac_admin TO current_user;
45+
46+
-- Function to make sure pgstac_admin is the owner of items
47+
CREATE OR REPLACE FUNCTION pgstac_admin_owns() RETURNS VOID AS $$
48+
DECLARE
49+
f RECORD;
50+
BEGIN
51+
FOR f IN (
52+
SELECT
53+
concat(
54+
oid::regproc::text,
55+
'(',
56+
coalesce(pg_get_function_identity_arguments(oid),''),
57+
')'
58+
) AS name,
59+
CASE prokind WHEN 'f' THEN 'FUNCTION' WHEN 'p' THEN 'PROCEDURE' WHEN 'a' THEN 'AGGREGATE' END as typ
60+
FROM pg_proc
61+
WHERE
62+
pronamespace=to_regnamespace('pgstac')
63+
AND proowner != to_regrole('pgstac_admin')
64+
AND proname NOT LIKE 'pg_stat%'
65+
)
66+
LOOP
67+
BEGIN
68+
EXECUTE format('ALTER %s %s OWNER TO pgstac_admin;', f.typ, f.name);
69+
EXCEPTION WHEN others THEN
70+
RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
71+
END;
72+
END LOOP;
73+
FOR f IN (
74+
SELECT
75+
oid::regclass::text as name,
76+
CASE relkind
77+
WHEN 'i' THEN 'INDEX'
78+
WHEN 'I' THEN 'INDEX'
79+
WHEN 'p' THEN 'TABLE'
80+
WHEN 'r' THEN 'TABLE'
81+
WHEN 'v' THEN 'VIEW'
82+
WHEN 'S' THEN 'SEQUENCE'
83+
ELSE NULL
84+
END as typ
85+
FROM pg_class
86+
WHERE relnamespace=to_regnamespace('pgstac') and relowner != to_regrole('pgstac_admin') AND relkind IN ('r','p','v','S') AND relname NOT LIKE 'pg_stat'
87+
)
88+
LOOP
89+
BEGIN
90+
EXECUTE format('ALTER %s %s OWNER TO pgstac_admin;', f.typ, f.name);
91+
EXCEPTION WHEN others THEN
92+
RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
93+
END;
94+
END LOOP;
95+
RETURN;
96+
END;
97+
$$ LANGUAGE PLPGSQL;
98+
SELECT pgstac_admin_owns();
99+
100+
CREATE SCHEMA IF NOT EXISTS pgstac AUTHORIZATION pgstac_admin;
101+
102+
GRANT ALL ON ALL FUNCTIONS IN SCHEMA pgstac to pgstac_admin;
103+
GRANT ALL ON ALL TABLES IN SCHEMA pgstac to pgstac_admin;
104+
GRANT ALL ON ALL SEQUENCES IN SCHEMA pgstac to pgstac_admin;
105+
106+
ALTER ROLE pgstac_admin SET SEARCH_PATH TO pgstac, public;
107+
ALTER ROLE pgstac_read SET SEARCH_PATH TO pgstac, public;
108+
ALTER ROLE pgstac_ingest SET SEARCH_PATH TO pgstac, public;
109+
110+
GRANT USAGE ON SCHEMA pgstac to pgstac_read;
111+
ALTER DEFAULT PRIVILEGES IN SCHEMA pgstac GRANT SELECT ON TABLES TO pgstac_read;
112+
ALTER DEFAULT PRIVILEGES IN SCHEMA pgstac GRANT USAGE ON TYPES TO pgstac_read;
113+
ALTER DEFAULT PRIVILEGES IN SCHEMA pgstac GRANT ALL ON SEQUENCES TO pgstac_read;
114+
115+
GRANT pgstac_read TO pgstac_ingest;
116+
GRANT ALL ON SCHEMA pgstac TO pgstac_ingest;
117+
ALTER DEFAULT PRIVILEGES IN SCHEMA pgstac GRANT ALL ON TABLES TO pgstac_ingest;
118+
ALTER DEFAULT PRIVILEGES IN SCHEMA pgstac GRANT ALL ON FUNCTIONS TO pgstac_ingest;
119+
120+
SET ROLE pgstac_admin;
121+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_admin IN SCHEMA pgstac GRANT SELECT ON TABLES TO pgstac_read;
122+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_admin IN SCHEMA pgstac GRANT USAGE ON TYPES TO pgstac_read;
123+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_admin IN SCHEMA pgstac GRANT ALL ON SEQUENCES TO pgstac_read;
124+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_admin IN SCHEMA pgstac GRANT ALL ON TABLES TO pgstac_ingest;
125+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_admin IN SCHEMA pgstac GRANT ALL ON FUNCTIONS TO pgstac_ingest;
126+
RESET ROLE;
127+
128+
SET ROLE pgstac_ingest;
129+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_ingest IN SCHEMA pgstac GRANT SELECT ON TABLES TO pgstac_read;
130+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_ingest IN SCHEMA pgstac GRANT USAGE ON TYPES TO pgstac_read;
131+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_ingest IN SCHEMA pgstac GRANT ALL ON SEQUENCES TO pgstac_read;
132+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_ingest IN SCHEMA pgstac GRANT ALL ON TABLES TO pgstac_ingest;
133+
ALTER DEFAULT PRIVILEGES FOR ROLE pgstac_ingest IN SCHEMA pgstac GRANT ALL ON FUNCTIONS TO pgstac_ingest;
134+
RESET ROLE;
135+
136+
SET SEARCH_PATH TO pgstac, public;
137+
SET ROLE pgstac_admin;
138+
139+
DO $$
140+
BEGIN
141+
DROP FUNCTION IF EXISTS analyze_items;
142+
EXCEPTION WHEN others THEN
143+
RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
144+
END
145+
$$;
146+
DO $$
147+
BEGIN
148+
DROP FUNCTION IF EXISTS validate_constraints;
149+
EXCEPTION WHEN others THEN
150+
RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
151+
END
152+
$$;
153+
154+
-- Install these idempotently as migrations do not put them before trying to modify the collections table
155+
156+
157+
CREATE OR REPLACE FUNCTION collection_geom(content jsonb)
158+
RETURNS geometry AS $$
159+
WITH box AS (SELECT content->'extent'->'spatial'->'bbox'->0 as box)
160+
SELECT
161+
st_makeenvelope(
162+
(box->>0)::float,
163+
(box->>1)::float,
164+
(box->>2)::float,
165+
(box->>3)::float,
166+
4326
167+
)
168+
FROM box;
169+
$$ LANGUAGE SQL IMMUTABLE STRICT;
170+
171+
CREATE OR REPLACE FUNCTION collection_datetime(content jsonb)
172+
RETURNS timestamptz AS $$
173+
SELECT
174+
CASE
175+
WHEN
176+
(content->'extent'->'temporal'->'interval'->0->>0) IS NULL
177+
THEN '-infinity'::timestamptz
178+
ELSE
179+
(content->'extent'->'temporal'->'interval'->0->>0)::timestamptz
180+
END
181+
;
182+
$$ LANGUAGE SQL IMMUTABLE STRICT;
183+
184+
CREATE OR REPLACE FUNCTION collection_enddatetime(content jsonb)
185+
RETURNS timestamptz AS $$
186+
SELECT
187+
CASE
188+
WHEN
189+
(content->'extent'->'temporal'->'interval'->0->>1) IS NULL
190+
THEN 'infinity'::timestamptz
191+
ELSE
192+
(content->'extent'->'temporal'->'interval'->0->>1)::timestamptz
193+
END
194+
;
195+
$$ LANGUAGE SQL IMMUTABLE STRICT;
196+
-- BEGIN migra calculated SQL
197+
set check_function_bodies = off;
198+
199+
CREATE OR REPLACE FUNCTION pgstac.q_to_tsquery(input text)
200+
RETURNS tsquery
201+
LANGUAGE plpgsql
202+
AS $function$
203+
DECLARE
204+
processed_text text;
205+
temp_text text;
206+
quote_array text[];
207+
placeholder text := '@QUOTE@';
208+
BEGIN
209+
-- Extract all quoted phrases and store in array
210+
quote_array := regexp_matches(input, '"[^"]*"', 'g');
211+
212+
-- Replace each quoted part with a unique placeholder if there are any quoted phrases
213+
IF array_length(quote_array, 1) IS NOT NULL THEN
214+
processed_text := input;
215+
FOR i IN array_lower(quote_array, 1) .. array_upper(quote_array, 1) LOOP
216+
processed_text := replace(processed_text, quote_array[i], placeholder || i || placeholder);
217+
END LOOP;
218+
ELSE
219+
processed_text := input;
220+
END IF;
221+
222+
-- Replace non-quoted text using regular expressions
223+
224+
-- , -> |
225+
processed_text := regexp_replace(processed_text, ',(?=(?:[^"]*"[^"]*")*[^"]*$)', ' | ', 'g');
226+
227+
-- and -> &
228+
processed_text := regexp_replace(processed_text, '\s+AND\s+', ' & ', 'gi');
229+
230+
-- or -> |
231+
processed_text := regexp_replace(processed_text, '\s+OR\s+', ' | ', 'gi');
232+
233+
-- + ->
234+
processed_text := regexp_replace(processed_text, '^\s*\+([a-zA-Z0-9_]+)', '\1', 'g'); -- +term at start
235+
processed_text := regexp_replace(processed_text, '\s*\+([a-zA-Z0-9_]+)', ' & \1', 'g'); -- +term elsewhere
236+
237+
-- - -> !
238+
processed_text := regexp_replace(processed_text, '^\s*\-([a-zA-Z0-9_]+)', '! \1', 'g'); -- -term at start
239+
processed_text := regexp_replace(processed_text, '\s*\-([a-zA-Z0-9_]+)', ' & ! \1', 'g'); -- -term elsewhere
240+
-- Replace placeholders back with quoted phrases if there were any
241+
IF array_length(quote_array, 1) IS NOT NULL THEN
242+
FOR i IN array_lower(quote_array, 1) .. array_upper(quote_array, 1) LOOP
243+
processed_text := replace(processed_text, placeholder || i || placeholder, '''' || substring(quote_array[i] from 2 for length(quote_array[i]) - 2) || '''');
244+
END LOOP;
245+
END IF;
246+
247+
-- Print processed_text to the console for debugging purposes
248+
RAISE NOTICE 'processed_text: %', processed_text;
249+
250+
RETURN to_tsquery('english', processed_text);
251+
END;
252+
$function$
253+
;
254+
255+
256+
-- END migra calculated SQL
257+
DO $$
258+
BEGIN
259+
INSERT INTO queryables (name, definition, property_wrapper, property_index_type) VALUES
260+
('id', '{"title": "Item ID","description": "Item identifier","$ref": "https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json#/definitions/core/allOf/2/properties/id"}', null, null);
261+
EXCEPTION WHEN unique_violation THEN
262+
RAISE NOTICE '%', SQLERRM USING ERRCODE = SQLSTATE;
263+
END
264+
$$;
265+
266+
DO $$
267+
BEGIN
268+
INSERT INTO queryables (name, definition, property_wrapper, property_index_type) VALUES
269+
('geometry', '{"title": "Item Geometry","description": "Item Geometry","$ref": "https://geojson.org/schema/Feature.json"}', null, null);
270+
EXCEPTION WHEN unique_violation THEN
271+
RAISE NOTICE '%', SQLERRM USING ERRCODE = SQLSTATE;
272+
END
273+
$$;
274+
275+
DO $$
276+
BEGIN
277+
INSERT INTO queryables (name, definition, property_wrapper, property_index_type) VALUES
278+
('datetime','{"description": "Datetime","type": "string","title": "Acquired","format": "date-time","pattern": "(\\+00:00|Z)$"}', null, null);
279+
EXCEPTION WHEN unique_violation THEN
280+
RAISE NOTICE '%', SQLERRM USING ERRCODE = SQLSTATE;
281+
END
282+
$$;
283+
284+
DELETE FROM queryables a USING queryables b
285+
WHERE a.name = b.name AND a.collection_ids IS NOT DISTINCT FROM b.collection_ids AND a.id > b.id;
286+
287+
288+
INSERT INTO pgstac_settings (name, value) VALUES
289+
('context', 'off'),
290+
('context_estimated_count', '100000'),
291+
('context_estimated_cost', '100000'),
292+
('context_stats_ttl', '1 day'),
293+
('default_filter_lang', 'cql2-json'),
294+
('additional_properties', 'true'),
295+
('use_queue', 'false'),
296+
('queue_timeout', '10 minutes'),
297+
('update_collection_extent', 'false'),
298+
('format_cache', 'false'),
299+
('readonly', 'false')
300+
ON CONFLICT DO NOTHING
301+
;
302+
303+
304+
INSERT INTO cql2_ops (op, template, types) VALUES
305+
('eq', '%s = %s', NULL),
306+
('neq', '%s != %s', NULL),
307+
('ne', '%s != %s', NULL),
308+
('!=', '%s != %s', NULL),
309+
('<>', '%s != %s', NULL),
310+
('lt', '%s < %s', NULL),
311+
('lte', '%s <= %s', NULL),
312+
('gt', '%s > %s', NULL),
313+
('gte', '%s >= %s', NULL),
314+
('le', '%s <= %s', NULL),
315+
('ge', '%s >= %s', NULL),
316+
('=', '%s = %s', NULL),
317+
('<', '%s < %s', NULL),
318+
('<=', '%s <= %s', NULL),
319+
('>', '%s > %s', NULL),
320+
('>=', '%s >= %s', NULL),
321+
('like', '%s LIKE %s', NULL),
322+
('ilike', '%s ILIKE %s', NULL),
323+
('+', '%s + %s', NULL),
324+
('-', '%s - %s', NULL),
325+
('*', '%s * %s', NULL),
326+
('/', '%s / %s', NULL),
327+
('not', 'NOT (%s)', NULL),
328+
('between', '%s BETWEEN %s AND %s', NULL),
329+
('isnull', '%s IS NULL', NULL),
330+
('upper', 'upper(%s)', NULL),
331+
('lower', 'lower(%s)', NULL),
332+
('casei', 'upper(%s)', NULL),
333+
('accenti', 'unaccent(%s)', NULL)
334+
ON CONFLICT (op) DO UPDATE
335+
SET
336+
template = EXCLUDED.template
337+
;
338+
339+
340+
ALTER FUNCTION to_text COST 5000;
341+
ALTER FUNCTION to_float COST 5000;
342+
ALTER FUNCTION to_int COST 5000;
343+
ALTER FUNCTION to_tstz COST 5000;
344+
ALTER FUNCTION to_text_array COST 5000;
345+
346+
ALTER FUNCTION update_partition_stats SECURITY DEFINER;
347+
ALTER FUNCTION partition_after_triggerfunc SECURITY DEFINER;
348+
ALTER FUNCTION drop_table_constraints SECURITY DEFINER;
349+
ALTER FUNCTION create_table_constraints SECURITY DEFINER;
350+
ALTER FUNCTION check_partition SECURITY DEFINER;
351+
ALTER FUNCTION repartition SECURITY DEFINER;
352+
ALTER FUNCTION where_stats SECURITY DEFINER;
353+
ALTER FUNCTION search_query SECURITY DEFINER;
354+
ALTER FUNCTION format_item SECURITY DEFINER;
355+
ALTER FUNCTION maintain_index SECURITY DEFINER;
356+
357+
GRANT USAGE ON SCHEMA pgstac to pgstac_read;
358+
GRANT ALL ON SCHEMA pgstac to pgstac_ingest;
359+
GRANT ALL ON SCHEMA pgstac to pgstac_admin;
360+
361+
-- pgstac_read role limited to using function apis
362+
GRANT EXECUTE ON FUNCTION search TO pgstac_read;
363+
GRANT EXECUTE ON FUNCTION search_query TO pgstac_read;
364+
GRANT EXECUTE ON FUNCTION item_by_id TO pgstac_read;
365+
GRANT EXECUTE ON FUNCTION get_item TO pgstac_read;
366+
GRANT SELECT ON ALL TABLES IN SCHEMA pgstac TO pgstac_read;
367+
368+
369+
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA pgstac to pgstac_ingest;
370+
GRANT ALL ON ALL TABLES IN SCHEMA pgstac to pgstac_ingest;
371+
GRANT USAGE ON ALL SEQUENCES IN SCHEMA pgstac to pgstac_ingest;
372+
373+
REVOKE ALL PRIVILEGES ON PROCEDURE run_queued_queries FROM public;
374+
GRANT ALL ON PROCEDURE run_queued_queries TO pgstac_admin;
375+
376+
REVOKE ALL PRIVILEGES ON FUNCTION run_queued_queries_intransaction FROM public;
377+
GRANT ALL ON FUNCTION run_queued_queries_intransaction TO pgstac_admin;
378+
379+
RESET ROLE;
380+
381+
SET ROLE pgstac_ingest;
382+
SELECT update_partition_stats_q(partition) FROM partitions_view;
383+
SELECT set_version('unreleased');

0 commit comments

Comments
 (0)