graphile-postgis 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +325 -0
- package/esm/index.d.ts +2 -0
- package/esm/index.js +1 -0
- package/esm/plugins/connection-filter-operators.js +22 -2
- package/esm/plugins/spatial-relations.d.ts +130 -0
- package/esm/plugins/spatial-relations.js +575 -0
- package/esm/preset.js +3 -1
- package/index.d.ts +2 -0
- package/index.js +6 -1
- package/package.json +5 -5
- package/plugins/connection-filter-operators.js +22 -2
- package/plugins/spatial-relations.d.ts +130 -0
- package/plugins/spatial-relations.js +583 -0
- package/preset.js +3 -1
|
@@ -213,6 +213,8 @@ function createPostgisOperatorFactory() {
|
|
|
213
213
|
}
|
|
214
214
|
const { inflection } = build;
|
|
215
215
|
const { schemaName, geometryCodec, geographyCodec } = postgisInfo;
|
|
216
|
+
const sqlGeomFromGeoJSON = pg_sql2_1.default.identifier(schemaName, 'st_geomfromgeojson');
|
|
217
|
+
const sqlGeographyType = pg_sql2_1.default.identifier(schemaName, 'geography');
|
|
216
218
|
// Collect all GQL type names for geometry and geography
|
|
217
219
|
const gqlTypeNamesByBase = {
|
|
218
220
|
geometry: [],
|
|
@@ -245,6 +247,7 @@ function createPostgisOperatorFactory() {
|
|
|
245
247
|
typeNames: gqlTypeNamesByBase[baseType],
|
|
246
248
|
operatorName,
|
|
247
249
|
description,
|
|
250
|
+
baseType: baseType,
|
|
248
251
|
resolve: (i, v) => pg_sql2_1.default.fragment `${sqlGisFunction}(${i}, ${v})`
|
|
249
252
|
});
|
|
250
253
|
}
|
|
@@ -258,6 +261,7 @@ function createPostgisOperatorFactory() {
|
|
|
258
261
|
typeNames: gqlTypeNamesByBase[baseType],
|
|
259
262
|
operatorName,
|
|
260
263
|
description,
|
|
264
|
+
baseType: baseType,
|
|
261
265
|
resolve: (i, v) => buildOperatorExpr(capturedOp, i, v)
|
|
262
266
|
});
|
|
263
267
|
}
|
|
@@ -267,8 +271,21 @@ function createPostgisOperatorFactory() {
|
|
|
267
271
|
// Convert to ConnectionFilterOperatorRegistration format.
|
|
268
272
|
// Each InternalSpec may target multiple type names; we expand each
|
|
269
273
|
// into individual registrations keyed by typeName.
|
|
274
|
+
//
|
|
275
|
+
// The default operatorApply pipeline binds the filter value as a raw
|
|
276
|
+
// text parameter cast to the column codec's sqlType (geometry /
|
|
277
|
+
// geography). PostgreSQL's geometry_in / geography_in parsers reject
|
|
278
|
+
// GeoJSON text, so we must wrap the input with ST_GeomFromGeoJSON
|
|
279
|
+
// ourselves — see within-distance-operator.ts for the pattern.
|
|
280
|
+
//
|
|
281
|
+
// We disable the default bind via `resolveSqlValue: () => sql.null`
|
|
282
|
+
// and construct the geometry value from `input` inside resolve(),
|
|
283
|
+
// mirroring the ST_DWithin implementation.
|
|
270
284
|
const registrations = [];
|
|
271
285
|
for (const spec of allSpecs) {
|
|
286
|
+
const geographyCast = spec.baseType === 'geography'
|
|
287
|
+
? pg_sql2_1.default.fragment `::${sqlGeographyType}`
|
|
288
|
+
: pg_sql2_1.default.fragment ``;
|
|
272
289
|
for (const typeName of spec.typeNames) {
|
|
273
290
|
registrations.push({
|
|
274
291
|
typeNames: typeName,
|
|
@@ -276,8 +293,11 @@ function createPostgisOperatorFactory() {
|
|
|
276
293
|
spec: {
|
|
277
294
|
description: spec.description,
|
|
278
295
|
resolveType: (fieldType) => fieldType,
|
|
279
|
-
|
|
280
|
-
|
|
296
|
+
resolveSqlValue: () => pg_sql2_1.default.null,
|
|
297
|
+
resolve(sqlIdentifier, _sqlValue, input, _$where, _details) {
|
|
298
|
+
const geoJsonStr = pg_sql2_1.default.value(JSON.stringify(input));
|
|
299
|
+
const geomSql = pg_sql2_1.default.fragment `${sqlGeomFromGeoJSON}(${geoJsonStr}::text)${geographyCast}`;
|
|
300
|
+
return spec.resolve(sqlIdentifier, geomSql);
|
|
281
301
|
}
|
|
282
302
|
},
|
|
283
303
|
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import 'graphile-build';
|
|
2
|
+
import 'graphile-build-pg';
|
|
3
|
+
import 'graphile-connection-filter';
|
|
4
|
+
import type { GraphileConfig } from 'graphile-config';
|
|
5
|
+
/**
|
|
6
|
+
* PostgisSpatialRelationsPlugin
|
|
7
|
+
*
|
|
8
|
+
* Adds cross-table spatial filtering to `graphile-connection-filter` by
|
|
9
|
+
* reading a `@spatialRelation` smart tag on geometry/geography columns and
|
|
10
|
+
* synthesising a virtual relation + filter field that emits an EXISTS
|
|
11
|
+
* subquery joined by a PostGIS predicate (e.g. `ST_Contains`, `ST_DWithin`).
|
|
12
|
+
*
|
|
13
|
+
* The regular `ConnectionFilterBackwardRelationsPlugin` is FK-driven — it
|
|
14
|
+
* joins on column equality. Spatial relationships are not backed by FKs, so
|
|
15
|
+
* this plugin hooks the same `pgCodec`-scoped filter input types and injects
|
|
16
|
+
* its own fields whose `apply()` emits `ST_<op>(...)` instead of `a = b`.
|
|
17
|
+
*
|
|
18
|
+
* Tag grammar:
|
|
19
|
+
*
|
|
20
|
+
* ```sql
|
|
21
|
+
* COMMENT ON COLUMN <owner_table>.<owner_col> IS
|
|
22
|
+
* E'@spatialRelation <relation_name> <target_ref> <operator> [<param_name>]';
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* - `target_ref` — `schema.table.col` or `table.col` (same schema as owner).
|
|
26
|
+
* - `operator` — one of the PG-native snake_case ops in OPERATOR_REGISTRY.
|
|
27
|
+
* - `param_name` — required iff the operator is parametric (currently
|
|
28
|
+
* only `st_dwithin`, which needs a distance).
|
|
29
|
+
*
|
|
30
|
+
* Examples:
|
|
31
|
+
*
|
|
32
|
+
* ```sql
|
|
33
|
+
* -- Point in polygon
|
|
34
|
+
* COMMENT ON COLUMN telemedicine_clinics.location IS
|
|
35
|
+
* E'@spatialRelation county counties.geom st_contains';
|
|
36
|
+
*
|
|
37
|
+
* -- Self-referential radius search
|
|
38
|
+
* COMMENT ON COLUMN telemedicine_clinics.location IS
|
|
39
|
+
* E'@spatialRelation nearbyClinic telemedicine_clinics.location st_dwithin distance';
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* Generated GraphQL (for the `st_dwithin` case):
|
|
43
|
+
*
|
|
44
|
+
* ```graphql
|
|
45
|
+
* telemedicineClinics(where: {
|
|
46
|
+
* nearbyClinic: {
|
|
47
|
+
* distance: 5000,
|
|
48
|
+
* some: { specialty: { eq: "pediatrics" } }
|
|
49
|
+
* }
|
|
50
|
+
* })
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* The generated SQL uses the same EXISTS pattern as backward relations but
|
|
54
|
+
* substitutes `ST_<op>(...)` for column equality:
|
|
55
|
+
*
|
|
56
|
+
* ```sql
|
|
57
|
+
* WHERE EXISTS (
|
|
58
|
+
* SELECT 1 FROM <target_table> other
|
|
59
|
+
* WHERE ST_<op>(other.<target_col>, self.<owner_col>[, distance])
|
|
60
|
+
* AND other.<pk> <> self.<pk> -- self-relations only
|
|
61
|
+
* AND <nested filter conditions>
|
|
62
|
+
* )
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export interface SpatialOperatorRegistration {
|
|
66
|
+
/** Tag-facing op name (PG-native snake_case). */
|
|
67
|
+
name: string;
|
|
68
|
+
/** Kind of PG-level operator. */
|
|
69
|
+
kind: 'function' | 'infix';
|
|
70
|
+
/**
|
|
71
|
+
* For `kind: 'function'`, the PG function name (snake_case) resolved
|
|
72
|
+
* against the PostGIS schema at SQL-emit time. For `kind: 'infix'`,
|
|
73
|
+
* the PG binary operator token (e.g. `&&`).
|
|
74
|
+
*/
|
|
75
|
+
pgToken: string;
|
|
76
|
+
/** Whether this op takes an extra numeric parameter (e.g. `st_dwithin`). */
|
|
77
|
+
parametric: boolean;
|
|
78
|
+
description: string;
|
|
79
|
+
}
|
|
80
|
+
export declare const OPERATOR_REGISTRY: Record<string, SpatialOperatorRegistration>;
|
|
81
|
+
export interface SpatialRelationInfo {
|
|
82
|
+
/** GraphQL-facing relation name, derived from the tag. */
|
|
83
|
+
relationName: string;
|
|
84
|
+
/** The codec that owns the tag (outer side of the EXISTS). */
|
|
85
|
+
ownerCodec: any;
|
|
86
|
+
/** The owning attribute name (column). */
|
|
87
|
+
ownerAttributeName: string;
|
|
88
|
+
/** Qualified target resource (inner side of the EXISTS). */
|
|
89
|
+
targetResource: any;
|
|
90
|
+
/** Column name on the target resource. */
|
|
91
|
+
targetAttributeName: string;
|
|
92
|
+
/** Resolved operator. */
|
|
93
|
+
operator: SpatialOperatorRegistration;
|
|
94
|
+
/** Field name for the parametric argument, if any. */
|
|
95
|
+
paramFieldName: string | null;
|
|
96
|
+
/** Whether owner === target (self-relation needs row exclusion). */
|
|
97
|
+
isSelfRelation: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Cached primary-key attribute names for the owner+target codecs. Used
|
|
100
|
+
* to synthesise the self-exclusion predicate (`other.<pk> <> self.<pk>`).
|
|
101
|
+
* `null` if the codec has no discoverable PK.
|
|
102
|
+
*/
|
|
103
|
+
ownerPkAttributes: string[] | null;
|
|
104
|
+
targetPkAttributes: string[] | null;
|
|
105
|
+
}
|
|
106
|
+
interface TagParseResult {
|
|
107
|
+
ok: true;
|
|
108
|
+
relationName: string;
|
|
109
|
+
targetRef: string;
|
|
110
|
+
operator: string;
|
|
111
|
+
paramName: string | null;
|
|
112
|
+
}
|
|
113
|
+
interface TagParseError {
|
|
114
|
+
ok: false;
|
|
115
|
+
error: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parse a single `@spatialRelation` tag value.
|
|
119
|
+
*
|
|
120
|
+
* Accepts a string of the form `<name> <target> <op> [<param>]`.
|
|
121
|
+
*/
|
|
122
|
+
export declare function parseSpatialRelationTag(raw: string): TagParseResult | TagParseError;
|
|
123
|
+
/**
|
|
124
|
+
* Build the full set of spatial relations from all resources.
|
|
125
|
+
* Validates tags and throws (at schema build) on anything malformed.
|
|
126
|
+
* Returns relations keyed by (owner codec identity, relation name).
|
|
127
|
+
*/
|
|
128
|
+
export declare function collectSpatialRelations(build: any): SpatialRelationInfo[];
|
|
129
|
+
export declare const PostgisSpatialRelationsPlugin: GraphileConfig.Plugin;
|
|
130
|
+
export {};
|