diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ec82aeb..e62fa65c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### 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 ## [8.0.0] - 2021-07-25 diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index b50dbb3ff..de7174f49 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -323,38 +323,47 @@ accessibleTables = relname as table_name, d.description as table_description, ( - c.relkind IN ('r', 'v','f') - AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8 - OR EXISTS ( - SELECT 1 - FROM pg_trigger - WHERE - pg_trigger.tgrelid = c.oid - AND (pg_trigger.tgtype::integer & 69) = 69 + c.relkind IN ('r','p') + OR ( + c.relkind IN ('v','f') + AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8 + OR EXISTS ( + SELECT 1 + FROM pg_trigger + WHERE + pg_trigger.tgrelid = c.oid + AND (pg_trigger.tgtype::integer & 69) = 69 + ) ) ) AS insertable, ( - c.relkind IN ('r', 'v','f') - AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 4) = 4 - -- CMD_UPDATE - OR EXISTS ( - SELECT 1 - FROM pg_trigger - WHERE - pg_trigger.tgrelid = c.oid - and (pg_trigger.tgtype::integer & 81) = 81 + c.relkind IN ('r','p') + OR ( + c.relkind IN ('v','f') + AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 4) = 4 + -- CMD_UPDATE + OR EXISTS ( + SELECT 1 + FROM pg_trigger + WHERE + pg_trigger.tgrelid = c.oid + and (pg_trigger.tgtype::integer & 81) = 81 + ) ) ) as updatable, ( - c.relkind IN ('r', 'v','f') - AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 16) = 16 - -- CMD_DELETE - OR EXISTS ( - SELECT 1 - FROM pg_trigger - WHERE - pg_trigger.tgrelid = c.oid - and (pg_trigger.tgtype::integer & 73) = 73 + c.relkind IN ('r','p') + OR ( + c.relkind IN ('v','f') + AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 16) = 16 + -- CMD_DELETE + OR EXISTS ( + SELECT 1 + FROM pg_trigger + WHERE + pg_trigger.tgrelid = c.oid + and (pg_trigger.tgtype::integer & 73) = 73 + ) ) ) as deletable from @@ -362,7 +371,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', 'f') + c.relkind in ('v','r','m','f','p') and n.nspname = $1 and ( pg_has_role(c.relowner, 'USAGE') @@ -468,7 +477,7 @@ allTables = c.relname AS table_name, d.description AS table_description, ( - c.relkind = 'r' + c.relkind IN ('r','p') OR ( c.relkind in ('v','f') AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 8) = 8 @@ -488,7 +497,7 @@ allTables = ) ) AS insertable, ( - c.relkind = 'r' + c.relkind IN ('r','p') OR ( c.relkind in ('v','f') AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 4) = 4 @@ -504,7 +513,7 @@ allTables = ) ) AS updatable, ( - c.relkind = 'r' + c.relkind IN ('r','p') OR ( c.relkind in ('v','f') AND (pg_relation_is_updatable(c.oid::regclass, FALSE) & 16) = 16 @@ -522,7 +531,7 @@ allTables = FROM pg_class c 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','f') + WHERE c.relkind IN ('v','r','m','f','p') AND n.nspname NOT IN ('pg_catalog', 'information_schema') ORDER BY table_schema, table_name |] @@ -559,7 +568,7 @@ allColumns tabs = pg_catalog.pg_namespace n WHERE 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 c.relnamespace = n.oid AND n.nspname <> ANY (ARRAY['pg_catalog', 'information_schema'] || $1) @@ -617,7 +626,7 @@ allColumns tabs = NOT pg_is_other_temp_schema(nc.oid) AND a.attnum > 0 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: AND (nc.nspname = ANY ($1) OR kc.r_oid IS NOT NULL) ) @@ -716,7 +725,7 @@ allPrimaryKeys tabs = nc.oid = c.connamespace AND nr.oid = r.relnamespace 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 c.contype = 'p' ), @@ -753,7 +762,7 @@ allPrimaryKeys tabs = AND r.oid = c.conrelid AND nc.oid = c.connamespace 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) ) ss WHERE diff --git a/test/Feature/InsertSpec.hs b/test/Feature/InsertSpec.hs index 04985606e..992d7bea3 100644 --- a/test/Feature/InsertSpec.hs +++ b/test/Feature/InsertSpec.hs @@ -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" [] diff --git a/test/Feature/OpenApiSpec.hs b/test/Feature/OpenApiSpec.hs index e3237206c..190ecb303 100644 --- a/test/Feature/OpenApiSpec.hs +++ b/test/Feature/OpenApiSpec.hs @@ -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 diff --git a/test/Feature/OptionsSpec.hs b/test/Feature/OptionsSpec.hs index 1e8bb969c..63c2d291d 100644 --- a/test/Feature/OptionsSpec.hs +++ b/test/Feature/OptionsSpec.hs @@ -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 diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 97b88d131..57f989d1d 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -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 diff --git a/test/Feature/UpsertSpec.hs b/test/Feature/UpsertSpec.hs index 0eaad6b37..5e9e6586a 100644 --- a/test/Feature/UpsertSpec.hs +++ b/test/Feature/UpsertSpec.hs @@ -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" diff --git a/test/Main.hs b/test/Main.hs index 8a6344f6a..385b7637b 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -127,14 +127,14 @@ main = do , ("Feature.EmbedDisambiguationSpec" , Feature.EmbedDisambiguationSpec.spec) , ("Feature.InsertSpec" , Feature.InsertSpec.spec actualPgVersion) , ("Feature.JsonOperatorSpec" , Feature.JsonOperatorSpec.spec actualPgVersion) - , ("Feature.OpenApiSpec" , Feature.OpenApiSpec.spec) - , ("Feature.OptionsSpec" , Feature.OptionsSpec.spec) + , ("Feature.OpenApiSpec" , Feature.OpenApiSpec.spec actualPgVersion) + , ("Feature.OptionsSpec" , Feature.OptionsSpec.spec actualPgVersion) , ("Feature.QuerySpec" , Feature.QuerySpec.spec actualPgVersion) , ("Feature.RawOutputTypesSpec" , Feature.RawOutputTypesSpec.spec) , ("Feature.RpcSpec" , Feature.RpcSpec.spec actualPgVersion) , ("Feature.SingularSpec" , Feature.SingularSpec.spec) , ("Feature.UpdateSpec" , Feature.UpdateSpec.spec) - , ("Feature.UpsertSpec" , Feature.UpsertSpec.spec) + , ("Feature.UpsertSpec" , Feature.UpsertSpec.spec actualPgVersion) ] hspec $ do diff --git a/test/fixtures/data.sql b/test/fixtures/data.sql index cfe48122d..8fb81404a 100644 --- a/test/fixtures/data.sql +++ b/test/fixtures/data.sql @@ -663,3 +663,28 @@ INSERT INTO private.films (id, title) VALUES (12,'douze commandements'), (2001,' TRUNCATE TABLE private.personnages CASCADE; 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$; \ No newline at end of file diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index 377e3c682..48301366f 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -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 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$; \ No newline at end of file diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index b3fdb2b7f..abc470fe9 100644 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -2185,3 +2185,61 @@ create table private.rollen ( foreign key (film_id) references test.filme(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$; \ No newline at end of file diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index 309b7f34f..94ff66a9d 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -709,6 +709,7 @@ def test_invalid_role_claim_key_notify_reload(defaultenv): postgrest.session.post("/rpc/reset_invalid_role_claim_key") + def test_db_prepared_statements_enable(defaultenv): "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") assert response.text == "true" + def test_db_prepared_statements_disable(defaultenv): "Should not use any prepared statements when the setting is disabled."