Skip to content

Commit aab2908

Browse files
authored
v0.4.2 - CQL2 and Migration Fixes (#80)
* migration fix for 0.3.4->0.3.5 * add handling for updated cql2 spec for timestamp/interval
1 parent d8059a1 commit aab2908

File tree

10 files changed

+2708
-6
lines changed

10 files changed

+2708
-6
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## [v0.4.2]
4+
5+
### Changed
6+
7+
- Add support for updated CQL2 spec to use timestamp or interval key
8+
9+
### Fixed
10+
11+
- Fix for 0.3.4 -> 0.3.5 migration making sure that partitions get renamed correctly
12+
313
## [v0.4.1]
414

515
### Changed

pypgstac/pypgstac/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""PyPGStac Version."""
2-
__version__ = "0.4.1"
2+
__version__ = "0.4.2"

pypgstac/pypgstac/migrations/pgstac.0.3.4-0.3.5.sql

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,34 @@ SELECT array_to_string(
930930
$function$
931931
;
932932

933+
DO $$
934+
DECLARE
935+
partition text;
936+
topartition text;
937+
q text;
938+
matches text[];
939+
BEGIN
940+
FOR partition IN
941+
SELECT
942+
(to_json(parse_ident(c.oid::pg_catalog.regclass::text)))->>-1
943+
FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i
944+
WHERE c.oid=i.inhrelid and i.inhparent='items'::regclass
945+
LOOP
946+
RAISE NOTICE 'Partition: %', partition;
947+
topartition := (to_json(regexp_split_to_array(partition, '\.')))->>-1;
948+
IF partition != topartition THEN
949+
RAISE NOTICE 'Renaming partition % to %', partition, topartition;
950+
q := format($q$
951+
ALTER TABLE %I RENAME TO %I;
952+
$q$, partition, topartition);
953+
RAISE NOTICE '%', q;
954+
EXECUTE q;
955+
END IF;
956+
END LOOP;
957+
END;
958+
$$ LANGUAGE PLPGSQL;
959+
960+
933961
SELECT partition_checks(partition) FROM all_items_partitions;
934962

935963
SELECT set_version('0.3.5');
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
SET SEARCH_PATH to pgstac, public;
2+
set check_function_bodies = off;
3+
4+
CREATE OR REPLACE FUNCTION pgstac.cql2_query(j jsonb, recursion integer DEFAULT 0)
5+
RETURNS text
6+
LANGUAGE plpgsql
7+
AS $function$
8+
DECLARE
9+
args jsonb := j->'args';
10+
jtype text := jsonb_typeof(j->'args');
11+
op text := lower(j->>'op');
12+
arg jsonb;
13+
argtext text;
14+
argstext text[] := '{}'::text[];
15+
inobj jsonb;
16+
_numeric text := '';
17+
ops jsonb :=
18+
'{
19+
"eq": "%s = %s",
20+
"lt": "%s < %s",
21+
"lte": "%s <= %s",
22+
"gt": "%s > %s",
23+
"gte": "%s >= %s",
24+
"le": "%s <= %s",
25+
"ge": "%s >= %s",
26+
"=": "%s = %s",
27+
"<": "%s < %s",
28+
"<=": "%s <= %s",
29+
">": "%s > %s",
30+
">=": "%s >= %s",
31+
"like": "%s LIKE %s",
32+
"ilike": "%s ILIKE %s",
33+
"+": "%s + %s",
34+
"-": "%s - %s",
35+
"*": "%s * %s",
36+
"/": "%s / %s",
37+
"in": "%s = ANY (%s)",
38+
"not": "NOT (%s)",
39+
"between": "%s BETWEEN (%2$s)[1] AND (%2$s)[2]",
40+
"lower":" lower(%s)",
41+
"upper":" upper(%s)",
42+
"isnull": "%s IS NULL"
43+
}'::jsonb;
44+
ret text;
45+
46+
BEGIN
47+
RAISE NOTICE 'j: %s', j;
48+
IF j ? 'filter' THEN
49+
RETURN cql2_query(j->'filter');
50+
END IF;
51+
52+
IF j ? 'upper' THEN
53+
RAISE NOTICE 'upper %s',jsonb_build_object(
54+
'op', 'upper',
55+
'args', jsonb_build_array( j-> 'upper')
56+
) ;
57+
RETURN cql2_query(
58+
jsonb_build_object(
59+
'op', 'upper',
60+
'args', jsonb_build_array( j-> 'upper')
61+
)
62+
);
63+
END IF;
64+
65+
IF j ? 'lower' THEN
66+
RETURN cql2_query(
67+
jsonb_build_object(
68+
'op', 'lower',
69+
'args', jsonb_build_array( j-> 'lower')
70+
)
71+
);
72+
END IF;
73+
74+
IF j ? 'args' AND jsonb_typeof(args) != 'array' THEN
75+
args := jsonb_build_array(args);
76+
END IF;
77+
-- END Cases where no further nesting is expected
78+
IF j ? 'op' THEN
79+
-- Special case to use JSONB index for equality
80+
IF op = 'eq'
81+
AND args->0 ? 'property'
82+
AND jsonb_typeof(args->1) IN ('number', 'string')
83+
AND (items_path(args->0->>'property')).eq IS NOT NULL
84+
THEN
85+
RETURN format((items_path(args->0->>'property')).eq, args->1);
86+
END IF;
87+
88+
-- Temporal Query
89+
IF op ilike 't_%' or op = 'anyinteracts' THEN
90+
RETURN temporal_op_query(op, args);
91+
END IF;
92+
93+
-- Spatial Query
94+
IF op ilike 's_%' or op = 'intersects' THEN
95+
RETURN spatial_op_query(op, args);
96+
END IF;
97+
98+
-- In Query - separate into separate eq statements so that we can use eq jsonb optimization
99+
IF op = 'in' THEN
100+
RAISE NOTICE '% IN args: %', repeat(' ', recursion), args;
101+
SELECT INTO inobj
102+
jsonb_agg(
103+
jsonb_build_object(
104+
'op', 'eq',
105+
'args', jsonb_build_array( args->0 , v)
106+
)
107+
)
108+
FROM jsonb_array_elements( args->1) v;
109+
RETURN cql2_query(jsonb_build_object('op','or','args',inobj));
110+
END IF;
111+
END IF;
112+
113+
IF j ? 'property' THEN
114+
RETURN (items_path(j->>'property')).path_txt;
115+
END IF;
116+
117+
IF j ? 'timestamp' THEN
118+
RETURN quote_literal(j->>'timestamp');
119+
END IF;
120+
121+
RAISE NOTICE '%jtype: %',repeat(' ', recursion), jtype;
122+
IF jsonb_typeof(j) = 'number' THEN
123+
RETURN format('%L::numeric', j->>0);
124+
END IF;
125+
126+
IF jsonb_typeof(j) = 'string' THEN
127+
RETURN quote_literal(j->>0);
128+
END IF;
129+
130+
IF jsonb_typeof(j) = 'array' THEN
131+
IF j @? '$[*] ? (@.type() == "number")' THEN
132+
RETURN CONCAT(quote_literal(textarr(j)::text), '::numeric[]');
133+
ELSE
134+
RETURN CONCAT(quote_literal(textarr(j)::text), '::text[]');
135+
END IF;
136+
END IF;
137+
RAISE NOTICE 'ARGS after array cleaning: %', args;
138+
139+
RAISE NOTICE '%beforeargs op: %, args: %',repeat(' ', recursion), op, args;
140+
IF j ? 'args' THEN
141+
FOR arg in SELECT * FROM jsonb_array_elements(args) LOOP
142+
argtext := cql2_query(arg, recursion + 1);
143+
RAISE NOTICE '% -- arg: %, argtext: %', repeat(' ', recursion), arg, argtext;
144+
argstext := argstext || argtext;
145+
END LOOP;
146+
END IF;
147+
RAISE NOTICE '%afterargs op: %, argstext: %',repeat(' ', recursion), op, argstext;
148+
149+
150+
IF op IN ('and', 'or') THEN
151+
RAISE NOTICE 'inand op: %, argstext: %', op, argstext;
152+
SELECT
153+
concat(' ( ',array_to_string(array_agg(e), concat(' ',op,' ')),' ) ')
154+
INTO ret
155+
FROM unnest(argstext) e;
156+
RETURN ret;
157+
END IF;
158+
159+
IF ops ? op THEN
160+
IF argstext[2] ~* 'numeric' THEN
161+
argstext := ARRAY[concat('(',argstext[1],')::numeric')] || argstext[2:3];
162+
END IF;
163+
RETURN format(concat('(',ops->>op,')'), VARIADIC argstext);
164+
END IF;
165+
166+
RAISE NOTICE '%op: %, argstext: %',repeat(' ', recursion), op, argstext;
167+
168+
RETURN NULL;
169+
END;
170+
$function$
171+
;
172+
173+
CREATE OR REPLACE FUNCTION pgstac.parse_dtrange(_indate jsonb, OUT _tstzrange tstzrange)
174+
RETURNS tstzrange
175+
LANGUAGE sql
176+
IMMUTABLE PARALLEL SAFE
177+
AS $function$
178+
WITH t AS (
179+
SELECT CASE
180+
WHEN _indate ? 'timestamp' THEN
181+
ARRAY[_indate->>'timestamp', 'infinity']
182+
WHEN _indate ? 'interval' THEN
183+
textarr(_indate->'interval')
184+
WHEN jsonb_typeof(_indate) = 'array' THEN
185+
textarr(_indate)
186+
ELSE
187+
regexp_split_to_array(
188+
btrim(_indate::text,'"'),
189+
'/'
190+
)
191+
END AS arr
192+
)
193+
, t1 AS (
194+
SELECT
195+
CASE
196+
WHEN array_upper(arr,1) = 1 OR arr[1] = '..' OR arr[1] IS NULL THEN '-infinity'::timestamptz
197+
ELSE arr[1]::timestamptz
198+
END AS st,
199+
CASE
200+
WHEN array_upper(arr,1) = 1 THEN arr[1]::timestamptz
201+
WHEN arr[2] = '..' OR arr[2] IS NULL THEN 'infinity'::timestamptz
202+
ELSE arr[2]::timestamptz
203+
END AS et
204+
FROM t
205+
)
206+
SELECT
207+
tstzrange(st,et)
208+
FROM t1;
209+
$function$
210+
;
211+
212+
213+
214+
SELECT set_version('0.4.2');

0 commit comments

Comments
 (0)