fix: geojson invalid query on unavailable PostGIS

Closes https://github.com/PostgREST/postgrest/issues/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.
This commit is contained in:
steve-chavez
2025-08-06 10:20:01 -05:00
committed by Steve Chavez
parent 7870add603
commit 0f1ca8faac
4 changed files with 49 additions and 9 deletions
+1
View File
@@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Fix OpenAPI broken docs link by @taimoorzaeem in #4080
- Fix OpenAPI specification incorrectly exposing GET methods for volatile functions by @joelonsql in #4174
- Fix empty spread embeddings return unexpected SQL error by @taimoorzaeem in #3887
- Fix `Accept: application/geo+json` generating an invalid query when PostGIS is not available by @steve-chavez in #4245
### Changed
+36 -4
View File
@@ -152,6 +152,7 @@ querySchemaCache conf@AppConfig{..} = do
reps <- SQL.statement conf $ dataRepresentations prepared
mHdlers <- SQL.statement conf $ mediaHandlers prepared
tzones <- SQL.statement mempty $ timezones prepared
hasPgis <- SQL.statement conf $ postgisFunc prepared
_ <-
let sleepCall = SQL.Statement "select pg_sleep($1 / 1000.0)" (param HE.int4) HD.noResult prepared in
whenJust configInternalSCSleep (`SQL.statement` sleepCall) -- only used for testing
@@ -164,7 +165,7 @@ querySchemaCache conf@AppConfig{..} = do
, dbRelationships = getOverrideRelationshipsMap rels cRels
, dbRoutines = funcs
, dbRepresentations = reps
, dbMediaHandlers = HM.union mHdlers initialMediaHandlers -- the custom handlers will override the initial ones
, dbMediaHandlers = HM.union mHdlers $ initialMediaHandlers hasPgis -- the custom handlers will override the initial ones
, dbTimezones = tzones
}
where
@@ -1048,12 +1049,14 @@ allViewsKeyDependencies =
having ncol = array_length(array_agg(row(col.attname, view_columns) order by pks_fks.ord), 1)
|]
initialMediaHandlers :: MediaHandlerMap
initialMediaHandlers =
initialMediaHandlers :: Bool -> MediaHandlerMap
initialMediaHandlers hasPostgisFunc =
HM.insert (RelAnyElement, MediaType.MTAny ) (BuiltinOvAggJson, MediaType.MTApplicationJSON) $
HM.insert (RelAnyElement, MediaType.MTApplicationJSON) (BuiltinOvAggJson, MediaType.MTApplicationJSON) $
HM.insert (RelAnyElement, MediaType.MTTextCSV ) (BuiltinOvAggCsv, MediaType.MTTextCSV) $
HM.insert (RelAnyElement, MediaType.MTGeoJSON ) (BuiltinOvAggGeoJson, MediaType.MTGeoJSON)
(if hasPostgisFunc
then HM.insert (RelAnyElement, MediaType.MTGeoJSON ) (BuiltinOvAggGeoJson, MediaType.MTGeoJSON)
else mempty)
HM.empty
mediaHandlers :: Bool -> SQL.Statement AppConfig MediaHandlerMap
@@ -1139,6 +1142,35 @@ timezones = SQL.Statement sql HE.noParams decodeTimezones
decodeTimezones :: HD.Result TimezoneNames
decodeTimezones = S.fromList <$> HD.rowList (column HD.text)
-- Find the postgis function that has the signature:
-- st_asgeojson(record,...) returns text
postgisFunc :: Bool -> SQL.Statement AppConfig Bool
postgisFunc = SQL.Statement sql params decoder
where
params =
(map escapeIdent . toList . configDbSchemas >$< arrayParam HE.text) <>
(map escapeIdent . toList . configDbExtraSearchPath >$< arrayParam HE.text)
decoder = HD.singleRow (column HD.bool)
sql = encodeUtf8 [trimming|
SELECT
exists(
SELECT
1
FROM pg_catalog.pg_proc AS p
JOIN pg_catalog.pg_depend AS d
ON d.objid = p.oid
AND d.deptype = 'e'
JOIN pg_catalog.pg_extension AS e
ON e.oid = d.refobjid
WHERE p.pronamespace = ANY($$1::regnamespace[] || $$2::regnamespace[])
AND p.proname = 'st_asgeojson'
AND e.extname = 'postgis'
AND p.proargtypes[0] = 'record'::regtype
AND pg_get_function_result(p.oid) = 'text'
);
|]
param :: HE.Value a -> HE.Params a
param = HE.param . HE.nonNullable
@@ -3,11 +3,6 @@
- - tag: BuiltinOvAggCsv
- tag: MTTextCSV
- - - tag: RelAnyElement
- tag: MTGeoJSON
- - tag: BuiltinOvAggGeoJson
- tag: MTGeoJSON
- - - tag: RelAnyElement
- tag: MTApplicationJSON
- - tag: BuiltinOvAggJson
+12
View File
@@ -1137,6 +1137,18 @@ def test_no_pool_connection_required_on_bad_embedding(defaultenv):
assert response.status_code == 400
def test_no_pool_connection_required_on_unavailable_postgis(defaultenv):
"no pool connection should be consumed when PostGIS is not available, the request should be quickly rejected at the plan level"
headers = {
"Accept": "application/geo+json",
}
with run(env=defaultenv, no_pool_connection_available=True) as postgrest:
response = postgrest.session.get("/projects", headers=headers)
assert response.status_code == 406
# https://github.com/PostgREST/postgrest/issues/2620
def test_notify_reloading_catalog_cache(defaultenv):
"notify should reload the connection catalog cache"