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:
laurenceisla
2021-08-13 15:06:56 -05:00
committed by GitHub
parent b3899e7aa4
commit 4e4548d702
12 changed files with 351 additions and 49 deletions
+2
View File
@@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added ### Added
- #1783, Include partitioned tables into the schema cache. Allows embedding, UPSERT, INSERT with Location response, OPTIONS request and OpenAPI support for partitioned tables - @laurenceisla
### Fixed ### Fixed
## [8.0.0] - 2021-07-25 ## [8.0.0] - 2021-07-25
+44 -35
View File
@@ -323,38 +323,47 @@ accessibleTables =
relname as table_name, relname as table_name,
d.description as table_description, d.description as table_description,
( (
c.relkind IN ('r', 'v','f') c.relkind IN ('r','p')
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8 OR (
OR EXISTS ( c.relkind IN ('v','f')
SELECT 1 AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8
FROM pg_trigger OR EXISTS (
WHERE SELECT 1
pg_trigger.tgrelid = c.oid FROM pg_trigger
AND (pg_trigger.tgtype::integer & 69) = 69 WHERE
pg_trigger.tgrelid = c.oid
AND (pg_trigger.tgtype::integer & 69) = 69
)
) )
) AS insertable, ) AS insertable,
( (
c.relkind IN ('r', 'v','f') c.relkind IN ('r','p')
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 4) = 4 OR (
-- CMD_UPDATE c.relkind IN ('v','f')
OR EXISTS ( AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 4) = 4
SELECT 1 -- CMD_UPDATE
FROM pg_trigger OR EXISTS (
WHERE SELECT 1
pg_trigger.tgrelid = c.oid FROM pg_trigger
and (pg_trigger.tgtype::integer & 81) = 81 WHERE
pg_trigger.tgrelid = c.oid
and (pg_trigger.tgtype::integer & 81) = 81
)
) )
) as updatable, ) as updatable,
( (
c.relkind IN ('r', 'v','f') c.relkind IN ('r','p')
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 16) = 16 OR (
-- CMD_DELETE c.relkind IN ('v','f')
OR EXISTS ( AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 16) = 16
SELECT 1 -- CMD_DELETE
FROM pg_trigger OR EXISTS (
WHERE SELECT 1
pg_trigger.tgrelid = c.oid FROM pg_trigger
and (pg_trigger.tgtype::integer & 73) = 73 WHERE
pg_trigger.tgrelid = c.oid
and (pg_trigger.tgtype::integer & 73) = 73
)
) )
) as deletable ) as deletable
from from
@@ -362,7 +371,7 @@ accessibleTables =
join pg_namespace n on n.oid = c.relnamespace 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 left join pg_catalog.pg_description as d on d.objoid = c.oid and d.objsubid = 0
where where
c.relkind in ('v', 'r', 'm', 'f') c.relkind in ('v','r','m','f','p')
and n.nspname = $1 and n.nspname = $1
and ( and (
pg_has_role(c.relowner, 'USAGE') pg_has_role(c.relowner, 'USAGE')
@@ -468,7 +477,7 @@ allTables =
c.relname AS table_name, c.relname AS table_name,
d.description AS table_description, d.description AS table_description,
( (
c.relkind = 'r' c.relkind IN ('r','p')
OR ( OR (
c.relkind in ('v','f') c.relkind in ('v','f')
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8 AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8
@@ -488,7 +497,7 @@ allTables =
) )
) AS insertable, ) AS insertable,
( (
c.relkind = 'r' c.relkind IN ('r','p')
OR ( OR (
c.relkind in ('v','f') c.relkind in ('v','f')
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 4) = 4 AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 4) = 4
@@ -504,7 +513,7 @@ allTables =
) )
) AS updatable, ) AS updatable,
( (
c.relkind = 'r' c.relkind IN ('r','p')
OR ( OR (
c.relkind in ('v','f') c.relkind in ('v','f')
AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 16) = 16 AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 16) = 16
@@ -522,7 +531,7 @@ allTables =
FROM pg_class c FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace 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 LEFT JOIN pg_catalog.pg_description as d on d.objoid = c.oid and d.objsubid = 0
WHERE c.relkind IN ('v','r','m','f') WHERE c.relkind IN ('v','r','m','f','p')
AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY table_schema, table_name |] ORDER BY table_schema, table_name |]
@@ -559,7 +568,7 @@ allColumns tabs =
pg_catalog.pg_namespace n pg_catalog.pg_namespace n
WHERE WHERE
r.contype IN ('f', 'p', 'u') r.contype IN ('f', 'p', 'u')
AND c.relkind IN ('r', 'v', 'f', 'm') AND c.relkind IN ('r', 'v', 'f', 'm', 'p')
AND r.conrelid = c.oid AND r.conrelid = c.oid
AND c.relnamespace = n.oid AND c.relnamespace = n.oid
AND n.nspname <> ANY (ARRAY['pg_catalog', 'information_schema'] || $1) AND n.nspname <> ANY (ARRAY['pg_catalog', 'information_schema'] || $1)
@@ -617,7 +626,7 @@ allColumns tabs =
NOT pg_is_other_temp_schema(nc.oid) NOT pg_is_other_temp_schema(nc.oid)
AND a.attnum > 0 AND a.attnum > 0
AND NOT a.attisdropped AND NOT a.attisdropped
AND c.relkind in ('r', 'v', 'f', 'm') AND c.relkind in ('r', 'v', 'f', 'm', 'p')
-- Filter only columns that are FK/PK or in the api schema: -- Filter only columns that are FK/PK or in the api schema:
AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL) AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL)
) )
@@ -716,7 +725,7 @@ allPrimaryKeys tabs =
nc.oid = c.connamespace nc.oid = c.connamespace
AND nr.oid = r.relnamespace AND nr.oid = r.relnamespace
AND c.conrelid = r.oid AND c.conrelid = r.oid
AND r.relkind = 'r' AND r.relkind IN ('r', 'p')
AND NOT pg_is_other_temp_schema(nr.oid) AND NOT pg_is_other_temp_schema(nr.oid)
AND c.contype = 'p' AND c.contype = 'p'
), ),
@@ -753,7 +762,7 @@ allPrimaryKeys tabs =
AND r.oid = c.conrelid AND r.oid = c.conrelid
AND nc.oid = c.connamespace AND nc.oid = c.connamespace
AND c.contype in ('p', 'u', 'f') AND c.contype in ('p', 'u', 'f')
AND r.relkind = 'r' AND r.relkind IN ('r', 'p')
AND NOT pg_is_other_temp_schema(nr.oid) AND NOT pg_is_other_temp_schema(nr.oid)
) ss ) ss
WHERE WHERE
+12 -3
View File
@@ -11,8 +11,8 @@ import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc import Text.Heredoc
import PostgREST.Config.PgVersion (PgVersion, pgVersion112, import PostgREST.Config.PgVersion (PgVersion, pgVersion110,
pgVersion130) pgVersion112, pgVersion130)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
@@ -111,7 +111,7 @@ spec actualPgVersion = do
, "Content-Range" <:> "*/*" ] , "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" $ it "should not throw and return location header when selecting without PK" $
request methodPost "/projects?select=name,client_id" [("Prefer", "return=headers-only")] request methodPost "/projects?select=name,client_id" [("Prefer", "return=headers-only")]
[json|{"id":11,"name":"New Project","client_id":2}|] `shouldRespondWith` "" [json|{"id":11,"name":"New Project","client_id":2}|] `shouldRespondWith` ""
@@ -120,6 +120,15 @@ spec actualPgVersion = do
, "Content-Range" <:> "*/*" ] , "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" $ context "requesting no representation" $
it "should not throw and return no location header when selecting without PK" $ it "should not throw and return no location header when selecting without PK" $
request methodPost "/projects?select=name,client_id" [] request methodPost "/projects?select=name,client_id" []
+31 -2
View File
@@ -11,12 +11,15 @@ import Network.HTTP.Types
import Test.Hspec hiding (pendingWith) import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai import Test.Hspec.Wai
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
pgVersion110)
import PostgREST.Version (docsVersion) import PostgREST.Version (docsVersion)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
spec :: SpecWith ((), Application) spec :: PgVersion -> SpecWith ((), Application)
spec = describe "OpenAPI" $ do spec actualPgVersion = describe "OpenAPI" $ do
it "root path returns a valid openapi spec" $ do it "root path returns a valid openapi spec" $ do
validateOpenApiResponse [("Accept", "application/openapi+json")] validateOpenApiResponse [("Accept", "application/openapi+json")]
request methodHead "/" (acceptHdrs "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" $ describe "Materialized view" $
it "includes materialized view properties" $ do it "includes materialized view properties" $ do
+18 -2
View File
@@ -7,11 +7,14 @@ import Network.HTTP.Types
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
pgVersion110)
import Protolude import Protolude
import SpecHelper import SpecHelper
spec :: SpecWith ((), Application) spec :: PgVersion -> SpecWith ((), Application)
spec = describe "Allow header" $ do spec actualPgVersion = describe "Allow header" $ do
context "a table" $ do context "a table" $ do
it "includes read/write verbs for writeable table" $ do it "includes read/write verbs for writeable table" $ do
r <- request methodOptions "/items" [] "" r <- request methodOptions "/items" [] ""
@@ -19,6 +22,19 @@ spec = describe "Allow header" $ do
simpleHeaders r `shouldSatisfy` simpleHeaders r `shouldSatisfy`
matchHeader "Allow" "OPTIONS,GET,HEAD,POST,PUT,PATCH,DELETE" 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 "a view" $ do
context "auto updatable" $ do context "auto updatable" $ do
it "includes read/write verbs for auto updatable views with pk" $ do it "includes read/write verbs for auto updatable views with pk" $ do
+79 -2
View File
@@ -8,8 +8,9 @@ import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import PostgREST.Config.PgVersion (PgVersion, pgVersion112, import PostgREST.Config.PgVersion (PgVersion, pgVersion110,
pgVersion121, pgVersion96) pgVersion112, pgVersion121,
pgVersion96)
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
@@ -395,6 +396,82 @@ spec actualPgVersion = do
[json|[{"id":1,"computed_overload":true}]|] [json|[{"id":1,"computed_overload":true}]|]
{ matchHeaders = [matchContentTypeJson] } { 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 describe "view embedding" $ do
it "can detect fk relations through views to tables in the public schema" $ it "can detect fk relations through views to tables in the public schema" $
get "/consumers_view?select=*,orders_view(*)" `shouldRespondWith` 200 get "/consumers_view?select=*,orders_view(*)" `shouldRespondWith` 200
+57 -2
View File
@@ -7,11 +7,13 @@ import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import PostgREST.Config.PgVersion (PgVersion, pgVersion110)
import Protolude hiding (get, put) import Protolude hiding (get, put)
import SpecHelper import SpecHelper
spec :: SpecWith ((), Application) spec :: PgVersion -> SpecWith ((), Application)
spec = spec actualPgVersion =
describe "UPSERT" $ do describe "UPSERT" $ do
context "with POST" $ do context "with POST" $ do
context "when Prefer: resolution=merge-duplicates is specified" $ do context "when Prefer: resolution=merge-duplicates is specified" $ do
@@ -43,6 +45,20 @@ spec =
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates", matchContentTypeJson] , 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" $ it "succeeds when the payload has no elements" $
request methodPost "/articles" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")] request methodPost "/articles" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")]
[json|[]|] `shouldRespondWith` [json|[]|] `shouldRespondWith`
@@ -99,6 +115,19 @@ spec =
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates", matchContentTypeJson] , 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" $ it "INSERTs and ignores rows on single unique key conflict" $
request methodPost "/single_unique?on_conflict=unique_key" request methodPost "/single_unique?on_conflict=unique_key"
[("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")]
@@ -264,6 +293,19 @@ spec =
`shouldRespondWith` `shouldRespondWith`
[json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$48,000.00", "company": "GEX", "occupation": "Railroad engineer" } ]|] [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 it "succeeds if the table has only PK cols and no other cols" $ do
-- assert that the next request will indeed be an insert -- assert that the next request will indeed be an insert
get "/only_pk?id=eq.10" get "/only_pk?id=eq.10"
@@ -315,6 +357,19 @@ spec =
`shouldRespondWith` `shouldRespondWith`
[json| [ { "first_name": "Frances M.", "last_name": "Roe", "salary": "$60,000.00", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|] [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 it "succeeds if the table has only PK cols and no other cols" $ do
-- assert that the next request will indeed be an update -- assert that the next request will indeed be an update
get "/only_pk?id=eq.1" get "/only_pk?id=eq.1"
+3 -3
View File
@@ -127,14 +127,14 @@ main = do
, ("Feature.EmbedDisambiguationSpec" , Feature.EmbedDisambiguationSpec.spec) , ("Feature.EmbedDisambiguationSpec" , Feature.EmbedDisambiguationSpec.spec)
, ("Feature.InsertSpec" , Feature.InsertSpec.spec actualPgVersion) , ("Feature.InsertSpec" , Feature.InsertSpec.spec actualPgVersion)
, ("Feature.JsonOperatorSpec" , Feature.JsonOperatorSpec.spec actualPgVersion) , ("Feature.JsonOperatorSpec" , Feature.JsonOperatorSpec.spec actualPgVersion)
, ("Feature.OpenApiSpec" , Feature.OpenApiSpec.spec) , ("Feature.OpenApiSpec" , Feature.OpenApiSpec.spec actualPgVersion)
, ("Feature.OptionsSpec" , Feature.OptionsSpec.spec) , ("Feature.OptionsSpec" , Feature.OptionsSpec.spec actualPgVersion)
, ("Feature.QuerySpec" , Feature.QuerySpec.spec actualPgVersion) , ("Feature.QuerySpec" , Feature.QuerySpec.spec actualPgVersion)
, ("Feature.RawOutputTypesSpec" , Feature.RawOutputTypesSpec.spec) , ("Feature.RawOutputTypesSpec" , Feature.RawOutputTypesSpec.spec)
, ("Feature.RpcSpec" , Feature.RpcSpec.spec actualPgVersion) , ("Feature.RpcSpec" , Feature.RpcSpec.spec actualPgVersion)
, ("Feature.SingularSpec" , Feature.SingularSpec.spec) , ("Feature.SingularSpec" , Feature.SingularSpec.spec)
, ("Feature.UpdateSpec" , Feature.UpdateSpec.spec) , ("Feature.UpdateSpec" , Feature.UpdateSpec.spec)
, ("Feature.UpsertSpec" , Feature.UpsertSpec.spec) , ("Feature.UpsertSpec" , Feature.UpsertSpec.spec actualPgVersion)
] ]
hspec $ do hspec $ do
+25
View File
@@ -663,3 +663,28 @@ INSERT INTO private.films (id, title) VALUES (12,'douze commandements'), (2001,'
TRUNCATE TABLE private.personnages CASCADE; TRUNCATE TABLE private.personnages CASCADE;
INSERT INTO private.personnages (film_id, role_id, character) VALUES (12,1,'méchant'), (2001,2,'astronaute'); INSERT INTO private.personnages (film_id, role_id, character) VALUES (12,1,'méchant'), (2001,2,'astronaute');
DO $do$BEGIN
IF (SELECT current_setting('server_version_num')::INT >= 100000) THEN
INSERT INTO test.partitioned_a(id, name) VALUES (1,'first');
INSERT INTO test.partitioned_a(id, name) VALUES (2,'first');
INSERT INTO test.partitioned_a(id, name) VALUES (3,'second');
INSERT INTO test.partitioned_a(id, name) VALUES (4,'second');
END IF;
IF (SELECT current_setting('server_version_num')::INT >= 110000) THEN
INSERT INTO test.reference_from_partitioned(id) VALUES (1),(2);
UPDATE test.partitioned_a SET id_ref = 1 WHERE id = 1;
END IF;
IF (SELECT current_setting('server_version_num')::INT >= 120000) THEN
INSERT INTO test.partitioned_b(id, name) VALUES (1,'first_b');
INSERT INTO test.partitioned_b(id, name, id_a, name_a) VALUES (2,'first_b', 2, 'first');
INSERT INTO test.partitioned_b(id, name) VALUES (3,'second_b');
INSERT INTO test.partitioned_b(id, name, id_a, name_a) VALUES (4,'second_b', 4, 'second');
INSERT INTO test.reference_to_partitioned(id) VALUES (1);
INSERT INTO test.reference_to_partitioned(id, id_a, name_a) VALUES (2, 2, 'first');
END IF;
END$do$;
+20
View File
@@ -179,3 +179,23 @@ REVOKE EXECUTE ON FUNCTION privileged_hello(text) FROM PUBLIC; -- All functions
GRANT EXECUTE ON FUNCTION privileged_hello(text) TO postgrest_test_author; GRANT EXECUTE ON FUNCTION privileged_hello(text) TO postgrest_test_author;
GRANT USAGE ON SCHEMA test TO postgrest_test_default_role; GRANT USAGE ON SCHEMA test TO postgrest_test_default_role;
DO $do$BEGIN
IF (SELECT current_setting('server_version_num')::INT >= 100000) THEN
GRANT ALL ON TABLE test.partitioned_a TO postgrest_test_anonymous;
GRANT ALL ON TABLE test.first_partition_a TO postgrest_test_anonymous;
GRANT ALL ON TABLE test.second_partition_a TO postgrest_test_anonymous;
END IF;
IF (SELECT current_setting('server_version_num')::INT >= 110000) THEN
GRANT ALL ON TABLE test.reference_from_partitioned TO postgrest_test_anonymous;
END IF;
IF (SELECT current_setting('server_version_num')::INT >= 120000) THEN
GRANT ALL ON TABLE test.partitioned_b TO postgrest_test_anonymous;
GRANT ALL ON TABLE test.first_partition_b TO postgrest_test_anonymous;
GRANT ALL ON TABLE test.second_partition_b TO postgrest_test_anonymous;
GRANT ALL ON TABLE test.reference_to_partitioned TO postgrest_test_anonymous;
END IF;
END$do$;
+58
View File
@@ -2185,3 +2185,61 @@ create table private.rollen (
foreign key (film_id) references test.filme(id), foreign key (film_id) references test.filme(id),
foreign key (rolle_id) references test.schauspieler(id) foreign key (rolle_id) references test.schauspieler(id)
); );
-- Tables used for testing embedding between partitioned tables
do $do$begin
-- partitioned tables using the PARTITION syntax are supported from pg v10
if (select current_setting('server_version_num')::int >= 100000) then
create table test.partitioned_a(
id int not null,
name varchar(64) not null
) partition by list (name);
comment on table test.partitioned_a is
$$A partitioned table
A test for partitioned tables$$;
create table test.first_partition_a partition of test.partitioned_a
for values in ('first');
create table test.second_partition_a partition of test.partitioned_a
for values in ('second');
end if;
-- primary keys for partitioned tables are supported from pg v11
if (select current_setting('server_version_num')::int >= 110000) then
create table test.reference_from_partitioned (
id int primary key
);
alter table test.partitioned_a add primary key (id, name);
alter table test.partitioned_a add column id_ref int references test.reference_from_partitioned(id);
end if;
-- foreign keys referencing partitioned tables are supported from pg v12
if (select current_setting('server_version_num')::int >= 120000) then
create table test.partitioned_b(
id int not null,
name varchar(64) not null,
id_a int,
name_a varchar(64),
primary key (id, name),
foreign key (id_a, name_a) references test.partitioned_a (id, name)
) partition by list (name);
create table test.first_partition_b partition of test.partitioned_b
for values in ('first_b');
create table test.second_partition_b partition of test.partitioned_b
for values in ('second_b');
create table test.reference_to_partitioned (
id int not null primary key,
id_a int,
name_a varchar(64),
foreign key (id_a, name_a) references test.partitioned_a (id, name)
);
end if;
end$do$;
+2
View File
@@ -709,6 +709,7 @@ def test_invalid_role_claim_key_notify_reload(defaultenv):
postgrest.session.post("/rpc/reset_invalid_role_claim_key") postgrest.session.post("/rpc/reset_invalid_role_claim_key")
def test_db_prepared_statements_enable(defaultenv): def test_db_prepared_statements_enable(defaultenv):
"Should use prepared statements when the setting is enabled." "Should use prepared statements when the setting is enabled."
@@ -716,6 +717,7 @@ def test_db_prepared_statements_enable(defaultenv):
response = postgrest.session.post("/rpc/uses_prepared_statements") response = postgrest.session.post("/rpc/uses_prepared_statements")
assert response.text == "true" assert response.text == "true"
def test_db_prepared_statements_disable(defaultenv): def test_db_prepared_statements_disable(defaultenv):
"Should not use any prepared statements when the setting is disabled." "Should not use any prepared statements when the setting is disabled."