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.
@@ -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
- resolve(sqlIdentifier, sqlValue, _input, _$where, _details) {
280
- return spec.resolve(sqlIdentifier, sqlValue);
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 {};