Skip to content

Commit de45386

Browse files
authored
update dependencies, fixes for postgres 17 (#329)
* update dependencies, fixes for postgres 17 * update queryables to more smartly handle * private field breaking pgtap tests
1 parent 9bdedef commit de45386

22 files changed

+579
-206
lines changed

.pre-commit-config.yaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ repos:
1818
- id: check-symlinks
1919

2020

21-
- repo: https://github.com/charliermarsh/ruff-pre-commit
22-
rev: 'v0.0.284'
21+
- repo: https://github.com/astral-sh/ruff-pre-commit
22+
rev: 'v0.8.2'
2323
hooks:
2424
- id: ruff
25-
files: pypgstac\/.*\.py$
25+
files: src/pypgstac\/.*\.py$
2626
args: [--fix, --exit-non-zero-on-fix]
27+
- id: ruff-format
28+
files: src/pypgstac\/.*\.py$
29+
2730

2831
- repo: local
2932
hooks:

docker/pgstac/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
ARG PG_MAJOR=15
1+
ARG PG_MAJOR=17
22
ARG POSTGIS_MAJOR=3
33

44
# Base postgres image that pgstac can be installed onto
55
FROM postgres:${PG_MAJOR}-bullseye AS pgstacbase
66
ARG POSTGIS_MAJOR
7+
ARG PG_MAJOR
78
RUN \
89
apt-get update \
910
&& apt-get upgrade -y \

docker/pypgstac/bin/test

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ function test_formatting(){
4646
cd $SRCDIR/pypgstac
4747

4848
echo "Running ruff"
49-
ruff -n python tests
49+
ruff check src/pypgstac tests
5050

5151
echo "Running mypy"
52-
mypy python
52+
mypy src/pypgstac
5353

5454
echo "Checking if there are any staged migrations."
5555
find $SRCDIR/pgstac/migrations | grep 'staged' && { echo "There are staged migrations in pypgstac/migrations. Please check migrations and remove staged suffix."; exit 1; }
@@ -82,7 +82,7 @@ DROP DATABASE IF EXISTS pgstac_test_pgtap WITH (force);
8282
EOSQL
8383
if [[ $(echo "$TESTOUTPUT" | grep -e '^not') ]]; then
8484
echo "PGTap tests failed."
85-
echo "$TESTOUTPUT"
85+
echo "$TESTOUTPUT" | awk NF
8686
exit 1
8787
else
8888
echo "PGTap Tests Passed!"

scripts/runinpypgstac

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,20 @@ if [[ $BUILD == 1 ]]; then
3939
sleep 4
4040
fi
4141
PGSTAC_RUNNING=$(docker compose ps pgstac --status running -q)
42+
echo "PGSTAC_RUNNING=$PGSTAC_RUNNING"
4243
if [[ $CPFILES == 1 ]]; then
43-
docker ps | grep pypgstacworker
44-
[[ $? == 0 ]] && echo "Killing pypgstacworker" && docker kill pypgstacworker
44+
echo "Checking if pypgstacworker is running"
45+
docker ps | grep pypgstacworker && echo "Killing pypgstacworker" && docker kill pypgstacworker || echo "pypgstac worker not running"
46+
echo "Running pypgstac worker"
4547
docker compose run -d --rm --name pypgstacworker pypgstac /bin/bash
48+
echo "Executing ${CONTAINER_ARGS[@]} in pypgstac worker"
4649
docker compose exec pypgstac "${CONTAINER_ARGS[@]}"
50+
echo "copying datafiles to host"
4751
docker cp pypgstacworker:/opt/src $SCRIPT_DIR/..
52+
echo "killing pypgstac worker"
4853
docker kill pypgstacworker
4954
else
55+
echo "Running ${CONTAINER_ARGS[@]} in pypgstacworker"
5056
docker compose run -T --rm pypgstac "${CONTAINER_ARGS[@]}"
5157
fi
5258
JOBEXITCODE=$?

src/pgstac/migrations/pgstac.0.9.1-unreleased.sql

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,47 @@ END;
251251
$function$
252252
;
253253

254+
CREATE OR REPLACE FUNCTION pgstac.chunker(_where text, OUT s timestamp with time zone, OUT e timestamp with time zone)
255+
RETURNS SETOF record
256+
LANGUAGE plpgsql
257+
AS $function$
258+
DECLARE
259+
explain jsonb;
260+
BEGIN
261+
IF _where IS NULL THEN
262+
_where := ' TRUE ';
263+
END IF;
264+
EXECUTE format('EXPLAIN (format json) SELECT 1 FROM items WHERE %s;', _where)
265+
INTO explain;
266+
RAISE DEBUG 'EXPLAIN: %', explain;
267+
268+
RETURN QUERY
269+
WITH t AS (
270+
SELECT j->>0 as p FROM
271+
jsonb_path_query(
272+
explain,
273+
'strict $.**."Relation Name" ? (@ != null)'
274+
) j
275+
),
276+
parts AS (
277+
SELECT sdate, edate FROM t JOIN partition_steps ON (t.p = name)
278+
),
279+
times AS (
280+
SELECT sdate FROM parts
281+
UNION
282+
SELECT edate FROM parts
283+
),
284+
uniq AS (
285+
SELECT DISTINCT sdate FROM times ORDER BY sdate
286+
),
287+
last AS (
288+
SELECT sdate, lead(sdate, 1) over () as edate FROM uniq
289+
)
290+
SELECT sdate, edate FROM last WHERE edate IS NOT NULL;
291+
END;
292+
$function$
293+
;
294+
254295
CREATE OR REPLACE FUNCTION pgstac.collection_search(_search jsonb DEFAULT '{}'::jsonb)
255296
RETURNS jsonb
256297
LANGUAGE plpgsql
@@ -274,6 +315,7 @@ BEGIN
274315
FROM collection_search_rows(_search) c;
275316

276317
number_returned := jsonb_array_length(out_records);
318+
RAISE DEBUG 'nm: %, nr: %, l:%, o:%', number_matched, number_returned, _limit, _offset;
277319

278320

279321

@@ -346,6 +388,108 @@ END;
346388
$function$
347389
;
348390

391+
create or replace view "pgstac"."partition_sys_meta" as SELECT (parse_ident((pg_partition_tree.relid)::text))[cardinality(parse_ident((pg_partition_tree.relid)::text))] AS partition,
392+
replace(replace(
393+
CASE
394+
WHEN (pg_partition_tree.level = 1) THEN pg_get_expr(c.relpartbound, c.oid)
395+
ELSE pg_get_expr(parent.relpartbound, parent.oid)
396+
END, 'FOR VALUES IN ('''::text, ''::text), ''')'::text, ''::text) AS collection,
397+
pg_partition_tree.level,
398+
c.reltuples,
399+
c.relhastriggers,
400+
COALESCE(constraint_tstzrange(pg_get_expr(c.relpartbound, c.oid)), tstzrange('-infinity'::timestamp with time zone, 'infinity'::timestamp with time zone, '[]'::text)) AS partition_dtrange,
401+
COALESCE((dt_constraint(edt.oid)).dt, constraint_tstzrange(pg_get_expr(c.relpartbound, c.oid)), tstzrange('-infinity'::timestamp with time zone, 'infinity'::timestamp with time zone, '[]'::text)) AS constraint_dtrange,
402+
COALESCE((dt_constraint(edt.oid)).edt, tstzrange('-infinity'::timestamp with time zone, 'infinity'::timestamp with time zone, '[]'::text)) AS constraint_edtrange
403+
FROM (((pg_partition_tree('items'::regclass) pg_partition_tree(relid, parentrelid, isleaf, level)
404+
JOIN pg_class c ON (((pg_partition_tree.relid)::oid = c.oid)))
405+
JOIN pg_class parent ON ((((pg_partition_tree.parentrelid)::oid = parent.oid) AND pg_partition_tree.isleaf)))
406+
LEFT JOIN pg_constraint edt ON (((edt.conrelid = c.oid) AND (edt.contype = 'c'::"char"))))
407+
WHERE pg_partition_tree.isleaf;
408+
409+
410+
create or replace view "pgstac"."partitions_view" as SELECT (parse_ident((pg_partition_tree.relid)::text))[cardinality(parse_ident((pg_partition_tree.relid)::text))] AS partition,
411+
replace(replace(
412+
CASE
413+
WHEN (pg_partition_tree.level = 1) THEN pg_get_expr(c.relpartbound, c.oid)
414+
ELSE pg_get_expr(parent.relpartbound, parent.oid)
415+
END, 'FOR VALUES IN ('''::text, ''::text), ''')'::text, ''::text) AS collection,
416+
pg_partition_tree.level,
417+
c.reltuples,
418+
c.relhastriggers,
419+
COALESCE(constraint_tstzrange(pg_get_expr(c.relpartbound, c.oid)), tstzrange('-infinity'::timestamp with time zone, 'infinity'::timestamp with time zone, '[]'::text)) AS partition_dtrange,
420+
COALESCE((dt_constraint(edt.oid)).dt, constraint_tstzrange(pg_get_expr(c.relpartbound, c.oid)), tstzrange('-infinity'::timestamp with time zone, 'infinity'::timestamp with time zone, '[]'::text)) AS constraint_dtrange,
421+
COALESCE((dt_constraint(edt.oid)).edt, tstzrange('-infinity'::timestamp with time zone, 'infinity'::timestamp with time zone, '[]'::text)) AS constraint_edtrange,
422+
partition_stats.dtrange,
423+
partition_stats.edtrange,
424+
partition_stats.spatial,
425+
partition_stats.last_updated
426+
FROM ((((pg_partition_tree('items'::regclass) pg_partition_tree(relid, parentrelid, isleaf, level)
427+
JOIN pg_class c ON (((pg_partition_tree.relid)::oid = c.oid)))
428+
JOIN pg_class parent ON ((((pg_partition_tree.parentrelid)::oid = parent.oid) AND pg_partition_tree.isleaf)))
429+
LEFT JOIN pg_constraint edt ON (((edt.conrelid = c.oid) AND (edt.contype = 'c'::"char"))))
430+
LEFT JOIN partition_stats ON (((parse_ident((pg_partition_tree.relid)::text))[cardinality(parse_ident((pg_partition_tree.relid)::text))] = partition_stats.partition)))
431+
WHERE pg_partition_tree.isleaf;
432+
433+
434+
CREATE OR REPLACE FUNCTION pgstac.queryable(dotpath text, OUT path text, OUT expression text, OUT wrapper text, OUT nulled_wrapper text)
435+
RETURNS record
436+
LANGUAGE plpgsql
437+
STABLE STRICT
438+
AS $function$
439+
DECLARE
440+
q RECORD;
441+
path_elements text[];
442+
BEGIN
443+
dotpath := replace(dotpath, 'properties.', '');
444+
IF dotpath = 'start_datetime' THEN
445+
dotpath := 'datetime';
446+
END IF;
447+
IF dotpath IN ('id', 'geometry', 'datetime', 'end_datetime', 'collection') THEN
448+
path := dotpath;
449+
expression := dotpath;
450+
wrapper := NULL;
451+
RETURN;
452+
END IF;
453+
454+
SELECT * INTO q FROM queryables
455+
WHERE
456+
name=dotpath
457+
OR name = 'properties.' || dotpath
458+
OR name = replace(dotpath, 'properties.', '')
459+
;
460+
IF q.property_wrapper IS NULL THEN
461+
IF q.definition->>'type' = 'number' THEN
462+
wrapper := 'to_float';
463+
nulled_wrapper := wrapper;
464+
ELSIF q.definition->>'format' = 'date-time' THEN
465+
wrapper := 'to_tstz';
466+
nulled_wrapper := wrapper;
467+
ELSE
468+
nulled_wrapper := NULL;
469+
wrapper := 'to_text';
470+
END IF;
471+
ELSE
472+
wrapper := q.property_wrapper;
473+
nulled_wrapper := wrapper;
474+
END IF;
475+
IF q.property_path IS NOT NULL THEN
476+
path := q.property_path;
477+
ELSE
478+
path_elements := string_to_array(dotpath, '.');
479+
IF path_elements[1] IN ('links', 'assets', 'stac_version', 'stac_extensions') THEN
480+
path := format('content->%s', array_to_path(path_elements));
481+
ELSIF path_elements[1] = 'properties' THEN
482+
path := format('content->%s', array_to_path(path_elements));
483+
ELSE
484+
path := format($F$content->'properties'->%s$F$, array_to_path(path_elements));
485+
END IF;
486+
END IF;
487+
expression := format('%I(%s)', wrapper, path);
488+
RETURN;
489+
END;
490+
$function$
491+
;
492+
349493
CREATE OR REPLACE FUNCTION pgstac.stac_search_to_where(j jsonb)
350494
RETURNS text
351495
LANGUAGE plpgsql
@@ -440,6 +584,85 @@ END;
440584
$function$
441585
;
442586

587+
CREATE OR REPLACE FUNCTION pgstac.update_partition_stats(_partition text, istrigger boolean DEFAULT false)
588+
RETURNS void
589+
LANGUAGE plpgsql
590+
STRICT SECURITY DEFINER
591+
AS $function$
592+
DECLARE
593+
dtrange tstzrange;
594+
edtrange tstzrange;
595+
cdtrange tstzrange;
596+
cedtrange tstzrange;
597+
extent geometry;
598+
collection text;
599+
BEGIN
600+
RAISE NOTICE 'Updating stats for %.', _partition;
601+
EXECUTE format(
602+
$q$
603+
SELECT
604+
tstzrange(min(datetime), max(datetime),'[]'),
605+
tstzrange(min(end_datetime), max(end_datetime), '[]')
606+
FROM %I
607+
$q$,
608+
_partition
609+
) INTO dtrange, edtrange;
610+
extent := st_estimatedextent('pgstac', _partition, 'geometry');
611+
RAISE DEBUG 'Estimated Extent: %', extent;
612+
INSERT INTO partition_stats (partition, dtrange, edtrange, spatial, last_updated)
613+
SELECT _partition, dtrange, edtrange, extent, now()
614+
ON CONFLICT (partition) DO
615+
UPDATE SET
616+
dtrange=EXCLUDED.dtrange,
617+
edtrange=EXCLUDED.edtrange,
618+
spatial=EXCLUDED.spatial,
619+
last_updated=EXCLUDED.last_updated
620+
;
621+
622+
SELECT
623+
constraint_dtrange, constraint_edtrange, pv.collection
624+
INTO cdtrange, cedtrange, collection
625+
FROM partitions_view pv WHERE partition = _partition;
626+
REFRESH MATERIALIZED VIEW partitions;
627+
REFRESH MATERIALIZED VIEW partition_steps;
628+
629+
630+
RAISE NOTICE 'Checking if we need to modify constraints...';
631+
RAISE NOTICE 'cdtrange: % dtrange: % cedtrange: % edtrange: %',cdtrange, dtrange, cedtrange, edtrange;
632+
IF
633+
(cdtrange IS DISTINCT FROM dtrange OR edtrange IS DISTINCT FROM cedtrange)
634+
AND NOT istrigger
635+
THEN
636+
RAISE NOTICE 'Modifying Constraints';
637+
RAISE NOTICE 'Existing % %', cdtrange, cedtrange;
638+
RAISE NOTICE 'New % %', dtrange, edtrange;
639+
PERFORM drop_table_constraints(_partition);
640+
PERFORM create_table_constraints(_partition, dtrange, edtrange);
641+
REFRESH MATERIALIZED VIEW partitions;
642+
REFRESH MATERIALIZED VIEW partition_steps;
643+
END IF;
644+
RAISE NOTICE 'Checking if we need to update collection extents.';
645+
IF get_setting_bool('update_collection_extent') THEN
646+
RAISE NOTICE 'updating collection extent for %', collection;
647+
PERFORM run_or_queue(format($q$
648+
UPDATE collections
649+
SET content = jsonb_set_lax(
650+
content,
651+
'{extent}'::text[],
652+
collection_extent(%L, FALSE),
653+
true,
654+
'use_json_null'
655+
) WHERE id=%L
656+
;
657+
$q$, collection, collection));
658+
ELSE
659+
RAISE NOTICE 'Not updating collection extent for %', collection;
660+
END IF;
661+
662+
END;
663+
$function$
664+
;
665+
443666
CREATE OR REPLACE FUNCTION pgstac.where_stats(inwhere text, updatestats boolean DEFAULT false, conf jsonb DEFAULT NULL::jsonb)
444667
RETURNS search_wheres
445668
LANGUAGE plpgsql

0 commit comments

Comments
 (0)