feat: Include partitioned tables into the schema cache
Allows embedding, UPSERT, INSERT with Location response, OPTIONS request and OpenAPI support for partitioned tables
This commit is contained in:
@@ -11,8 +11,8 @@ import Test.Hspec.Wai
|
||||
import Test.Hspec.Wai.JSON
|
||||
import Text.Heredoc
|
||||
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion112,
|
||||
pgVersion130)
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion110,
|
||||
pgVersion112, pgVersion130)
|
||||
|
||||
import Protolude hiding (get)
|
||||
import SpecHelper
|
||||
@@ -111,7 +111,7 @@ spec actualPgVersion = do
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
context "requesting headers only representation" $
|
||||
context "requesting headers only representation" $ do
|
||||
it "should not throw and return location header when selecting without PK" $
|
||||
request methodPost "/projects?select=name,client_id" [("Prefer", "return=headers-only")]
|
||||
[json|{"id":11,"name":"New Project","client_id":2}|] `shouldRespondWith` ""
|
||||
@@ -120,6 +120,15 @@ spec actualPgVersion = do
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
when (actualPgVersion >= pgVersion110) $
|
||||
it "should not throw and return location header for partitioned tables when selecting without PK" $
|
||||
request methodPost "/partitioned_a" [("Prefer", "return=headers-only")]
|
||||
[json|{"id":5,"name":"first"}|] `shouldRespondWith` ""
|
||||
{ matchStatus = 201
|
||||
, matchHeaders = [ "Location" <:> "/partitioned_a?id=eq.5&name=eq.first"
|
||||
, "Content-Range" <:> "*/*" ]
|
||||
}
|
||||
|
||||
context "requesting no representation" $
|
||||
it "should not throw and return no location header when selecting without PK" $
|
||||
request methodPost "/projects?select=name,client_id" []
|
||||
|
||||
@@ -11,12 +11,15 @@ import Network.HTTP.Types
|
||||
import Test.Hspec hiding (pendingWith)
|
||||
import Test.Hspec.Wai
|
||||
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
||||
pgVersion110)
|
||||
|
||||
import PostgREST.Version (docsVersion)
|
||||
import Protolude hiding (get)
|
||||
import SpecHelper
|
||||
|
||||
spec :: SpecWith ((), Application)
|
||||
spec = describe "OpenAPI" $ do
|
||||
spec :: PgVersion -> SpecWith ((), Application)
|
||||
spec actualPgVersion = describe "OpenAPI" $ do
|
||||
it "root path returns a valid openapi spec" $ do
|
||||
validateOpenApiResponse [("Accept", "application/openapi+json")]
|
||||
request methodHead "/" (acceptHdrs "application/openapi+json") ""
|
||||
@@ -195,6 +198,32 @@ spec = describe "OpenAPI" $ do
|
||||
]
|
||||
|]
|
||||
|
||||
when (actualPgVersion >= pgVersion100) $ do
|
||||
describe "Partitioned table" $
|
||||
|
||||
it "includes partitioned table properties" $ do
|
||||
r <- simpleBody <$> get "/"
|
||||
|
||||
let method s = key "paths" . key "/partitioned_a" . key s
|
||||
getSummary = r ^? method "get" . key "summary"
|
||||
getDescription = r ^? method "get" . key "description"
|
||||
getParameterId = r ^? method "get" . key "parameters" . nth 0 . key "$ref"
|
||||
getParameterName = r ^? method "get" . key "parameters" . nth 1 . key "$ref"
|
||||
getParameterRef = r ^? method "get" . key "parameters" . nth 2 . key "$ref"
|
||||
|
||||
liftIO $ do
|
||||
|
||||
getSummary `shouldBe` Just "A partitioned table"
|
||||
|
||||
getDescription `shouldBe` Just "A test for partitioned tables"
|
||||
|
||||
getParameterId `shouldBe` Just "#/parameters/rowFilter.partitioned_a.id"
|
||||
|
||||
getParameterName `shouldBe` Just "#/parameters/rowFilter.partitioned_a.name"
|
||||
|
||||
when (actualPgVersion >= pgVersion110) $
|
||||
getParameterRef `shouldBe` Just "#/parameters/rowFilter.partitioned_a.id_ref"
|
||||
|
||||
describe "Materialized view" $
|
||||
|
||||
it "includes materialized view properties" $ do
|
||||
|
||||
@@ -7,11 +7,14 @@ import Network.HTTP.Types
|
||||
import Test.Hspec
|
||||
import Test.Hspec.Wai
|
||||
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
||||
pgVersion110)
|
||||
|
||||
import Protolude
|
||||
import SpecHelper
|
||||
|
||||
spec :: SpecWith ((), Application)
|
||||
spec = describe "Allow header" $ do
|
||||
spec :: PgVersion -> SpecWith ((), Application)
|
||||
spec actualPgVersion = describe "Allow header" $ do
|
||||
context "a table" $ do
|
||||
it "includes read/write verbs for writeable table" $ do
|
||||
r <- request methodOptions "/items" [] ""
|
||||
@@ -19,6 +22,19 @@ spec = describe "Allow header" $ do
|
||||
simpleHeaders r `shouldSatisfy`
|
||||
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
|
||||
|
||||
when (actualPgVersion >= pgVersion100) $
|
||||
context "a partitioned table" $ do
|
||||
it "includes read/write verbs for writeable partitioned tables" $ do
|
||||
r <- request methodOptions "/partitioned_a" [] ""
|
||||
liftIO $
|
||||
simpleHeaders r `shouldSatisfy`
|
||||
matchHeader "Allow" (
|
||||
if actualPgVersion >= pgVersion110 then
|
||||
"OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE"
|
||||
else
|
||||
"OPTIONS,GET,HEAD,POST,PATCH,DELETE"
|
||||
)
|
||||
|
||||
context "a view" $ do
|
||||
context "auto updatable" $ do
|
||||
it "includes read/write verbs for auto updatable views with pk" $ do
|
||||
|
||||
@@ -8,8 +8,9 @@ import Test.Hspec hiding (pendingWith)
|
||||
import Test.Hspec.Wai
|
||||
import Test.Hspec.Wai.JSON
|
||||
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion112,
|
||||
pgVersion121, pgVersion96)
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion110,
|
||||
pgVersion112, pgVersion121,
|
||||
pgVersion96)
|
||||
import Protolude hiding (get)
|
||||
import SpecHelper
|
||||
|
||||
@@ -395,6 +396,82 @@ spec actualPgVersion = do
|
||||
[json|[{"id":1,"computed_overload":true}]|]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
when (actualPgVersion >= pgVersion110) $ do
|
||||
describe "partitioned tables embedding" $ do
|
||||
it "can request a table as parent from a partitioned table" $
|
||||
get "/partitioned_a?id=in.(1,2)&select=id,name,reference_from_partitioned(id)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"name":"first","reference_from_partitioned":{"id":1}},
|
||||
{"id":2,"name":"first","reference_from_partitioned":null}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can request partitioned tables as children from a table" $
|
||||
get "/reference_from_partitioned?select=id,partitioned_a(id,name)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"partitioned_a":[{"id":1,"name":"first"}]},
|
||||
{"id":2,"partitioned_a":[]}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
when (actualPgVersion >= pgVersion121) $ do
|
||||
it "can request tables as children from a partitioned table" $
|
||||
get "/partitioned_a?id=in.(1,2)&select=id,name,reference_to_partitioned(id)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"name":"first","reference_to_partitioned":[]},
|
||||
{"id":2,"name":"first","reference_to_partitioned":[{"id":2}]}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can request a partitioned table as parent from a table" $
|
||||
get "/reference_to_partitioned?select=id,partitioned_a(id,name)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"partitioned_a":null},
|
||||
{"id":2,"partitioned_a":{"id":2,"name":"first"}}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can request partitioned tables as children from a partitioned table" $
|
||||
get "/partitioned_a?id=in.(1,2,4)&select=id,name,partitioned_b(id,name)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"name":"first","partitioned_b":[]},
|
||||
{"id":2,"name":"first","partitioned_b":[{"id":2,"name":"first_b"}]},
|
||||
{"id":4,"name":"second","partitioned_b":[{"id":4,"name":"second_b"}]}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can request a partitioned table as parent from a partitioned table" $ do
|
||||
get "/partitioned_b?id=in.(2,4)&select=id,name,partitioned_a(id,name)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":2,"name":"first_b","partitioned_a":{"id":2,"name":"first"}},
|
||||
{"id":4,"name":"second_b","partitioned_a":{"id":4,"name":"second"}}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can request partitions as children from a partitioned table" $
|
||||
get "/partitioned_a?id=in.(1,2,4)&select=id,name,first_partition_b(id)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"name":"first","first_partition_b":[]},
|
||||
{"id":2,"name":"first","first_partition_b":[{"id":2}]},
|
||||
{"id":4,"name":"second","first_partition_b":[]}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can request a partitioned table as parent from a partition" $
|
||||
get "/first_partition_b?select=id,name,partitioned_a(id,name)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"name":"first_b","partitioned_a":null},
|
||||
{"id":2,"name":"first_b","partitioned_a":{"id":2,"name":"first"}}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can request a partition as parent from a partitioned table" $
|
||||
get "/partitioned_b?id=in.(1,3,4)&select=id,name,second_partition_a(id,name)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":1,"name":"first_b","second_partition_a":null},
|
||||
{"id":3,"name":"second_b","second_partition_a":null},
|
||||
{"id":4,"name":"second_b","second_partition_a":{"id":4,"name":"second"}}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
it "can request partitioned tables as children from a partition" $
|
||||
get "/second_partition_a?select=id,name,partitioned_b(id,name)&order=id.asc" `shouldRespondWith`
|
||||
[json|
|
||||
[{"id":3,"name":"second","partitioned_b":[]},
|
||||
{"id":4,"name":"second","partitioned_b":[{"id":4,"name":"second_b"}]}] |]
|
||||
{ matchHeaders = [matchContentTypeJson] }
|
||||
|
||||
describe "view embedding" $ do
|
||||
it "can detect fk relations through views to tables in the public schema" $
|
||||
get "/consumers_view?select=*,orders_view(*)" `shouldRespondWith` 200
|
||||
|
||||
@@ -7,11 +7,13 @@ import Test.Hspec
|
||||
import Test.Hspec.Wai
|
||||
import Test.Hspec.Wai.JSON
|
||||
|
||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion110)
|
||||
|
||||
import Protolude hiding (get, put)
|
||||
import SpecHelper
|
||||
|
||||
spec :: SpecWith ((), Application)
|
||||
spec =
|
||||
spec :: PgVersion -> SpecWith ((), Application)
|
||||
spec actualPgVersion =
|
||||
describe "UPSERT" $ do
|
||||
context "with POST" $ do
|
||||
context "when Prefer: resolution=merge-duplicates is specified" $ do
|
||||
@@ -43,6 +45,20 @@ spec =
|
||||
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates", matchContentTypeJson]
|
||||
}
|
||||
|
||||
when (actualPgVersion >= pgVersion110) $
|
||||
it "INSERTs and UPDATEs rows on composite pk conflict for partitioned tables" $
|
||||
request methodPost "/partitioned_a" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")]
|
||||
[json| [
|
||||
{ "id": 4, "name": "second", "id_ref": 2},
|
||||
{ "id": 6, "name": "first", "id_ref": 1 }
|
||||
]|] `shouldRespondWith` [json| [
|
||||
{ "id": 4, "name": "second", "id_ref": 2 },
|
||||
{ "id": 6, "name": "first", "id_ref": 1 }
|
||||
]|]
|
||||
{ matchStatus = 201
|
||||
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates", matchContentTypeJson]
|
||||
}
|
||||
|
||||
it "succeeds when the payload has no elements" $
|
||||
request methodPost "/articles" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")]
|
||||
[json|[]|] `shouldRespondWith`
|
||||
@@ -99,6 +115,19 @@ spec =
|
||||
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates", matchContentTypeJson]
|
||||
}
|
||||
|
||||
when (actualPgVersion >= pgVersion110) $
|
||||
it "INSERTs and ignores rows on composite pk conflict for partitioned tables" $
|
||||
request methodPost "/partitioned_a" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")]
|
||||
[json| [
|
||||
{ "id": 4, "name": "second", "id_ref": 1 },
|
||||
{ "id": 7, "name": "second", "id_ref": 2 }
|
||||
]|] `shouldRespondWith` [json| [
|
||||
{ "id": 7, "name": "second", "id_ref": 2 }
|
||||
]|]
|
||||
{ matchStatus = 201
|
||||
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates", matchContentTypeJson]
|
||||
}
|
||||
|
||||
it "INSERTs and ignores rows on single unique key conflict" $
|
||||
request methodPost "/single_unique?on_conflict=unique_key"
|
||||
[("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")]
|
||||
@@ -264,6 +293,19 @@ spec =
|
||||
`shouldRespondWith`
|
||||
[json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$48,000.00", "company": "GEX", "occupation": "Railroad engineer" } ]|]
|
||||
|
||||
when (actualPgVersion >= pgVersion110) $
|
||||
it "succeeds on a partitioned table with composite pk" $ do
|
||||
-- assert that the next request will indeed be an insert
|
||||
get "/partitioned_a?id=eq.8&name=eq.first"
|
||||
`shouldRespondWith`
|
||||
[json|[]|]
|
||||
|
||||
request methodPut "/partitioned_a?id=eq.8&name=eq.first"
|
||||
[("Prefer", "return=representation")]
|
||||
[json| [ { "id": 8, "name": "first" } ]|]
|
||||
`shouldRespondWith`
|
||||
[json| [ { "id": 8, "name": "first", "id_ref": null } ]|]
|
||||
|
||||
it "succeeds if the table has only PK cols and no other cols" $ do
|
||||
-- assert that the next request will indeed be an insert
|
||||
get "/only_pk?id=eq.10"
|
||||
@@ -315,6 +357,19 @@ spec =
|
||||
`shouldRespondWith`
|
||||
[json| [ { "first_name": "Frances M.", "last_name": "Roe", "salary": "$60,000.00", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|]
|
||||
|
||||
when (actualPgVersion >= pgVersion110) $
|
||||
it "succeeds on a partitioned table with composite pk" $ do
|
||||
-- assert that the next request will indeed be an update
|
||||
get "/partitioned_a?id=eq.2&name=eq.first"
|
||||
`shouldRespondWith`
|
||||
[json| [ { "id": 2, "name": "first", "id_ref": null } ]|]
|
||||
|
||||
request methodPut "/partitioned_a?id=eq.2&name=eq.first"
|
||||
[("Prefer", "return=representation")]
|
||||
[json| [ { "id": 2, "name": "first", "id_ref": 1 } ]|]
|
||||
`shouldRespondWith`
|
||||
[json| [ { "id": 2, "name": "first", "id_ref": 1 } ]|]
|
||||
|
||||
it "succeeds if the table has only PK cols and no other cols" $ do
|
||||
-- assert that the next request will indeed be an update
|
||||
get "/only_pk?id=eq.1"
|
||||
|
||||
Reference in New Issue
Block a user