Skip to content

Commit 2cc92df

Browse files
committed
Merge feat/postgis-spatial-filter-tests (resolve CI matrix — include both graphile-postgis and graphql/orm-test)
2 parents 1d89fd0 + acf2cc6 commit 2cc92df

4 files changed

Lines changed: 1126 additions & 0 deletions

File tree

.github/workflows/run-tests.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ jobs:
7575
env: {}
7676
- package: graphile/graphile-postgis
7777
env: {}
78+
- package: graphql/orm-test
79+
env: {}
7880
- package: graphql/server-test
7981
env: {}
8082
- package: graphql/env

graphile/graphile-postgis/__tests__/connection-filter-integration.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,43 @@ describe('Integration: connection-filter OperatorSpec compatibility', () => {
147147
}
148148
}
149149
});
150+
151+
// Regression guard for constructive-io/constructive-planning#724.
152+
//
153+
// Spatial filter operators receive GeoJSON input that must be wrapped with
154+
// ST_GeomFromGeoJSON(...)::<codec> before hitting PostgreSQL — PG's
155+
// geometry_in / geography_in parsers do NOT accept GeoJSON text cast to
156+
// geometry / geography directly.
157+
//
158+
// graphile-connection-filter's operatorApply falls through to
159+
// `sqlValueWithCodec(resolvedInput, inputCodec)` (a raw text bind cast to
160+
// the codec's sqlType) unless the operator spec overrides the binding via
161+
// `resolveSqlValue`, `resolveInput`, or `resolveInputCodec`. Without one
162+
// of those overrides the operator is broken end-to-end on both codecs.
163+
//
164+
// See plugins/within-distance-operator.ts for the correct pattern
165+
// (`resolveSqlValue: () => sql.null` + manual ST_GeomFromGeoJSON wrap in
166+
// resolve()).
167+
it('every spec overrides value binding so GeoJSON is wrapped with ST_GeomFromGeoJSON', () => {
168+
const { registered } = runFactory();
169+
170+
for (const { spec, operatorName, typeName } of registered) {
171+
const hasBindingOverride =
172+
typeof spec.resolveSqlValue === 'function' ||
173+
typeof spec.resolveInput === 'function' ||
174+
spec.resolveInputCodec !== undefined;
175+
176+
expect({
177+
operatorName,
178+
typeName,
179+
hasBindingOverride,
180+
}).toEqual({
181+
operatorName,
182+
typeName,
183+
hasBindingOverride: true,
184+
});
185+
}
186+
});
150187
});
151188

