From 30cf1d100a8b91f199be5ffb195811ce8bc5628d Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Fri, 4 May 2018 14:38:05 -0500 Subject: [PATCH] Add foreign tables to OpenAPI output --- CHANGELOG.md | 1 + src/PostgREST/DbStructure.hs | 4 ++-- test/Feature/QuerySpec.hs | 18 ++++++++++++++---- test/Feature/StructureSpec.hs | 32 ++++++++++++++++++++++++++++++++ test/fixtures/data.sql | 3 +++ test/fixtures/privileges.sql | 1 + test/fixtures/schema.sql | 16 ++++++++++++++++ 7 files changed, 69 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 665780e1a..b4d76a876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #1078, Add ability to specify source column in embed - @steve-chavez - #821, Allow embeds alias to be used in filters - @steve-chavez - #906, Add jspath configurable `role-claim-key` - @steve-chavez +- #1061, Add foreign tables to OpenAPI output - @rhyamada ### Fixed diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 75948880c..dbff35b61 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -219,7 +219,7 @@ accessibleTables = join pg_namespace n on n.oid = c.relnamespace left join pg_catalog.pg_description as d on d.objoid = c.oid and d.objsubid = 0 where - c.relkind in ('v', 'r', 'm') + c.relkind in ('v', 'r', 'm', 'f') and n.nspname = $1 and ( pg_has_role(c.relowner, 'USAGE'::text) @@ -346,7 +346,7 @@ allTables = AND (pg_trigger.tgtype::integer & 69) = 69) ) AS insertable FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind IN ('v','r','m') + WHERE c.relkind IN ('v','r','m','f') AND n.nspname NOT IN ('pg_catalog', 'information_schema') GROUP BY table_schema, table_name, insertable ORDER BY table_schema, table_name |] diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 2eb8833ea..2efb54586 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -403,7 +403,7 @@ spec = do it "can detect fk relations through views to tables in the public schema" $ get "/consumers_view?select=*,orders_view(*)" `shouldRespondWith` 200 - context "path fixed" $ do + describe "path fixed" $ do it "works when requesting children 2 levels" $ get "/clients?id=eq.1&select=id,projects:projects.client_id(id,tasks(id))" `shouldRespondWith` [json|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|] @@ -428,7 +428,7 @@ spec = do [json|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|] { matchHeaders = [matchContentTypeJson] } - context "aliased embeds" $ do + describe "aliased embeds" $ do it "works with child relation" $ get "/space?select=id,zones:zone(id,name),stores:zone(id,name)&zones.zone_type_id=eq.2&stores.zone_type_id=eq.3" `shouldRespondWith` [json|[ @@ -485,7 +485,7 @@ spec = do { "id":4,"childs":[]} ]|] { matchHeaders = [matchContentTypeJson] } - context "tables with self reference foreign keys" $ do + describe "tables with self reference foreign keys" $ do context "one self reference foreign key" $ do it "embeds parents recursively" $ get "/family_tree?id=in.(3,4)&select=id,parent(id,name,parent(*))" `shouldRespondWith` @@ -911,7 +911,7 @@ spec = do it "only returns an empty result set if the in value is empty" $ get "/items_with_different_col_types?int_data=in.( ,3,4)" `shouldRespondWith` 400 - context "Embedding when column name = table name" $ do + describe "Embedding when column name = table name" $ do it "works with child embeds" $ get "/being?select=*,descendant(*)&limit=1" `shouldRespondWith` [json|[{"being":1,"descendant":[{"descendant":1,"being":1},{"descendant":2,"being":1},{"descendant":3,"being":1}]}]|] @@ -920,3 +920,13 @@ spec = do get "/being?select=*,part(*)&limit=1" `shouldRespondWith` [json|[{"being":1,"part":[{"part":1}]}]|] { matchHeaders = [matchContentTypeJson] } + + describe "Foreign table" $ do + it "can be queried by using regular filters" $ + get "/projects_dump?id=in.(1,2,3)" `shouldRespondWith` + [json| [{"id":1,"name":"Windows 7","client_id":1}, {"id":2,"name":"Windows 10","client_id":1}, {"id":3,"name":"IOS","client_id":2}]|] + { matchHeaders = [matchContentTypeJson] } + it "can be queried with select, order and limit" $ + get "/projects_dump?select=id,name&order=id.desc&limit=3" `shouldRespondWith` + [json| [{"id":5,"name":"Orphan"}, {"id":4,"name":"OSX"}, {"id":3,"name":"IOS"}] |] + { matchHeaders = [matchContentTypeJson] } diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs index 89081e4d3..45e4077f5 100644 --- a/test/Feature/StructureSpec.hs +++ b/test/Feature/StructureSpec.hs @@ -131,6 +131,38 @@ spec = do . nth 0 liftIO $ tableTag `shouldBe` Just [aesonQQ|"authors_only"|] + describe "Foreign table" $ + + it "includes foreign table properties" $ do + r <- simpleBody <$> get "/" + + let method s = key "paths" . key "/projects_dump" . key s + getSummary = r ^? method "get" . key "summary" + getDescription = r ^? method "get" . key "description" + getParameters = r ^? method "get" . key "parameters" + + liftIO $ do + + getSummary `shouldBe` Just "A temporary projects dump" + + getDescription `shouldBe` Just "Just a test for foreign tables" + + getParameters `shouldBe` Just + [aesonQQ| + [ + { "$ref": "#/parameters/rowFilter.projects_dump.id" }, + { "$ref": "#/parameters/rowFilter.projects_dump.name" }, + { "$ref": "#/parameters/rowFilter.projects_dump.client_id" }, + { "$ref": "#/parameters/select" }, + { "$ref": "#/parameters/order" }, + { "$ref": "#/parameters/range" }, + { "$ref": "#/parameters/rangeUnit" }, + { "$ref": "#/parameters/offset" }, + { "$ref": "#/parameters/limit" }, + { "$ref": "#/parameters/preferCount" } + ] + |] + describe "RPC" $ do it "includes body schema for arguments" $ do diff --git a/test/fixtures/data.sql b/test/fixtures/data.sql index 02b1522fe..d421b4d0a 100644 --- a/test/fixtures/data.sql +++ b/test/fixtures/data.sql @@ -408,3 +408,6 @@ INSERT INTO zone VALUES (1, 'zone 1', 2, 1); INSERT INTO zone VALUES (2, 'zone 2', 2, 1); INSERT INTO zone VALUES (3, 'store 3', 3, 1); INSERT INTO zone VALUES (4, 'store 4', 3, 1); + +-- for foreign table projects_dump +copy (select id, name, client_id from projects) to '/tmp/projects_dump.csv' with csv; diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index 8550b3d01..b94dbb746 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -78,6 +78,7 @@ GRANT ALL ON TABLE , person_detail , space , zone + , projects_dump TO postgrest_test_anonymous; GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous; diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index ffce6249f..8ef333926 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -1435,3 +1435,19 @@ create table zone( name text, zone_type_id integer, space_id integer references space(id)); + +-- foreign table tests +create extension file_fdw; + +create server import_csv foreign data wrapper file_fdw; + +create foreign table projects_dump ( + id integer, + name text, + client_id integer +) server import_csv options ( filename '/tmp/projects_dump.csv', format 'csv'); + +comment on foreign table projects_dump is +$$A temporary projects dump + +Just a test for foreign tables$$;