Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
toolchain, for production deployments where the Rust build environment is not needed.
- Dependabot coverage expanded to Docker base images and pip packages (two new
ecosystems with grouped update policies).

### Changed
- `pypgstac migrate` now delegates runtime migration planning and apply logic to
`pgstac-migrate`; `src/pypgstac/src/pypgstac/migrate.py` remains as a
compatibility wrapper.
Expand Down Expand Up @@ -111,6 +109,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
`python-runtime` groups).
- `docker-compose.yml` removes explicit `container_name` entries to avoid conflicts
between concurrent local instances.
- Appended `collection` as a secondary sorting tie-breaker before `id` inside `keyset_sortkeys`. This ensures deterministic pagination tokens globally and enables PostgreSQL partition pruning for optimized multi-collection searches.
- Updated pgTAP test suite expectations and output snapshots to match the new `collection, id` sorting structure.

### Removed
- PL/Rust support: `pgstacbase-plrust` and `pgstac-plrust` Docker targets removed; the
Expand All @@ -135,6 +135,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- `scripts/container-scripts/test` now derives the active database from
`PGDATABASE`/`POSTGRES_DB` when checking server extensions and refreshing
collation versions, instead of assuming `postgis`.
- Fixed an edge-case bug where keyset pagination returned 0 features on Page 2 if items across different collections shared identical IDs and timestamps (#392).
- Resolved a Python type-checking diagnostic in `pypgstac.load` regarding dynamic file opening modes.


## [v0.9.11]
Expand Down
8 changes: 4 additions & 4 deletions src/pgstac/pgstac.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2293,7 +2293,7 @@ BEGIN
END IF;

IF jsonb_typeof(j) = 'object' THEN
-- GeoJSON geometry args (Point/Polygon/.../GeometryCollection) are not cql1 expressions;
-- GeoJSON geometry args (Point/Polygon/.../GeometryCollection) are not cql2 expressions;
-- pass them through unchanged so spatial ops keep their geometry intact.
IF j ? 'type' AND (j ? 'coordinates' OR j ? 'geometries') THEN
RETURN j;
Expand Down Expand Up @@ -2870,9 +2870,10 @@ RETURNS TABLE(ord int, field text, expr text, dir text, notnull boolean) AS $$
SELECT coalesce(_search->'sortby','[{"field":"datetime","direction":"desc"}]'::jsonb) AS s
),
app AS (
-- Append collection first (helps partition pruning), then id to guarantee global uniqueness
SELECT s
|| jsonb_build_object('field','id','direction', s->0->>'direction')
|| jsonb_build_object('field','collection','direction', s->0->>'direction') AS s
|| jsonb_build_object('field','collection','direction', s->0->>'direction')
|| jsonb_build_object('field','id','direction', s->0->>'direction') AS s
FROM base
),
rows AS (
Expand Down Expand Up @@ -5829,7 +5830,6 @@ BEGIN
END LOOP;
END;
$$ LANGUAGE PLPGSQL;

-- END FRAGMENT: 997_maintenance.sql

-- BEGIN FRAGMENT: 998_idempotent_post.sql
Expand Down
5 changes: 3 additions & 2 deletions src/pgstac/sql/002d_keyset.sql
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ RETURNS TABLE(ord int, field text, expr text, dir text, notnull boolean) AS $$
SELECT coalesce(_search->'sortby','[{"field":"datetime","direction":"desc"}]'::jsonb) AS s
),
app AS (
-- Append collection first (helps partition pruning), then id to guarantee global uniqueness
SELECT s
|| jsonb_build_object('field','id','direction', s->0->>'direction')
|| jsonb_build_object('field','collection','direction', s->0->>'direction') AS s
|| jsonb_build_object('field','collection','direction', s->0->>'direction')
|| jsonb_build_object('field','id','direction', s->0->>'direction') AS s
FROM base
),
rows AS (
Expand Down
3 changes: 2 additions & 1 deletion src/pgstac/tests/basic/collection_searches.sql.out
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ SELECT page, ids FROM pg_temp.collwalk('{"ids":["testcollection_1","testcollecti
2 | ["testcollection_1"]

select collection_search('{"filter":{"op":"eq", "args":[{"property":"title"},"My Test Collection."]},"limit":10, "sortby":[{"field":"datetime","direction":"desc"}]}');
{"links": [{"rel": "next", "href": "./collections?token=next:MjAxMi0wMy0zMSAwMDowMDowMCswMB90ZXN0Y29sbGVjdGlvbl82MzkfY29sbGVjdGlvbnM=", "type": "application/json", "method": "GET"}], "collections": [{"id": "testcollection_648", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[170, 80, 180, 90]]}, "temporal": {"interval": [["2012-06-02 00:00:00+00", "2012-08-02 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_647", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[160, 80, 170, 90]]}, "temporal": {"interval": [["2012-05-26 00:00:00+00", "2012-07-26 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_646", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[150, 80, 160, 90]]}, "temporal": {"interval": [["2012-05-19 00:00:00+00", "2012-07-19 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_645", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[140, 80, 150, 90]]}, "temporal": {"interval": [["2012-05-12 00:00:00+00", "2012-07-12 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_644", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[130, 80, 140, 90]]}, "temporal": {"interval": [["2012-05-05 00:00:00+00", "2012-07-05 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_643", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[120, 80, 130, 90]]}, "temporal": {"interval": [["2012-04-28 00:00:00+00", "2012-06-28 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_642", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[110, 80, 120, 90]]}, "temporal": {"interval": [["2012-04-21 00:00:00+00", "2012-06-21 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_641", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[100, 80, 110, 90]]}, "temporal": {"interval": [["2012-04-14 00:00:00+00", "2012-06-14 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_640", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[90, 80, 100, 90]]}, "temporal": {"interval": [["2012-04-07 00:00:00+00", "2012-06-07 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_639", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[80, 80, 90, 90]]}, "temporal": {"interval": [["2012-03-31 00:00:00+00", "2012-05-31 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}], "numberMatched": 648, "numberReturned": 10}
{"links": [{"rel": "next", "href": "./collections?token=next:MjAxMi0wMy0zMSAwMDowMDowMCswMB9jb2xsZWN0aW9ucx90ZXN0Y29sbGVjdGlvbl82Mzk=", "type": "application/json", "method": "GET"}], "collections": [{"id": "testcollection_648", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[170, 80, 180, 90]]}, "temporal": {"interval": [["2012-06-02 00:00:00+00", "2012-08-02 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_647", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[160, 80, 170, 90]]}, "temporal": {"interval": [["2012-05-26 00:00:00+00", "2012-07-26 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_646", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[150, 80, 160, 90]]}, "temporal": {"interval": [["2012-05-19 00:00:00+00", "2012-07-19 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_645", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[140, 80, 150, 90]]}, "temporal": {"interval": [["2012-05-12 00:00:00+00", "2012-07-12 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_644", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[130, 80, 140, 90]]}, "temporal": {"interval": [["2012-05-05 00:00:00+00", "2012-07-05 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_643", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[120, 80, 130, 90]]}, "temporal": {"interval": [["2012-04-28 00:00:00+00", "2012-06-28 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_642", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[110, 80, 120, 90]]}, "temporal": {"interval": [["2012-04-21 00:00:00+00", "2012-06-21 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_641", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[100, 80, 110, 90]]}, "temporal": {"interval": [["2012-04-14 00:00:00+00", "2012-06-14 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_640", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[90, 80, 100, 90]]}, "temporal": {"interval": [["2012-04-07 00:00:00+00", "2012-06-07 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_639", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[80, 80, 90, 90]]}, "temporal": {"interval": [["2012-03-31 00:00:00+00", "2012-05-31 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}], "numberMatched": 648, "numberReturned": 10}

select collection_search('{"datetime":["2012-01-01","2012-01-02"], "filter":{"op":"eq", "args":[{"property":"title"},"My Test Collection."]},"limit":10, "sortby":[{"field":"datetime","direction":"desc"}]}');
{"links": [], "collections": [{"id": "testcollection_626", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[-50, 80, -40, 90]]}, "temporal": {"interval": [["2011-12-31 00:00:00+00", "2012-02-29 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_625", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[-60, 80, -50, 90]]}, "temporal": {"interval": [["2011-12-24 00:00:00+00", "2012-02-24 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_624", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[-70, 80, -60, 90]]}, "temporal": {"interval": [["2011-12-17 00:00:00+00", "2012-02-17 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_623", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[-80, 80, -70, 90]]}, "temporal": {"interval": [["2011-12-10 00:00:00+00", "2012-02-10 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_622", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[-90, 80, -80, 90]]}, "temporal": {"interval": [["2011-12-03 00:00:00+00", "2012-02-03 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_621", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[-100, 80, -90, 90]]}, "temporal": {"interval": [["2011-11-26 00:00:00+00", "2012-01-26 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_620", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[-110, 80, -100, 90]]}, "temporal": {"interval": [["2011-11-19 00:00:00+00", "2012-01-19 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_619", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[-120, 80, -110, 90]]}, "temporal": {"interval": [["2011-11-12 00:00:00+00", "2012-01-12 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}, {"id": "testcollection_618", "type": "Collection", "title": "My Test Collection.", "extent": {"spatial": {"bbox": [[-130, 80, -120, 90]]}, "temporal": {"interval": [["2011-11-05 00:00:00+00", "2012-01-05 00:00:00+00"]]}}, "description": "Description of my test collection.", "stac_extensions": []}], "numberMatched": 9, "numberReturned": 9}

select collection_search('{"ids":["testcollection_1","testcollection_2"], "fields": {"include": ["title"]}}');
{"links": [], "collections": [{"id": "testcollection_1", "title": "My Test Collection."}, {"id": "testcollection_2", "title": "My Test Collection."}], "numberMatched": 2, "numberReturned": 2}

Loading