diff --git a/CHANGELOG.md b/CHANGELOG.md index e8e64fd8d..5741521ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/PostgREST/SchemaCache.hs b/src/PostgREST/SchemaCache.hs index df21aede2..37120663a 100644 --- a/src/PostgREST/SchemaCache.hs +++ b/src/PostgREST/SchemaCache.hs @@ -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 diff --git a/test/io/__snapshots__/test_cli/test_schema_cache_snapshot[dbMediaHandlers].yaml b/test/io/__snapshots__/test_cli/test_schema_cache_snapshot[dbMediaHandlers].yaml index 7ad8a3f44..442adf959 100644 --- a/test/io/__snapshots__/test_cli/test_schema_cache_snapshot[dbMediaHandlers].yaml +++ b/test/io/__snapshots__/test_cli/test_schema_cache_snapshot[dbMediaHandlers].yaml @@ -3,11 +3,6 @@ - - tag: BuiltinOvAggCsv - tag: MTTextCSV -- - - tag: RelAnyElement - - tag: MTGeoJSON - - - tag: BuiltinOvAggGeoJson - - tag: MTGeoJSON - - - - tag: RelAnyElement - tag: MTApplicationJSON - - tag: BuiltinOvAggJson diff --git a/test/io/test_io.py b/test/io/test_io.py index 33c7d781e..4fb93f435 100644 --- a/test/io/test_io.py +++ b/test/io/test_io.py @@ -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"