Skip to content

Commit 0f1ca8f

Browse files
committed
fix: geojson invalid query on unavailable PostGIS
Closes #4245. It adds a query on schema cache construction, but doesn't add a new attribute to the SchemaCache type. We only need the information to be passed to the `initialMediaHandlers` function to build the builtin media handlers.
1 parent 7870add commit 0f1ca8f

File tree

4 files changed

+49
-9
lines changed

4 files changed

+49
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1818
- Fix OpenAPI broken docs link by @taimoorzaeem in #4080
1919
- Fix OpenAPI specification incorrectly exposing GET methods for volatile functions by @joelonsql in #4174
2020
- Fix empty spread embeddings return unexpected SQL error by @taimoorzaeem in #3887
21+
- Fix `Accept: application/geo+json` generating an invalid query when PostGIS is not available by @steve-chavez in #4245
2122

2223
### Changed
2324

src/PostgREST/SchemaCache.hs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ querySchemaCache conf@AppConfig{..} = do
152152
reps <- SQL.statement conf $ dataRepresentations prepared
153153
mHdlers <- SQL.statement conf $ mediaHandlers prepared
154154
tzones <- SQL.statement mempty $ timezones prepared
155+
hasPgis <- SQL.statement conf $ postgisFunc prepared
155156
_ <-
156157
let sleepCall = SQL.Statement "select pg_sleep($1 / 1000.0)" (param HE.int4) HD.noResult prepared in
157158
whenJust configInternalSCSleep (`SQL.statement` sleepCall) -- only used for testing
@@ -164,7 +165,7 @@ querySchemaCache conf@AppConfig{..} = do
164165
, dbRelationships = getOverrideRelationshipsMap rels cRels
165166
, dbRoutines = funcs
166167
, dbRepresentations = reps
167-
, dbMediaHandlers = HM.union mHdlers initialMediaHandlers -- the custom handlers will override the initial ones
168+
, dbMediaHandlers = HM.union mHdlers $ initialMediaHandlers hasPgis -- the custom handlers will override the initial ones
168169
, dbTimezones = tzones
169170
}
170171
where
@@ -1048,12 +1049,14 @@ allViewsKeyDependencies =
10481049
having ncol = array_length(array_agg(row(col.attname, view_columns) order by pks_fks.ord), 1)
10491050
|]
10501051

1051-
initialMediaHandlers :: MediaHandlerMap
1052-
initialMediaHandlers =
1052+
initialMediaHandlers :: Bool -> MediaHandlerMap
1053+
initialMediaHandlers hasPostgisFunc =
10531054
HM.insert (RelAnyElement, MediaType.MTAny ) (BuiltinOvAggJson, MediaType.MTApplicationJSON) $
10541055
HM.insert (RelAnyElement, MediaType.MTApplicationJSON) (BuiltinOvAggJson, MediaType.MTApplicationJSON) $
10551056
HM.insert (RelAnyElement, MediaType.MTTextCSV ) (BuiltinOvAggCsv, MediaType.MTTextCSV) $
1056-
HM.insert (RelAnyElement, MediaType.MTGeoJSON ) (BuiltinOvAggGeoJson, MediaType.MTGeoJSON)
1057+
(if hasPostgisFunc
1058+
then HM.insert (RelAnyElement, MediaType.MTGeoJSON ) (BuiltinOvAggGeoJson, MediaType.MTGeoJSON)
1059+
else mempty)
10571060
HM.empty
10581061

10591062
mediaHandlers :: Bool -> SQL.Statement AppConfig MediaHandlerMap
@@ -1139,6 +1142,35 @@ timezones = SQL.Statement sql HE.noParams decodeTimezones
11391142
decodeTimezones :: HD.Result TimezoneNames
11401143
decodeTimezones = S.fromList <$> HD.rowList (column HD.text)
11411144

1145+
1146+
-- Find the postgis function that has the signature:
1147+
-- st_asgeojson(record,...) returns text
1148+
postgisFunc :: Bool -> SQL.Statement AppConfig Bool
1149+
postgisFunc = SQL.Statement sql params decoder
1150+
where
1151+
params =
1152+
(map escapeIdent . toList . configDbSchemas >$< arrayParam HE.text) <>
1153+
(map escapeIdent . toList . configDbExtraSearchPath >$< arrayParam HE.text)
1154+
decoder = HD.singleRow (column HD.bool)
1155+
sql = encodeUtf8 [trimming|
1156+
SELECT
1157+
exists(
1158+
SELECT
1159+
1
1160+
FROM pg_catalog.pg_proc AS p
1161+
JOIN pg_catalog.pg_depend AS d
1162+
ON d.objid = p.oid
1163+
AND d.deptype = 'e'
1164+
JOIN pg_catalog.pg_extension AS e
1165+
ON e.oid = d.refobjid
1166+
WHERE p.pronamespace = ANY($$1::regnamespace[] || $$2::regnamespace[])
1167+
AND p.proname = 'st_asgeojson'
1168+
AND e.extname = 'postgis'
1169+
AND p.proargtypes[0] = 'record'::regtype
1170+
AND pg_get_function_result(p.oid) = 'text'
1171+
);
1172+
|]
1173+
11421174
param :: HE.Value a -> HE.Params a
11431175
param = HE.param . HE.nonNullable
11441176

test/io/__snapshots__/test_cli/test_schema_cache_snapshot[dbMediaHandlers].yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@
33
- - tag: BuiltinOvAggCsv
44
- tag: MTTextCSV
55

6-
- - - tag: RelAnyElement
7-
- tag: MTGeoJSON
8-
- - tag: BuiltinOvAggGeoJson
9-
- tag: MTGeoJSON
10-
116
- - - tag: RelAnyElement
127
- tag: MTApplicationJSON
138
- - tag: BuiltinOvAggJson

test/io/test_io.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,18 @@ def test_no_pool_connection_required_on_bad_embedding(defaultenv):
11371137
assert response.status_code == 400
11381138

11391139

1140+
def test_no_pool_connection_required_on_unavailable_postgis(defaultenv):
1141+
"no pool connection should be consumed when PostGIS is not available, the request should be quickly rejected at the plan level"
1142+
1143+
headers = {
1144+
"Accept": "application/geo+json",
1145+
}
1146+
1147+
with run(env=defaultenv, no_pool_connection_available=True) as postgrest:
1148+
response = postgrest.session.get("/projects", headers=headers)
1149+
assert response.status_code == 406
1150+
1151+
11401152
# https://github.com/PostgREST/postgrest/issues/2620
11411153
def test_notify_reloading_catalog_cache(defaultenv):
11421154
"notify should reload the connection catalog cache"

0 commit comments

Comments
 (0)