Skip to content

Commit cb760fb

Browse files
authored
Merge pull request #13 from stac-utils/search_bugfixes
Search bugfixes
2 parents 6c57452 + 407a8e6 commit cb760fb

File tree

8 files changed

+1463
-14
lines changed

8 files changed

+1463
-14
lines changed

Dockerfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ RUN \
2525
python3-pip \
2626
python3-setuptools \
2727
# git \
28-
&& pip3 install -U pip setuptools packaging migra[pg] \
29-
&& apt-get remove -y apt-transport-https software-properties-common build-essential python3-pip python3-setuptools \
28+
&& pip3 install -U pip setuptools packaging \
29+
&& pip3 install -U psycopg2-binary \
30+
&& pip3 install -U migra[pg] \
31+
&& apt-get remove -y apt-transport-https software-properties-common build-essential python3-pip python3-dev python3-setuptools \
3032
&& apt-get -y autoremove \
3133
&& rm -rf /var/lib/apt/lists/*
3234

Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,18 @@ install-pypgstac: build-pypgstac
2323
cd pypgstac; \
2424
pip install -U dist/pypgstac-${VERSION}-py3-none-any.whl; \
2525

26+
.PHONY: push-git-tag
27+
push-git-tag:
28+
git tag -a v${VERSION}; \
29+
git push origin v${VERSION}
30+
31+
2632
.PHONY: publish-pypgstac
27-
publish-pypgstac: build-pypgstac
33+
publish-pypgstac: build-pypgstac push-git-tag
2834
cd pypgstac; \
2935
poetry publish
3036

31-
.PHONY: docker-repo
37+
.PHONY: docker-repo push-git-tag
3238
docker-repo:
3339
[ -z "${DOCKER_REPO}" ] && { echo "DOCKER_REPO variable must be set"; exit 1; } || echo "Setting verstion to ${DOCKER_REPO}"
3440

pypgstac/pypgstac/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.2.4'
1+
__version__ = '0.2.5'
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
SET SEARCH_PATH TO pgstac, public;
2+
BEGIN;
3+
CREATE OR REPLACE FUNCTION bbox_geom(_bbox jsonb) RETURNS geometry AS $$
4+
SELECT CASE jsonb_array_length(_bbox)
5+
WHEN 4 THEN
6+
ST_SetSRID(ST_MakeEnvelope(
7+
(_bbox->>0)::float,
8+
(_bbox->>1)::float,
9+
(_bbox->>2)::float,
10+
(_bbox->>3)::float
11+
),4326)
12+
WHEN 6 THEN
13+
ST_SetSRID(ST_3DMakeBox(
14+
ST_MakePoint(
15+
(_bbox->>0)::float,
16+
(_bbox->>1)::float,
17+
(_bbox->>2)::float
18+
),
19+
ST_MakePoint(
20+
(_bbox->>3)::float,
21+
(_bbox->>4)::float,
22+
(_bbox->>5)::float
23+
)
24+
),4326)
25+
ELSE null END;
26+
;
27+
$$ LANGUAGE SQL IMMUTABLE PARALLEL SAFE;
28+
29+
30+
CREATE OR REPLACE FUNCTION search(_search jsonb = '{}'::jsonb) RETURNS SETOF jsonb AS $$
31+
DECLARE
32+
qstart timestamptz := clock_timestamp();
33+
_sort text := '';
34+
_rsort text := '';
35+
_limit int := 10;
36+
_geom geometry;
37+
qa text[];
38+
pq text[];
39+
query text;
40+
pq_prop record;
41+
pq_op record;
42+
prev_id text := NULL;
43+
next_id text := NULL;
44+
whereq text := 'TRUE';
45+
links jsonb := '[]'::jsonb;
46+
token text;
47+
tok_val text;
48+
tok_q text := 'TRUE';
49+
tok_sort text;
50+
first_id text;
51+
first_dt timestamptz;
52+
last_id text;
53+
sort text;
54+
rsort text;
55+
dt text[];
56+
dqa text[];
57+
dq text;
58+
mq_where text;
59+
startdt timestamptz;
60+
enddt timestamptz;
61+
item items%ROWTYPE;
62+
counter int := 0;
63+
batchcount int;
64+
month timestamptz;
65+
m record;
66+
_dtrange tstzrange := tstzrange('-infinity','infinity');
67+
_dtsort text;
68+
_token_dtrange tstzrange := tstzrange('-infinity','infinity');
69+
_token_record items%ROWTYPE;
70+
is_prev boolean := false;
71+
includes text[];
72+
excludes text[];
73+
BEGIN
74+
-- Create table from sort query of items to sort
75+
CREATE TEMP TABLE pgstac_tmp_sorts ON COMMIT DROP AS SELECT * FROM sort_base(_search->'sortby');
76+
77+
-- Get the datetime sort direction, necessary for efficient cycling through partitions
78+
SELECT INTO _dtsort dir FROM pgstac_tmp_sorts WHERE key='datetime';
79+
RAISE NOTICE '_dtsort: %',_dtsort;
80+
81+
SELECT INTO _sort string_agg(s.sort,', ') FROM pgstac_tmp_sorts s;
82+
SELECT INTO _rsort string_agg(s.rsort,', ') FROM pgstac_tmp_sorts s;
83+
tok_sort := _sort;
84+
85+
86+
-- Get datetime from query as a tstzrange
87+
IF _search ? 'datetime' THEN
88+
_dtrange := search_dtrange(_search->'datetime');
89+
_token_dtrange := _dtrange;
90+
END IF;
91+
92+
-- Get the paging token
93+
IF _search ? 'token' THEN
94+
token := _search->>'token';
95+
tok_val := substr(token,6);
96+
IF starts_with(token, 'prev:') THEN
97+
is_prev := true;
98+
END IF;
99+
SELECT INTO _token_record * FROM items WHERE id=tok_val;
100+
IF
101+
(is_prev AND _dtsort = 'DESC')
102+
OR
103+
(not is_prev AND _dtsort = 'ASC')
104+
THEN
105+
_token_dtrange := _dtrange * tstzrange(_token_record.datetime, 'infinity');
106+
ELSIF
107+
_dtsort IS NOT NULL
108+
THEN
109+
_token_dtrange := _dtrange * tstzrange('-infinity',_token_record.datetime);
110+
END IF;
111+
IF is_prev THEN
112+
tok_q := filter_by_order(tok_val, _search->'sortby', 'first');
113+
_sort := _rsort;
114+
ELSIF starts_with(token, 'next:') THEN
115+
tok_q := filter_by_order(tok_val, _search->'sortby', 'last');
116+
END IF;
117+
END IF;
118+
RAISE NOTICE 'timing: %', age(clock_timestamp(), qstart);
119+
RAISE NOTICE 'tok_q: % _token_dtrange: %', tok_q, _token_dtrange;
120+
121+
IF _search ? 'ids' THEN
122+
RAISE NOTICE 'searching solely based on ids... %',_search;
123+
qa := array_append(qa, in_array_q('id', _search->'ids'));
124+
ELSE
125+
IF _search ? 'intersects' THEN
126+
_geom := ST_SetSRID(ST_GeomFromGeoJSON(_search->>'intersects'), 4326);
127+
ELSIF _search ? 'bbox' THEN
128+
_geom := bbox_geom(_search->'bbox');
129+
END IF;
130+
131+
IF _geom IS NOT NULL THEN
132+
qa := array_append(qa, format('st_intersects(geometry, %L::geometry)',_geom));
133+
END IF;
134+
135+
IF _search ? 'collections' THEN
136+
qa := array_append(qa, in_array_q('collection_id', _search->'collections'));
137+
END IF;
138+
139+
IF _search ? 'query' THEN
140+
qa := array_cat(qa,
141+
stac_query(_search->'query')
142+
);
143+
END IF;
144+
END IF;
145+
146+
IF _search ? 'limit' THEN
147+
_limit := (_search->>'limit')::int;
148+
END IF;
149+
150+
IF _search ? 'fields' THEN
151+
IF _search->'fields' ? 'exclude' THEN
152+
excludes=textarr(_search->'fields'->'exclude');
153+
END IF;
154+
IF _search->'fields' ? 'include' THEN
155+
includes=textarr(_search->'fields'->'include');
156+
IF array_length(includes, 1)>0 AND NOT 'id' = ANY (includes) THEN
157+
includes = includes || '{id}';
158+
END IF;
159+
END IF;
160+
RAISE NOTICE 'Includes: %, Excludes: %', includes, excludes;
161+
END IF;
162+
163+
whereq := COALESCE(array_to_string(qa,' AND '),' TRUE ');
164+
dq := COALESCE(array_to_string(dqa,' AND '),' TRUE ');
165+
RAISE NOTICE 'timing before temp table: %', age(clock_timestamp(), qstart);
166+
167+
CREATE TEMP TABLE results_page ON COMMIT DROP AS
168+
SELECT * FROM items_by_partition(
169+
concat(whereq, ' AND ', tok_q),
170+
_token_dtrange,
171+
_sort,
172+
_limit + 1
173+
);
174+
RAISE NOTICE 'timing after temp table: %', age(clock_timestamp(), qstart);
175+
176+
RAISE NOTICE 'timing before min/max: %', age(clock_timestamp(), qstart);
177+
178+
IF is_prev THEN
179+
SELECT INTO last_id, first_id, counter
180+
first_value(id) OVER (),
181+
last_value(id) OVER (),
182+
count(*) OVER ()
183+
FROM results_page;
184+
ELSE
185+
SELECT INTO first_id, last_id, counter
186+
first_value(id) OVER (),
187+
last_value(id) OVER (),
188+
count(*) OVER ()
189+
FROM results_page;
190+
END IF;
191+
RAISE NOTICE 'firstid: %, lastid %', first_id, last_id;
192+
RAISE NOTICE 'timing after min/max: %', age(clock_timestamp(), qstart);
193+
194+
195+
196+
197+
IF counter > _limit THEN
198+
next_id := last_id;
199+
RAISE NOTICE 'next_id: %', next_id;
200+
ELSE
201+
RAISE NOTICE 'No more next';
202+
END IF;
203+
204+
IF tok_q = 'TRUE' THEN
205+
RAISE NOTICE 'Not a paging query, no previous item';
206+
ELSE
207+
RAISE NOTICE 'Getting previous item id';
208+
RAISE NOTICE 'timing: %', age(clock_timestamp(), qstart);
209+
SELECT INTO _token_record * FROM items WHERE id=first_id;
210+
IF
211+
_dtsort = 'DESC'
212+
THEN
213+
_token_dtrange := _dtrange * tstzrange(_token_record.datetime, 'infinity');
214+
ELSE
215+
_token_dtrange := _dtrange * tstzrange('-infinity',_token_record.datetime);
216+
END IF;
217+
RAISE NOTICE '% %', _token_dtrange, _dtrange;
218+
SELECT id INTO prev_id FROM items_by_partition(
219+
concat(whereq, ' AND ', filter_by_order(first_id, _search->'sortby', 'prev')),
220+
_token_dtrange,
221+
_rsort,
222+
1
223+
);
224+
RAISE NOTICE 'timing: %', age(clock_timestamp(), qstart);
225+
226+
RAISE NOTICE 'prev_id: %', prev_id;
227+
END IF;
228+
229+
230+
RETURN QUERY
231+
WITH features AS (
232+
SELECT filter_jsonb(content, includes, excludes) as content
233+
FROM results_page LIMIT _limit
234+
),
235+
j AS (SELECT jsonb_agg(content) as feature_arr FROM features)
236+
SELECT jsonb_build_object(
237+
'type', 'FeatureCollection',
238+
'features', coalesce (
239+
CASE WHEN is_prev THEN flip_jsonb_array(feature_arr) ELSE feature_arr END
240+
,'[]'::jsonb),
241+
'links', links,
242+
'timeStamp', now(),
243+
'next', next_id,
244+
'prev', prev_id
245+
)
246+
FROM j
247+
;
248+
249+
250+
END;
251+
$$ LANGUAGE PLPGSQL SET SEARCH_PATH TO pgstac,public;
252+
INSERT INTO migrations (version) VALUES ('0.2.5');
253+
254+
COMMIT;

0 commit comments

Comments
 (0)