152189
describe('Integration: type name generation matches graphile-postgis', () => {
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
-- PostGIS spatial filter integration seed for orm-test.
2+
--
3+
-- Exercises every ORM-exposed PostGIS spatial filter operator across every
4+
-- concrete geometry subtype plus the geography codec. Acts as a regression
5+
-- guard for constructive-io/constructive-planning#724 (GeoJSON input of
6+
-- spatial filters must be wrapped with ST_GeomFromGeoJSON in the generated
7+
-- SQL).
8+
--
9+
-- Requires postgres-plus image with the `postgis` extension.
10+
-- Extensions are installed via pgsql-test db.extensions config (not inline).
11+
12+
CREATE SCHEMA IF NOT EXISTS postgis_test;
13+
14+
GRANT USAGE ON SCHEMA postgis_test TO PUBLIC;
15+
ALTER DEFAULT PRIVILEGES IN SCHEMA postgis_test GRANT ALL ON TABLES TO PUBLIC;
16+
ALTER DEFAULT PRIVILEGES IN SCHEMA postgis_test GRANT ALL ON SEQUENCES TO PUBLIC;
17+
18+
-- ============================================================================
19+
-- GEOMETRY CODEC — one table per concrete subtype
20+
-- ============================================================================
21+
22+
-- Point: 6 US cities. `secondary_loc` is nullable to drive isNull tests.
23+
CREATE TABLE postgis_test.cities_geom (
24+
id serial PRIMARY KEY,
25+
name text NOT NULL,
26+
loc geometry(Point, 4326) NOT NULL,
27+
secondary_loc geometry(Point, 4326)
28+
);
29+
CREATE INDEX idx_cities_geom_loc ON postgis_test.cities_geom USING gist(loc);
30+
CREATE INDEX idx_cities_geom_secondary_loc ON postgis_test.cities_geom USING gist(secondary_loc);
31+
32+
-- Polygon: 4 bounded regions with known containment relationships.
33+
CREATE TABLE postgis_test.regions_geom (
34+
id serial PRIMARY KEY,
35+
name text NOT NULL,
36+
shape geometry(Polygon, 4326) NOT NULL
37+
);
38+
CREATE INDEX idx_regions_geom_shape ON postgis_test.regions_geom USING gist(shape);
39+
40+
-- MultiPolygon: 2 disjoint multi-region shapes (west-coast metros, east-coast metros).
41+
CREATE TABLE postgis_test.territories_geom (
42+
id serial PRIMARY KEY,
43+
name text NOT NULL,
44+
regions geometry(MultiPolygon, 4326) NOT NULL
45+
);
46+
CREATE INDEX idx_territories_geom_regions ON postgis_test.territories_geom USING gist(regions);
47+
48+
-- LineString: 2 multi-vertex routes (I-5 corridor and transcontinental).
49+
CREATE TABLE postgis_test.routes_geom (
50+
id serial PRIMARY KEY,
51+
name text NOT NULL,
52+
path geometry(LineString, 4326) NOT NULL
53+
);
54+
CREATE INDEX idx_routes_geom_path ON postgis_test.routes_geom USING gist(path);
55+
56+
-- MultiPoint: 2 point bags (west-coast, east-coast).
57+
CREATE TABLE postgis_test.swarms_geom (
58+
id serial PRIMARY KEY,
59+
name text NOT NULL,
60+
points geometry(MultiPoint, 4326) NOT NULL
61+
);
62+
CREATE INDEX idx_swarms_geom_points ON postgis_test.swarms_geom USING gist(points);
63+
64+
-- MultiLineString: 2 multi-line networks (bay-area transit, east-coast rail).
65+
CREATE TABLE postgis_test.networks_geom (
66+
id serial PRIMARY KEY,
67+
name text NOT NULL,
68+
paths geometry(MultiLineString, 4326) NOT NULL
69+
);
70+
CREATE INDEX idx_networks_geom_paths ON postgis_test.networks_geom USING gist(paths);
71+
72+
-- GeometryCollection: 2 heterogeneous bags (mixed shapes).
73+
CREATE TABLE postgis_test.collections_geom (
74+
id serial PRIMARY KEY,
75+
name text NOT NULL,
76+
shapes geometry(GeometryCollection, 4326) NOT NULL
77+
);
78+
CREATE INDEX idx_collections_geom_shapes ON postgis_test.collections_geom USING gist(shapes);
79+
80+
-- PointZ: 2 altitude-aware points so intersects3D has a real column to hit.
81+
CREATE TABLE postgis_test.towers_geom (
82+
id serial PRIMARY KEY,
83+
name text NOT NULL,
84+
loc3d geometry(PointZ, 4326) NOT NULL
85+
);
86+
CREATE INDEX idx_towers_geom_loc3d ON postgis_test.towers_geom USING gist(loc3d);
87+
88+
-- ============================================================================
89+
-- GEOGRAPHY CODEC — Point + Polygon (the only codec shapes the operator set
90+
-- registers for; see graphile-postgis FUNCTION_SPECS / OPERATOR_SPECS).
91+
-- ============================================================================
92+
93+
CREATE TABLE postgis_test.cities_geog (
94+
id serial PRIMARY KEY,
95+
name text NOT NULL,
96+
loc geography(Point, 4326) NOT NULL
97+
);
98+
CREATE INDEX idx_cities_geog_loc ON postgis_test.cities_geog USING gist(loc);
99+
100+
CREATE TABLE postgis_test.regions_geog (
101+
id serial PRIMARY KEY,
102+
name text NOT NULL,
103+
shape geography(Polygon, 4326) NOT NULL
104+
);
105+
CREATE INDEX idx_regions_geog_shape ON postgis_test.regions_geog USING gist(shape);
106+
107+
-- ============================================================================
108+
-- SEED DATA
109+
-- ============================================================================
110+
111+
-- Cities (geometry): id 1..6 = SF, Oakland, LA, NY, Seattle, Chicago.
112+
-- Two rows have a non-null secondary_loc (SF, Oakland) so isNull partitions
113+
-- the table evenly enough to be meaningful.
114+
INSERT INTO postgis_test.cities_geom (id, name, loc, secondary_loc) VALUES
115+
(1, 'San Francisco', ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326),
116+
ST_SetSRID(ST_MakePoint(-122.4783, 37.8199), 4326)),
117+
(2, 'Oakland', ST_SetSRID(ST_MakePoint(-122.2712, 37.8044), 4326),
118+
ST_SetSRID(ST_MakePoint(-122.2585, 37.8024), 4326)),
119+
(3, 'Los Angeles', ST_SetSRID(ST_MakePoint(-118.2437, 34.0522), 4326), NULL),
120+
(4, 'New York', ST_SetSRID(ST_MakePoint( -74.0060, 40.7128), 4326), NULL),
121+
(5, 'Seattle', ST_SetSRID(ST_MakePoint(-122.3321, 47.6062), 4326), NULL),
122+
(6, 'Chicago', ST_SetSRID(ST_MakePoint( -87.6298, 41.8781), 4326), NULL);
123+
124+
-- Cities (geography): same coordinates, geography codec.
125+
INSERT INTO postgis_test.cities_geog (id, name, loc) VALUES
126+
(1, 'San Francisco', ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography),
127+
(2, 'Oakland', ST_SetSRID(ST_MakePoint(-122.2712, 37.8044), 4326)::geography),
128+
(3, 'Los Angeles', ST_SetSRID(ST_MakePoint(-118.2437, 34.0522), 4326)::geography),
129+
(4, 'New York', ST_SetSRID(ST_MakePoint( -74.0060, 40.7128), 4326)::geography),
130+
(5, 'Seattle', ST_SetSRID(ST_MakePoint(-122.3321, 47.6062), 4326)::geography),
131+
(6, 'Chicago', ST_SetSRID(ST_MakePoint( -87.6298, 41.8781), 4326)::geography);
132+
133+
-- Regions (geometry): bounding rectangles.
134+
-- Bay Area covers SF + Oakland, NYC Metro covers NY, West Coast Strip covers
135+
-- SF + Oakland + LA + Seattle, Pacific Ocean contains no cities.
136+
INSERT INTO postgis_test.regions_geom (id, name, shape) VALUES
137+
(1, 'Bay Area', ST_GeomFromText('POLYGON((-122.55 37.70, -122.20 37.70, -122.20 37.85, -122.55 37.85, -122.55 37.70))', 4326)),
138+
(2, 'NYC Metro', ST_GeomFromText('POLYGON((-74.15 40.60, -73.70 40.60, -73.70 40.90, -74.15 40.90, -74.15 40.60))', 4326)),
139+
(3, 'West Coast Strip', ST_GeomFromText('POLYGON((-122.55 32.00, -117.00 32.00, -117.00 49.00, -122.55 49.00, -122.55 32.00))', 4326)),
140+
(4, 'Pacific Ocean', ST_GeomFromText('POLYGON((-135 15, -125 15, -125 40, -135 40, -135 15))', 4326));
141+
142+
-- Regions (geography): same shapes on the geography codec.
143+
INSERT INTO postgis_test.regions_geog (id, name, shape) VALUES
144+
(1, 'Bay Area', ST_GeomFromText('POLYGON((-122.55 37.70, -122.20 37.70, -122.20 37.85, -122.55 37.85, -122.55 37.70))', 4326)::geography),
145+
(2, 'NYC Metro', ST_GeomFromText('POLYGON((-74.15 40.60, -73.70 40.60, -73.70 40.90, -74.15 40.90, -74.15 40.60))', 4326)::geography),
146+
(3, 'West Coast Strip', ST_GeomFromText('POLYGON((-122.55 32.00, -117.00 32.00, -117.00 49.00, -122.55 49.00, -122.55 32.00))', 4326)::geography),
147+
(4, 'Pacific Ocean', ST_GeomFromText('POLYGON((-135 15, -125 15, -125 40, -135 40, -135 15))', 4326)::geography);
148+
149+
-- Territories (MultiPolygon): 3-part west-coast (Bay Area + LA Basin +
150+
-- Seattle Area) and 3-part east-coast (NYC Metro + DC area + Boston area).
151+
INSERT INTO postgis_test.territories_geom (id, name, regions) VALUES
152+
(1, 'West Coast Metros', ST_GeomFromText(
153+
'MULTIPOLYGON(((-122.55 37.70, -122.20 37.70, -122.20 37.85, -122.55 37.85, -122.55 37.70)),
154+
((-119.00 33.00, -117.00 33.00, -117.00 35.00, -119.00 35.00, -119.00 33.00)),
155+
((-123.00 47.00, -121.00 47.00, -121.00 48.00, -123.00 48.00, -123.00 47.00)))', 4326)),
156+
(2, 'East Coast Metros', ST_GeomFromText(
157+
'MULTIPOLYGON(((-74.15 40.60, -73.70 40.60, -73.70 40.90, -74.15 40.90, -74.15 40.60)),
158+
((-77.20 38.80, -76.90 38.80, -76.90 39.00, -77.20 39.00, -77.20 38.80)),
159+
((-71.20 42.30, -71.00 42.30, -71.00 42.40, -71.20 42.40, -71.20 42.30)))', 4326));
160+
161+
-- Routes (LineString).
162+
-- I-5 Corridor: SF → LA → Seattle (zig-zag, bbox spans whole west coast).
163+
-- Transcontinental: SF → Chicago → NY (bbox spans full continental US).
164+
INSERT INTO postgis_test.routes_geom (id, name, path) VALUES
165+
(1, 'I-5 Corridor', ST_GeomFromText('LINESTRING(-122.4194 37.7749, -118.2437 34.0522, -122.3321 47.6062)', 4326)),
166+
(2, 'Transcontinental', ST_GeomFromText('LINESTRING(-122.4194 37.7749, -87.6298 41.8781, -74.0060 40.7128)', 4326));
167+
168+
-- Swarms (MultiPoint).
169+
INSERT INTO postgis_test.swarms_geom (id, name, points) VALUES
170+
(1, 'West Coast Swarm', ST_GeomFromText('MULTIPOINT((-122.4194 37.7749), (-118.2437 34.0522), (-122.3321 47.6062))', 4326)),
171+
(2, 'East Coast Swarm', ST_GeomFromText('MULTIPOINT(( -74.0060 40.7128), ( -77.0369 38.9072), ( -71.0589 42.3601))', 4326));
172+
173+
-- Networks (MultiLineString).
174+
-- Bay Area Transit is a pair of short lines around SF/Oakland (both lie
175+
-- inside the Bay Area region polygon).
176+
-- East Coast Rail connects NY→DC and NY→Boston.
177+
INSERT INTO postgis_test.networks_geom (id, name, paths) VALUES
178+
(1, 'Bay Area Transit', ST_GeomFromText(
179+
'MULTILINESTRING((-122.4194 37.7749, -122.2712 37.8044),
180+
(-122.4783 37.8199, -122.2585 37.8024))', 4326)),
181+
(2, 'East Coast Rail', ST_GeomFromText(
182+
'MULTILINESTRING(( -74.0060 40.7128, -77.0369 38.9072),
183+
( -74.0060 40.7128, -71.0589 42.3601))', 4326));
184+
185+
-- Collections (GeometryCollection) — heterogeneous shape bags.
186+
INSERT INTO postgis_test.collections_geom (id, name, shapes) VALUES
187+
(1, 'West Coast Mix', ST_GeomFromText(
188+
'GEOMETRYCOLLECTION(
189+
POINT(-122.4194 37.7749),
190+
POLYGON((-122.55 37.70, -122.20 37.70, -122.20 37.85, -122.55 37.85, -122.55 37.70)),
191+
LINESTRING(-122.4194 37.7749, -118.2437 34.0522)
192+
)', 4326)),
193+
(2, 'East Coast Mix', ST_GeomFromText(
194+
'GEOMETRYCOLLECTION(
195+
POINT(-74.0060 40.7128),
196+
POLYGON((-74.15 40.60, -73.70 40.60, -73.70 40.90, -74.15 40.90, -74.15 40.60))
197+
)', 4326));
198+
199+
-- Towers (PointZ): real SF structures with altitude in metres.
200+
INSERT INTO postgis_test.towers_geom (id, name, loc3d) VALUES
201+
(1, 'Sutro Tower', ST_SetSRID(ST_MakePoint(-122.4528, 37.7552, 254), 4326)),
202+
(2, 'Salesforce Tower', ST_SetSRID(ST_MakePoint(-122.3975, 37.7895, 326), 4326));
203+
204+
-- Reset sequences.
205+
SELECT setval('postgis_test.cities_geom_id_seq', 6);
206+
SELECT setval('postgis_test.cities_geog_id_seq', 6);
207+
SELECT setval('postgis_test.regions_geom_id_seq', 4);
208+
SELECT setval('postgis_test.regions_geog_id_seq', 4);
209+
SELECT setval('postgis_test.territories_geom_id_seq', 2);
210+
SELECT setval('postgis_test.routes_geom_id_seq', 2);
211+
SELECT setval('postgis_test.swarms_geom_id_seq', 2);
212+
SELECT setval('postgis_test.networks_geom_id_seq', 2);
213+
SELECT setval('postgis_test.collections_geom_id_seq', 2);
214+
SELECT setval('postgis_test.towers_geom_id_seq', 2);

0 commit comments

Comments
 (0)