From 870f7a39b077269faebdc32006e7c41acff79b4a Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Wed, 22 Jul 2020 13:49:39 -0500 Subject: [PATCH] Add PostGIS GeoJSON support Works for GET, POST, PATCH, PUT, DELETE, RPC. --- CHANGELOG.md | 5 + default.nix | 12 +- postgrest.cabal | 1 + src/PostgREST/App.hs | 3 + src/PostgREST/ContentType.hs | 3 + src/PostgREST/Query/SqlFragment.hs | 4 + src/PostgREST/Query/Statements.hs | 15 +- src/PostgREST/Request/ApiRequest.hs | 2 +- test/spec/Feature/Query/PostGISSpec.hs | 232 +++++++++++++++++++++++++ test/spec/Main.hs | 4 +- test/spec/fixtures/data.sql | 11 ++ test/spec/fixtures/privileges.sql | 2 + test/spec/fixtures/schema.sql | 20 +++ 13 files changed, 300 insertions(+), 14 deletions(-) create mode 100644 test/spec/Feature/Query/PostGISSpec.hs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6663b94b9..bbd2bcd59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2268, Allow returning XML from single-column queries - @fjf2002 - #2300, RPC POST for function w/single unnamed XML param #2300 - @fjf2002 - #1959, Bulk update with PATCH - @steve-chavez + - #1564, Allow geojson output by specifying the `Accept: application/geo+json` media type - @steve-chavez + + Requires postgis >= 3.0 + + Works for GET, RPC, POST/PATCH/DELETE with `Prefer: return=representation`. + + Resource embedding works and the embedded rows will go into the `properties` key + + In case of multiple geometries in the same table, you can choose which one will go into the `geometry` key with the usual `?select` query parameter. ### Fixed diff --git a/default.nix b/default.nix index c057494c1..8b56de60a 100644 --- a/default.nix +++ b/default.nix @@ -46,12 +46,12 @@ let postgresqlVersions = [ - { name = "postgresql-14"; postgresql = pkgs.postgresql_14; } - { name = "postgresql-13"; postgresql = pkgs.postgresql_13; } - { name = "postgresql-12"; postgresql = pkgs.postgresql_12; } - { name = "postgresql-11"; postgresql = pkgs.postgresql_11; } - { name = "postgresql-10"; postgresql = pkgs.postgresql_10; } - { name = "postgresql-9.6"; postgresql = pkgs.postgresql_9_6; } + { name = "postgresql-14"; postgresql = pkgs.postgresql_14.withPackages (p: [ p.postgis ]); } + { name = "postgresql-13"; postgresql = pkgs.postgresql_13.withPackages (p: [ p.postgis ]); } + { name = "postgresql-12"; postgresql = pkgs.postgresql_12.withPackages (p: [ p.postgis ]); } + { name = "postgresql-11"; postgresql = pkgs.postgresql_11.withPackages (p: [ p.postgis ]); } + { name = "postgresql-10"; postgresql = pkgs.postgresql_10.withPackages (p: [ p.postgis ]); } + { name = "postgresql-9.6"; postgresql = pkgs.postgresql_9_6.withPackages (p: [ p.postgis ]); } ]; patches = diff --git a/postgrest.cabal b/postgrest.cabal index 1663009dd..6a63257ed 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -199,6 +199,7 @@ test-suite spec Feature.Query.JsonOperatorSpec Feature.Query.MultipleSchemaSpec Feature.Query.ErrorSpec + Feature.Query.PostGISSpec Feature.Query.QueryLimitedSpec Feature.Query.QuerySpec Feature.Query.RangeSpec diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index afd97fdf5..9e92c3cc9 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -271,6 +271,7 @@ handleRead headersOnly identifier context@RequestContext{..} = do (shouldCount iPreferCount) (iAcceptContentType == CTTextCSV) (iAcceptContentType == CTTextXML) + (iAcceptContentType == CTGeoJSON) bField configDbPreparedStatements @@ -464,6 +465,7 @@ handleInvoke invMethod proc context@RequestContext{..} = do (iAcceptContentType == CTSingularJSON) (iAcceptContentType == CTTextCSV) (iAcceptContentType == CTTextXML) + (iAcceptContentType == CTGeoJSON) (iPreferParameters == Just MultipleObjects) bField (configDbPreparedStatements ctxConfig) @@ -551,6 +553,7 @@ writeQuery mutation identifier@QualifiedIdentifier{..} isInsert pkCols context@R (iAcceptContentType ctxApiRequest == CTSingularJSON) isInsert (iAcceptContentType ctxApiRequest == CTTextCSV) + (iAcceptContentType ctxApiRequest == CTGeoJSON) (iPreferRepresentation ctxApiRequest) pkCols (configDbPreparedStatements ctxConfig) diff --git a/src/PostgREST/ContentType.hs b/src/PostgREST/ContentType.hs index 40c4a1384..ced8e5b8c 100644 --- a/src/PostgREST/ContentType.hs +++ b/src/PostgREST/ContentType.hs @@ -18,6 +18,7 @@ import Protolude data ContentType = CTApplicationJSON | CTSingularJSON + | CTGeoJSON | CTTextCSV | CTTextPlain | CTTextXML @@ -40,6 +41,7 @@ toHeader ct = (hContentType, toMime ct <> charset) -- | Convert from ContentType to a ByteString representing the mime type toMime :: ContentType -> ByteString toMime CTApplicationJSON = "application/json" +toMime CTGeoJSON = "application/geo+json" toMime CTTextCSV = "text/csv" toMime CTTextPlain = "text/plain" toMime CTTextXML = "text/xml" @@ -55,6 +57,7 @@ decodeContentType :: BS.ByteString -> ContentType decodeContentType ct = case BS.takeWhile (/= BS.c2w ';') ct of "application/json" -> CTApplicationJSON + "application/geo+json" -> CTGeoJSON "text/csv" -> CTTextCSV "text/plain" -> CTTextPlain "text/xml" -> CTTextXML diff --git a/src/PostgREST/Query/SqlFragment.hs b/src/PostgREST/Query/SqlFragment.hs index bff595d90..269b76c48 100644 --- a/src/PostgREST/Query/SqlFragment.hs +++ b/src/PostgREST/Query/SqlFragment.hs @@ -11,6 +11,7 @@ module PostgREST.Query.SqlFragment , SqlFragment , asBinaryF , asCsvF + , asGeoJsonF , asJsonF , asJsonSingleF , asXmlF @@ -182,6 +183,9 @@ asJsonSingleF returnsScalar asXmlF :: FieldName -> SqlFragment asXmlF fieldName = "coalesce(xmlagg(_postgrest_t." <> pgFmtIdent fieldName <> "), '')" +asGeoJsonF :: SqlFragment +asGeoJsonF = "json_build_object('type', 'FeatureCollection', 'features', coalesce(json_agg(ST_AsGeoJSON(_postgrest_t)::json), '[]'))" + asBinaryF :: FieldName -> SqlFragment asBinaryF fieldName = "coalesce(string_agg(_postgrest_t." <> pgFmtIdent fieldName <> ", ''), '')" diff --git a/src/PostgREST/Query/Statements.hs b/src/PostgREST/Query/Statements.hs index d11255c8a..e188b0f89 100644 --- a/src/PostgREST/Query/Statements.hs +++ b/src/PostgREST/Query/Statements.hs @@ -43,10 +43,10 @@ import Protolude -} type ResultsWithCount = (Maybe Int64, Int64, [BS.ByteString], BS.ByteString, Either Error [GucHeader], Either Error (Maybe Status)) -createWriteStatement :: SQL.Snippet -> SQL.Snippet -> Bool -> Bool -> Bool -> +createWriteStatement :: SQL.Snippet -> SQL.Snippet -> Bool -> Bool -> Bool -> Bool -> PreferRepresentation -> [Text] -> Bool -> SQL.Statement () ResultsWithCount -createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys = +createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv asGeoJson rep pKeys = SQL.dynamicallyParameterized snippet decodeStandard where snippet = @@ -74,6 +74,7 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys bodyF | rep /= Full = "''" | asCsv = asCsvF + | asGeoJson = asGeoJsonF | wantSingle = asJsonSingleF False | otherwise = asJsonF False @@ -86,9 +87,9 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys decodeStandard = fromMaybe (Nothing, 0, [], mempty, Right [], Right Nothing) <$> HD.rowMaybe standardRow -createReadStatement :: SQL.Snippet -> SQL.Snippet -> Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> Bool -> +createReadStatement :: SQL.Snippet -> SQL.Snippet -> Bool -> Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> Bool -> SQL.Statement () ResultsWithCount -createReadStatement selectQuery countQuery isSingle countTotal asCsv asXml binaryField = +createReadStatement selectQuery countQuery isSingle countTotal asCsv asXml asGeoJson binaryField = SQL.dynamicallyParameterized snippet decodeStandard where snippet = @@ -109,6 +110,7 @@ createReadStatement selectQuery countQuery isSingle countTotal asCsv asXml binar bodyF | asCsv = asCsvF | isSingle = asJsonSingleF False + | asGeoJson = asGeoJsonF | isJust binaryField && asXml = asXmlF $ fromJust binaryField | isJust binaryField = asBinaryF $ fromJust binaryField | otherwise = asJsonF False @@ -130,9 +132,9 @@ standardRow = (,,,,,) <$> nullableColumn HD.int8 <*> column HD.int8 type ProcResults = (Maybe Int64, Int64, ByteString, Either Error [GucHeader], Either Error (Maybe Status)) callProcStatement :: Bool -> Bool -> SQL.Snippet -> SQL.Snippet -> SQL.Snippet -> Bool -> - Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> Bool -> + Bool -> Bool -> Bool -> Bool -> Bool -> Maybe FieldName -> Bool -> SQL.Statement () ProcResults -callProcStatement returnsScalar returnsSingle callProcQuery selectQuery countQuery countTotal asSingle asCsv asXml multObjects binaryField = +callProcStatement returnsScalar returnsSingle callProcQuery selectQuery countQuery countTotal asSingle asCsv asXml asGeoJson multObjects binaryField = SQL.dynamicallyParameterized snippet decodeProc where snippet = @@ -152,6 +154,7 @@ callProcStatement returnsScalar returnsSingle callProcQuery selectQuery countQue bodyF | asSingle = asJsonSingleF returnsScalar | asCsv = asCsvF + | asGeoJson = asGeoJsonF | isJust binaryField && asXml = asXmlF $ fromJust binaryField | isJust binaryField = asBinaryF $ fromJust binaryField | returnsSingle diff --git a/src/PostgREST/Request/ApiRequest.hs b/src/PostgREST/Request/ApiRequest.hs index fd1312077..221ba91c2 100644 --- a/src/PostgREST/Request/ApiRequest.hs +++ b/src/PostgREST/Request/ApiRequest.hs @@ -431,7 +431,7 @@ requestContentTypes conf action path = ++ rawContentTypes conf ++ [CTOpenAPI | pIsRootSpec path] defaultContentTypes = - [CTApplicationJSON, CTSingularJSON, CTTextCSV] + [CTApplicationJSON, CTSingularJSON, CTGeoJSON, CTTextCSV] rawContentTypes :: AppConfig -> [ContentType] rawContentTypes AppConfig{..} = diff --git a/test/spec/Feature/Query/PostGISSpec.hs b/test/spec/Feature/Query/PostGISSpec.hs new file mode 100644 index 000000000..a3e30dbb8 --- /dev/null +++ b/test/spec/Feature/Query/PostGISSpec.hs @@ -0,0 +1,232 @@ +module Feature.Query.PostGISSpec where + +import Network.Wai (Application) + +import Network.HTTP.Types +import Test.Hspec +import Test.Hspec.Wai +import Test.Hspec.Wai.JSON + +import PostgREST.Config.PgVersion (PgVersion, pgVersion100) + +import Protolude hiding (get) +import SpecHelper + +spec :: PgVersion -> SpecWith ((), Application) +spec actualPgVersion = describe "PostGIS features" $ + context "GeoJSON output" $ do + it "works for a table that has a geometry column" $ + request methodGet "/shops" + [("Accept", "application/geo+json")] "" `shouldRespondWith` + [json| { + "type" : "FeatureCollection", + "features" : [ + {"type": "Feature", "geometry": {"type":"Point","coordinates":[-71.10044,42.373695]}, "properties": {"id": 1, "address": "1369 Cambridge St"}} + , {"type": "Feature", "geometry": {"type":"Point","coordinates":[-71.10543,42.366432]}, "properties": {"id": 2, "address": "757 Massachusetts Ave"}} + , {"type": "Feature", "geometry": {"type":"Point","coordinates":[-71.081924,42.36437]}, "properties": {"id": 3, "address": "605 W Kendall St"}} + ]} |] + { matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] } + + it "fails for a table that doesn't have a geometry column" $ + request methodGet "/projects" + [("Accept", "application/geo+json")] "" `shouldRespondWith` + [json| {"hint":null,"details":null,"code":"22023","message":"geometry column is missing"} |] + { matchStatus = 400 + , matchHeaders = [matchContentTypeJson] + } + + it "gives an empty features array on no rows" $ + request methodGet "/shops?id=gt.3" + [("Accept", "application/geo+json")] "" `shouldRespondWith` + [json| { + "type" : "FeatureCollection", + "features" : []} |] + { matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] } + + it "must include the geometry column when using ?select" $ + request methodGet "/shops?select=id,shop_geom&id=eq.1" + [("Accept", "application/geo+json")] "" `shouldRespondWith` + [json| { + "type" : "FeatureCollection", + "features" : [ + {"type": "Feature", "geometry": {"type":"Point","coordinates":[-71.10044,42.373695]}, "properties": {"id": 1}} + ] }|] + { matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] } + + it "works with resource embedding" $ + request methodGet "/shops?select=*,shop_bles(*)&id=eq.1" + [("Accept", "application/geo+json")] "" `shouldRespondWith` + (if actualPgVersion >= pgVersion100 + then [json| { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" }, + "properties": { + "address": "1369 Cambridge St", "id": 1, + "shop_bles": [ + { "id": 1, "name": "Beacon-1", "shop_id": 1 , + "coords": { "coordinates": [ -71.10044, 42.373695 ], "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Point" }, + "range_area": { + "coordinates": [ [ [ -71.10045254230499, 42.37387083326593 ], [ -71.10048070549963, 42.37377126199953 ], [ -71.10039688646793, 42.37375838212269 ], [ -71.10037006437777, 42.37385844878863 ], [ -71.10045254230499, 42.37387083326593 ] ] ], + "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Polygon" } + }, + { "coords": { "coordinates": [ -71.10044, 42.373695 ], "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Point" }, + "id": 2, "name": "Beacon-2", "shop_id": 1, + "range_area": { + "coordinates": [ [ [ -71.10034391283989, 42.37385299961788 ], [ -71.10036939382553, 42.373756895982865 ], [ -71.1002916097641, 42.373745997623224 ], [ -71.1002641171217, 42.37384408279195 ], [ -71.10034391283989, 42.37385299961788 ] ] ], + "crs": { "properties": { "name": "EPSG:4326" }, "type": "name" }, "type": "Polygon" } + } + ] + } + } + ] + }|] + else [json| { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" }, + "properties": { + "address": "1369 Cambridge St", "id": 1, + "shop_bles": [ + { "id": 1, "name": "Beacon-1", "shop_id": 1 , + "coords": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" }, + "range_area": { + "coordinates": [ [ [ -71.10045254230499, 42.37387083326593 ], [ -71.10048070549963, 42.37377126199953 ], [ -71.10039688646793, 42.37375838212269 ], [ -71.10037006437777, 42.37385844878863 ], [ -71.10045254230499, 42.37387083326593 ] ] ], + "type": "Polygon" } + }, + { "coords": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" }, + "id": 2, "name": "Beacon-2", "shop_id": 1, + "range_area": { + "coordinates": [ [ [ -71.10034391283989, 42.37385299961788 ], [ -71.10036939382553, 42.373756895982865 ], [ -71.1002916097641, 42.373745997623224 ], [ -71.1002641171217, 42.37384408279195 ], [ -71.10034391283989, 42.37385299961788 ] ] ], + "type": "Polygon" } + } + ] + } + } + ] + }|]) + { matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] } + + it "works with RPC" $ + request methodGet "/rpc/get_shop?id=1" + [("Accept", "application/geo+json")] "" `shouldRespondWith` + [json|{ + "type" : "FeatureCollection", + "features" : [ + {"type": "Feature", "geometry": {"type":"Point","coordinates":[-71.10044,42.373695]}, "properties": {"id": 1, "address": "1369 Cambridge St"} } + ] + }|] + { matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] } + + it "works with Prefer: return=representation after POST" $ + request methodPost "/shops" + [("Accept", "application/geo+json"), ("Prefer", "return=representation")] [json| + {"id": 4, "address": "1354 Massachusetts Ave", "shop_geom": "SRID=4326;POINT(-71.11834 42.373238)"} + |] `shouldRespondWith` + [json|{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { "coordinates": [ -71.11834, 42.373238 ], "type": "Point" }, + "properties": { "address": "1354 Massachusetts Ave", "id": 4 } + } + ] + }|] + { matchStatus = 201 + , matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] + } + + it "works with Prefer: return=representation after PATCH" $ + request methodPatch "/shops?id=eq.3" + [("Accept", "application/geo+json"), ("Prefer", "return=representation")] + [json| { "address": "1354 Massachusetts Avenue"} |] + `shouldRespondWith` + [json|{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { "coordinates": [-71.081924,42.36437], "type": "Point" }, + "properties": { "address": "1354 Massachusetts Avenue", "id": 3 } + } + ] + }|] + { matchStatus = 200 + , matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] + } + + it "works with Prefer: return=representation after PUT" $ + request methodPut "/shops?id=eq.3" + [("Accept", "application/geo+json"), ("Prefer", "return=representation")] + [json| { "id": 3, "address": "1354 Massachusetts Avenue"} |] + `shouldRespondWith` + [json|{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { "coordinates": [-71.081924,42.36437], "type": "Point" }, + "properties": { "address": "1354 Massachusetts Avenue", "id": 3 } + } + ] + }|] + { matchStatus = 200 + , matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] + } + + it "works with Prefer: return=representation after DELETE" $ + request methodDelete "/shops?id=eq.3" + [("Accept", "application/geo+json"), ("Prefer", "return=representation")] "" `shouldRespondWith` + [json|{ + "type" : "FeatureCollection", + "features" : [ + {"type": "Feature", "geometry": {"type":"Point","coordinates":[-71.081924,42.36437]}, "properties": {"id": 3, "address": "605 W Kendall St"}} + ] + }|] + { matchStatus = 200 + , matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] + } + + context "multiple geometry columns" $ + it "can select the geometry column to get as a feature with ?select" $ do + request methodGet "/shop_bles?select=id,name,coords&id=eq.1" + [("Accept", "application/geo+json")] "" `shouldRespondWith` + [json|{ + "type" : "FeatureCollection", + "features" : [ + {"type": "Feature", + "geometry": {"type":"Point","coordinates":[-71.10044,42.373695]}, + "properties": {"id": 1, "name": "Beacon-1"} + } + ]}|] + { matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] } + + request methodGet "/shop_bles?select=id,name,range_area&id=eq.1" + [("Accept", "application/geo+json")] "" `shouldRespondWith` + [json|{ + "type" : "FeatureCollection", + "features" : [ + {"type": "Feature", + "geometry": {"type":"Polygon","coordinates":[[[-71.100452542,42.373870833],[-71.100480705,42.373771262],[-71.100396886,42.373758382],[-71.100370064,42.373858449],[-71.100452542,42.373870833]]]}, + "properties": {"id": 1, "name": "Beacon-1"}} + ]}|] + { matchHeaders = ["Content-Type" <:> "application/geo+json; charset=utf-8"] } + + it "gets the geojson geometry object with the regular application/json output" $ + request methodGet "/shops?id=eq.1" [] "" `shouldRespondWith` + (if actualPgVersion >= pgVersion100 + then [json|[{ + "id":1,"address":"1369 Cambridge St", + "shop_geom":{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-71.10044,42.373695]} + }]|] + else [json|[{ + "address": "1369 Cambridge St", + "id": 1, + "shop_geom": { "coordinates": [ -71.10044, 42.373695 ], "type": "Point" } + }]|]) + { matchHeaders = [matchContentTypeJson] } diff --git a/test/spec/Main.hs b/test/spec/Main.hs index b5ee37fbe..ab8120def 100644 --- a/test/spec/Main.hs +++ b/test/spec/Main.hs @@ -44,6 +44,7 @@ import qualified Feature.Query.HtmlRawOutputSpec import qualified Feature.Query.InsertSpec import qualified Feature.Query.JsonOperatorSpec import qualified Feature.Query.MultipleSchemaSpec +import qualified Feature.Query.PostGISSpec import qualified Feature.Query.QueryLimitedSpec import qualified Feature.Query.QuerySpec import qualified Feature.Query.RangeSpec @@ -199,8 +200,9 @@ main = do describe "Feature.Query.ErrorSpec" Feature.Query.ErrorSpec.spec -- this test runs with an extra search path - parallel $ before extraSearchPathApp $ + parallel $ before extraSearchPathApp $ do describe "Feature.ExtraSearchPathSpec" Feature.ExtraSearchPathSpec.spec + describe "Feature.Query.PostGISSpec" $ Feature.Query.PostGISSpec.spec actualPgVersion -- this test runs with a root spec function override parallel $ before rootSpecApp $ diff --git a/test/spec/fixtures/data.sql b/test/spec/fixtures/data.sql index 68f48e3d6..fd62da7d1 100644 --- a/test/spec/fixtures/data.sql +++ b/test/spec/fixtures/data.sql @@ -784,3 +784,14 @@ INSERT INTO test.bulk_update_items (id, name, observation) VALUES (1, 'item-1', TRUNCATE TABLE test.bulk_update_items_cpk CASCADE; INSERT INTO test.bulk_update_items_cpk (id, name, observation) VALUES (1, 'item-1', NULL), (2, 'item-2', NULL), (3, 'item-3', NULL); + +TRUNCATE TABLE shops CASCADE; +INSERT INTO shops(id, address, shop_geom) VALUES(1, '1369 Cambridge St', 'SRID=4326;POINT(-71.10044 42.373695)'); +INSERT INTO shops(id, address, shop_geom) VALUES(2, '757 Massachusetts Ave', 'SRID=4326;POINT(-71.10543 42.366432)'); +INSERT INTO shops(id, address, shop_geom) VALUES(3, '605 W Kendall St', 'SRID=4326;POINT(-71.081924 42.36437)'); + +TRUNCATE TABLE shop_bles CASCADE; +INSERT INTO shop_bles(id, name, coords, shop_id, range_area) VALUES(1, 'Beacon-1', 'SRID=4326;POINT(-71.10044 42.373695)', 1, + extensions.ST_GeomFromGeoJSON('{"type": "Polygon", "coordinates": [ [ [ -71.10045254230499, 42.37387083326593 ], [ -71.10048070549963, 42.37377126199953 ], [ -71.10039688646793, 42.37375838212269 ], [ -71.10037006437777, 42.37385844878863 ], [ -71.10045254230499, 42.37387083326593 ] ] ]}')); +INSERT INTO shop_bles(id, name, coords, shop_id, range_area) VALUES(2, 'Beacon-2', 'SRID=4326;POINT(-71.10044 42.373695)', 1, + extensions.ST_GeomFromGeoJSON('{"type": "Polygon", "coordinates": [ [ [ -71.10034391283989, 42.37385299961788 ], [ -71.10036939382553, 42.373756895982865 ], [ -71.1002916097641, 42.373745997623224 ], [ -71.1002641171217, 42.37384408279195 ], [ -71.10034391283989, 42.37385299961788 ] ] ]}')); diff --git a/test/spec/fixtures/privileges.sql b/test/spec/fixtures/privileges.sql index 152b1ea04..d1230aaaf 100644 --- a/test/spec/fixtures/privileges.sql +++ b/test/spec/fixtures/privileges.sql @@ -188,6 +188,8 @@ GRANT ALL ON TABLE , view_test , bulk_update_items , bulk_update_items_cpk + , shops + , shop_bles TO postgrest_test_anonymous; GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous; diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index e3ef0e22e..6f022259a 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -2649,3 +2649,23 @@ CREATE TABLE test.bulk_update_items_cpk ( observation TEXT, PRIMARY KEY (id, name) ); + +create extension if not exists postgis with schema extensions; + +create table shops ( + id int primary key +, address text +, shop_geom extensions.geometry(POINT, 4326) +); + +create table shop_bles ( + id int primary key +, name text +, coords extensions.geometry(POINT, 4326) +, range_area extensions.geometry(POLYGON, 4326) +, shop_id int references shops(id) +); + +create function get_shop(id int) returns shops as $$ + select * from shops where id = $1; +$$ language sql;