From c5245317845d8c0524800b08b3aded150b8eb6a0 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 6 Apr 2020 21:04:55 +0200 Subject: [PATCH] Fix overloaded computed columns on RPC (#1473) * fix some typos and spelling * fix pg_source CTE name should be prefixed with pgrst_ * added tests for overloaded computed columns on patch calls --- CHANGELOG.md | 2 + src/PostgREST/App.hs | 16 +++++--- src/PostgREST/DbRequestBuilder.hs | 7 ++-- src/PostgREST/Private/QueryFragment.hs | 4 +- src/PostgREST/QueryBuilder.hs | 19 +++++---- src/PostgREST/Types.hs | 2 +- test/Feature/EmbedDisambiguationSpec.hs | 18 ++++----- test/Feature/InsertSpec.hs | 51 ++++++++++++++++++------- test/Feature/MultipleSchemaSpec.hs | 30 +++++++-------- test/Feature/QuerySpec.hs | 43 ++++++++++++++++----- test/QueryCost.hs | 10 ++--- test/fixtures/data.sql | 29 ++++++++++++++ test/fixtures/privileges.sql | 9 +++-- test/fixtures/schema.sql | 22 ++++++++++- 14 files changed, 184 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e19b44f9f..b7476417d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- #1473, Fix overloaded computed columns on RPC - @wolfgangwalther + ## [7.0.0] - 2020-04-03 ### Added diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 14b69ebf6..a9b59c40a 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -46,7 +46,8 @@ import PostgREST.ApiRequest (Action (..), ApiRequest (..), import PostgREST.Auth (containsRole, jwtClaims, parseSecret) import PostgREST.Config (AppConfig (..)) -import PostgREST.DbRequestBuilder (mutateRequest, readRequest) +import PostgREST.DbRequestBuilder (mutateRequest, readRequest, + returningCols) import PostgREST.DbStructure import PostgREST.Error (PgError (..), SimpleError (..), errorResponseFor, singularityError) @@ -127,7 +128,7 @@ app dbStructure proc cols conf apiRequest = (ActionRead headersOnly, TargetIdent (QualifiedIdentifier tSchema tName), Nothing) -> case readSqlParts tSchema tName of Left errorResponse -> return errorResponse - Right (q, cq, bField) -> do + Right (q, cq, bField, _) -> do let cQuery = if estimatedCount then limitedQuery cq ((+ 1) <$> maxRows) -- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed else cq @@ -293,10 +294,10 @@ app dbStructure proc cols conf apiRequest = let tName = fromMaybe pName $ procTableName =<< proc in case readSqlParts tSchema tName of Left errorResponse -> return errorResponse - Right (q, cq, bField) -> do + Right (q, cq, bField, returning) -> do let preferParams = iPreferParameters apiRequest - pq = requestToCallProcQuery qi (specifiedProcArgs cols proc) returnsScalar preferParams + pq = requestToCallProcQuery qi (specifiedProcArgs cols proc) returnsScalar preferParams returning stm = callProcStatement returnsScalar pq q cq shouldCount (contentType == CTSingularJSON) (contentType == CTTextCSV) (contentType `elem` rawContentTypes) (preferParams == Just MultipleObjects) bField pgVer @@ -351,11 +352,14 @@ app dbStructure proc cols conf apiRequest = readSqlParts s t = let readReq = readRequest s t maxRows (dbRelations dbStructure) apiRequest + returnings :: ReadRequest -> Either Response [FieldName] + returnings rr = Right (returningCols rr) in - (,,) <$> + (,,,) <$> (readRequestToQuery <$> readReq) <*> (readRequestToCountQuery <$> readReq) <*> - (binaryField contentType rawContentTypes returnsScalar =<< readReq) + (binaryField contentType rawContentTypes returnsScalar =<< readReq) <*> + (returnings =<< readReq) mutateSqlParts s t = let diff --git a/src/PostgREST/DbRequestBuilder.hs b/src/PostgREST/DbRequestBuilder.hs index e6a5865e6..e07138488 100644 --- a/src/PostgREST/DbRequestBuilder.hs +++ b/src/PostgREST/DbRequestBuilder.hs @@ -14,6 +14,7 @@ A query tree is built in case of resource embedding. By inferring the relationsh module PostgREST.DbRequestBuilder ( readRequest , mutateRequest +, returningCols ) where import qualified Data.HashMap.Strict as M @@ -40,7 +41,7 @@ readRequest :: Schema -> TableName -> Maybe Integer -> [Relation] -> ApiRequest readRequest schema rootTableName maxRows allRels apiRequest = mapLeft errorResponseFor $ treeRestrictRange maxRows =<< - augumentRequestWithJoin schema rootRels =<< + augmentRequestWithJoin schema rootRels =<< addFiltersOrdersRanges apiRequest <*> (initReadRequest rootName <$> pRequestSelect sel) where @@ -89,8 +90,8 @@ treeRestrictRange maxRows request = pure $ nodeRestrictRange maxRows <$> request nodeRestrictRange :: Maybe Integer -> ReadNode -> ReadNode nodeRestrictRange m (q@Select {range_=r}, i) = (q{range_=restrictRange m r }, i) -augumentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest -augumentRequestWithJoin schema allRels request = +augmentRequestWithJoin :: Schema -> [Relation] -> ReadRequest -> Either ApiRequestError ReadRequest +augmentRequestWithJoin schema allRels request = addRels schema allRels Nothing request >>= addJoinConditions Nothing diff --git a/src/PostgREST/Private/QueryFragment.hs b/src/PostgREST/Private/QueryFragment.hs index 4d0d36be5..03324a7ad 100644 --- a/src/PostgREST/Private/QueryFragment.hs +++ b/src/PostgREST/Private/QueryFragment.hs @@ -186,8 +186,8 @@ countF :: SqlQuery -> Bool -> (SqlFragment, SqlFragment) countF countQuery shouldCount = if shouldCount then ( - ", pg_source_count AS (" <> countQuery <> ")" - , "(SELECT pg_catalog.count(*) FROM pg_source_count)" ) + ", pgrst_source_count AS (" <> countQuery <> ")" + , "(SELECT pg_catalog.count(*) FROM pgrst_source_count)" ) else ( mempty , "null::bigint") diff --git a/src/PostgREST/QueryBuilder.hs b/src/PostgREST/QueryBuilder.hs index 3d958d2d2..49e182aab 100644 --- a/src/PostgREST/QueryBuilder.hs +++ b/src/PostgREST/QueryBuilder.hs @@ -113,15 +113,15 @@ mutateRequestToQuery (Delete mainQi logicForest returnings) = returningF mainQi returnings ] -requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> SqlQuery -requestToCallProcQuery qi pgArgs returnsScalar preferParams = +requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> [FieldName] -> SqlQuery +requestToCallProcQuery qi pgArgs returnsScalar preferParams returnings = unwords [ "WITH", argsCTE, sourceBody ] where paramsAsSingleObject = preferParams == Just SingleObject - paramsAsMulitpleObjects = preferParams == Just MultipleObjects + paramsAsMultipleObjects = preferParams == Just MultipleObjects (argsCTE, args) | null pgArgs = (ignoredBody, "") @@ -132,7 +132,7 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams = "pgrst_args AS (", "SELECT * FROM json_to_recordset(" <> selectBody <> ") AS _(" <> fmtArgs (\a -> " " <> pgaType a) <> ")", ")"] - , if paramsAsMulitpleObjects + , if paramsAsMultipleObjects then fmtArgs (\a -> " := pgrst_args." <> pgFmtIdent (pgaName a)) else fmtArgs (\a -> " := (SELECT " <> pgFmtIdent (pgaName a) <> " FROM pgrst_args LIMIT 1)") ) @@ -142,20 +142,25 @@ requestToCallProcQuery qi pgArgs returnsScalar preferParams = sourceBody :: SqlFragment sourceBody - | paramsAsMulitpleObjects = + | paramsAsMultipleObjects = if returnsScalar then "SELECT " <> callIt <> " AS pgrst_scalar FROM pgrst_args" else unwords [ "SELECT pgrst_lat_args.*" , "FROM pgrst_args," - , "LATERAL ( SELECT * FROM " <> callIt <> " ) pgrst_lat_args" ] + , "LATERAL ( SELECT " <> returned_columns <> " FROM " <> callIt <> " ) pgrst_lat_args" ] | otherwise = if returnsScalar then "SELECT " <> callIt <> " AS pgrst_scalar" - else "SELECT * FROM " <> callIt + else "SELECT " <> returned_columns <> " FROM " <> callIt callIt :: SqlFragment callIt = fromQi qi <> "(" <> args <> ")" + returned_columns :: SqlFragment + returned_columns + | null returnings = "*" + | otherwise = intercalate ", " (pgFmtColumn (QualifiedIdentifier mempty $ qiName qi) <$> returnings) + -- | SQL query meant for COUNTing the root node of the Tree. -- It only takes WHERE into account and doesn't include LIMIT/OFFSET because it would reduce the COUNT. diff --git a/src/PostgREST/Types.hs b/src/PostgREST/Types.hs index 0d5c34b06..9cdd54750 100644 --- a/src/PostgREST/Types.hs +++ b/src/PostgREST/Types.hs @@ -522,7 +522,7 @@ pgVersion121 :: PgVersion pgVersion121 = PgVersion 120001 "12.1" sourceCTEName :: SqlFragment -sourceCTEName = "pg_source" +sourceCTEName = "pgrst_source" -- | full jspath, e.g. .property[0].attr.detail type JSPath = [JSPathExp] diff --git a/test/Feature/EmbedDisambiguationSpec.hs b/test/Feature/EmbedDisambiguationSpec.hs index b9cbb49ac..8f26e8988 100644 --- a/test/Feature/EmbedDisambiguationSpec.hs +++ b/test/Feature/EmbedDisambiguationSpec.hs @@ -324,20 +324,20 @@ spec = ]|] { matchHeaders = [matchContentTypeJson] } - it "embeds childs recursively" $ - get "/family_tree?id=eq.1&select=id,name, childs:family_tree!parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith` + it "embeds children recursively" $ + get "/family_tree?id=eq.1&select=id,name, children:family_tree!parent(id,name,children:family_tree!parent(id,name))" `shouldRespondWith` [json|[{ - "id": "1", "name": "Parental Unit", "childs": [ - { "id": "2", "name": "Kid One", "childs": [ { "id": "4", "name": "Grandkid One" } ] }, - { "id": "3", "name": "Kid Two", "childs": [ { "id": "5", "name": "Grandkid Two" } ] } + "id": "1", "name": "Parental Unit", "children": [ + { "id": "2", "name": "Kid One", "children": [ { "id": "4", "name": "Grandkid One" } ] }, + { "id": "3", "name": "Kid Two", "children": [ { "id": "5", "name": "Grandkid Two" } ] } ] }]|] { matchHeaders = [matchContentTypeJson] } - it "embeds parent and then embeds childs" $ - get "/family_tree?id=eq.2&select=id,name,parent(id,name,childs:family_tree!parent(id,name))" `shouldRespondWith` + it "embeds parent and then embeds children" $ + get "/family_tree?id=eq.2&select=id,name,parent(id,name,children:family_tree!parent(id,name))" `shouldRespondWith` [json|[{ "id": "2", "name": "Kid One", "parent": { - "id": "1", "name": "Parental Unit", "childs": [ { "id": "2", "name": "Kid One" }, { "id": "3", "name": "Kid Two"} ] + "id": "1", "name": "Parental Unit", "children": [ { "id": "2", "name": "Kid One" }, { "id": "3", "name": "Kid Two"} ] } }]|] { matchHeaders = [matchContentTypeJson] } @@ -356,7 +356,7 @@ spec = } }]|] { matchHeaders = [matchContentTypeJson] } - it "embeds childs" $ do + it "embeds children" $ do get "/organizations?select=id,name,refereeds:organizations!referee(id,name)&id=eq.1" `shouldRespondWith` [json|[{ "id": 1, "name": "Referee Org", diff --git a/test/Feature/InsertSpec.hs b/test/Feature/InsertSpec.hs index ce0fe4aaf..ad9b7c336 100644 --- a/test/Feature/InsertSpec.hs +++ b/test/Feature/InsertSpec.hs @@ -464,17 +464,40 @@ spec actualPgVersion = do matchHeaders = [] } - it "can provide a representation" $ do - _ <- post "/items" - [json| { id: 1 } |] - request methodPatch - "/items?id=eq.1" - [("Prefer", "return=representation")] - [json| { id: 99 } |] - `shouldRespondWith` [json| [{id:99}] |] - { matchHeaders = [matchContentTypeJson] } - -- put value back for other tests - void $ request methodPatch "/items?id=eq.99" [] [json| { "id":1 } |] + context "with representation requested" $ do + it "can provide a representation" $ do + _ <- post "/items" + [json| { id: 1 } |] + request methodPatch + "/items?id=eq.1" + [("Prefer", "return=representation")] + [json| { id: 99 } |] + `shouldRespondWith` [json| [{id:99}] |] + { matchHeaders = [matchContentTypeJson] } + -- put value back for other tests + void $ request methodPatch "/items?id=eq.99" [] [json| { "id":1 } |] + + it "can return computed columns" $ + request methodPatch + "/items?id=eq.1&select=id,always_true" + [("Prefer", "return=representation")] + [json| { id: 1 } |] + `shouldRespondWith` [json| [{ id: 1, always_true: true }] |] + { matchHeaders = [matchContentTypeJson] } + + it "can select overloaded computed columns" $ do + request methodPatch + "/items?id=eq.1&select=id,computed_overload" + [("Prefer", "return=representation")] + [json| { id: 1 } |] + `shouldRespondWith` [json| [{ id: 1, computed_overload: true }] |] + { matchHeaders = [matchContentTypeJson] } + request methodPatch + "/items2?id=eq.1&select=id,computed_overload" + [("Prefer", "return=representation")] + [json| { id: 1 } |] + `shouldRespondWith` [json| [{ id: 1, computed_overload: true }] |] + { matchHeaders = [matchContentTypeJson] } it "makes no updates and returns 204, when patching with an empty json object/array" $ do request methodPatch "/items" [] [json| {} |] @@ -561,7 +584,7 @@ spec actualPgVersion = do , matchHeaders = [ matchContentTypeJson , "Location" <:> "/web_content?id=eq.6" ] } - it "embeds childs after update" $ + it "embeds children after update" $ request methodPatch "/web_content?id=eq.0&select=id,name,web_content(name)" [("Prefer", "return=representation")] [json|{"name": "tardis-patched"}|] @@ -573,7 +596,7 @@ spec actualPgVersion = do matchHeaders = [matchContentTypeJson] } - it "embeds parent, childs and grandchilds after update" $ + it "embeds parent, children and grandchildren after update" $ request methodPatch "/web_content?id=eq.0&select=id,name,web_content(name,web_content(name)),parent_content:p_web_id(name)" [("Prefer", "return=representation")] [json|{"name": "tardis-patched-2"}|] @@ -594,7 +617,7 @@ spec actualPgVersion = do matchHeaders = [matchContentTypeJson] } - it "embeds childs after update without explicitly including the id in the ?select" $ + it "embeds children after update without explicitly including the id in the ?select" $ request methodPatch "/web_content?id=eq.0&select=name,web_content(name)" [("Prefer", "return=representation")] [json|{"name": "tardis-patched"}|] diff --git a/test/Feature/MultipleSchemaSpec.hs b/test/Feature/MultipleSchemaSpec.hs index d45bc888e..ffe2a6592 100644 --- a/test/Feature/MultipleSchemaSpec.hs +++ b/test/Feature/MultipleSchemaSpec.hs @@ -77,7 +77,7 @@ spec actualPgVersion = context "Inserting tables on different schemas" $ do it "succeeds inserting on default schema and returning it" $ - request methodPost "/childs" [("Prefer", "return=representation")] [json|{"name": "child v1-1", "parent_id": 1}|] + request methodPost "/children" [("Prefer", "return=representation")] [json|{"name": "child v1-1", "parent_id": 1}|] `shouldRespondWith` [json|[{"id":1, "name": "child v1-1", "parent_id": 1}]|] { @@ -86,7 +86,7 @@ spec actualPgVersion = } it "succeeds inserting on the v1 schema and returning its parent" $ - request methodPost "/childs?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v1")] + request methodPost "/children?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v1")] [json|{"name": "child v1-2", "parent_id": 2}|] `shouldRespondWith` [json|[{"id":2, "parent": {"id": 2, "name": "parent v1-2"}}]|] @@ -96,7 +96,7 @@ spec actualPgVersion = } it "succeeds inserting on the v2 schema and returning its parent" $ - request methodPost "/childs?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v2")] + request methodPost "/children?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v2")] [json|{"name": "child v2-3", "parent_id": 3}|] `shouldRespondWith` [json|[{"id":1, "parent": {"id": 3, "name": "parent v2-3"}}]|] @@ -106,7 +106,7 @@ spec actualPgVersion = } it "fails when inserting on an unknown schema" $ - request methodPost "/childs" [("Content-Profile", "unknown")] + request methodPost "/children" [("Content-Profile", "unknown")] [json|{"name": "child 4", "parent_id": 4}|] `shouldRespondWith` [json|{"message":"The schema must be one of the following: v1, v2"}|] @@ -125,22 +125,22 @@ spec actualPgVersion = } it "succeeds in calling the v1 schema proc and embedding" $ - request methodGet "/rpc/get_parents_below?id=6&select=id,name,childs(id,name)" [("Accept-Profile", "v1")] "" + request methodGet "/rpc/get_parents_below?id=6&select=id,name,children(id,name)" [("Accept-Profile", "v1")] "" `shouldRespondWith` [json| [ - {"id":1,"name":"parent v1-1","childs":[{"id":1,"name":"child v1-1"}]}, - {"id":2,"name":"parent v1-2","childs":[{"id":2,"name":"child v1-2"}]}] |] + {"id":1,"name":"parent v1-1","children":[{"id":1,"name":"child v1-1"}]}, + {"id":2,"name":"parent v1-2","children":[{"id":2,"name":"child v1-2"}]}] |] { matchStatus = 200 , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v1"] } it "succeeds in calling the v2 schema proc and embedding" $ - request methodGet "/rpc/get_parents_below?id=6&select=id,name,childs(id,name)" [("Accept-Profile", "v2")] "" + request methodGet "/rpc/get_parents_below?id=6&select=id,name,children(id,name)" [("Accept-Profile", "v2")] "" `shouldRespondWith` [json| [ - {"id":3,"name":"parent v2-3","childs":[{"id":1,"name":"child v2-3"}]}, - {"id":4,"name":"parent v2-4","childs":[]}] |] + {"id":3,"name":"parent v2-3","children":[{"id":1,"name":"child v2-3"}]}, + {"id":4,"name":"parent v2-4","children":[]}] |] { matchStatus = 200 , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"] @@ -148,7 +148,7 @@ spec actualPgVersion = context "Modifying tables on different schemas" $ do it "succeeds in patching on the v1 schema and returning its parent" $ - request methodPatch "/childs?select=name,parent(name)&id=eq.1" [("Content-Profile", "v1"), ("Prefer", "return=representation")] + request methodPatch "/children?select=name,parent(name)&id=eq.1" [("Content-Profile", "v1"), ("Prefer", "return=representation")] [json|{"name": "child v1-1 updated"}|] `shouldRespondWith` [json|[{"name":"child v1-1 updated", "parent": {"name": "parent v1-1"}}]|] @@ -158,7 +158,7 @@ spec actualPgVersion = } it "succeeds in patching on the v2 schema and returning its parent" $ - request methodPatch "/childs?select=name,parent(name)&id=eq.1" [("Content-Profile", "v2"), ("Prefer", "return=representation")] + request methodPatch "/children?select=name,parent(name)&id=eq.1" [("Content-Profile", "v2"), ("Prefer", "return=representation")] [json|{"name": "child v2-1 updated"}|] `shouldRespondWith` [json|[{"name":"child v2-1 updated", "parent": {"name": "parent v2-3"}}]|] @@ -168,13 +168,13 @@ spec actualPgVersion = } it "succeeds on deleting on the v2 schema" $ do - request methodDelete "/childs?id=eq.1" [("Content-Profile", "v2"), ("Prefer", "return=representation")] "" + request methodDelete "/children?id=eq.1" [("Content-Profile", "v2"), ("Prefer", "return=representation")] "" `shouldRespondWith` [json|[{"id": 1, "name": "child v2-1 updated", "parent_id": 3}]|] { matchStatus = 200 , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"] } - request methodGet "/childs?id=eq.1" [("Accept-Profile", "v2")] "" + request methodGet "/children?id=eq.1" [("Accept-Profile", "v2")] "" `shouldRespondWith` "[]" { matchStatus = 200 @@ -183,7 +183,7 @@ spec actualPgVersion = when (actualPgVersion >= pgVersion96) $ it "succeeds on PUT on the v2 schema" $ - request methodPut "/childs?id=eq.111" [("Content-Profile", "v2"), ("Prefer", "return=representation")] + request methodPut "/children?id=eq.111" [("Content-Profile", "v2"), ("Prefer", "return=representation")] [json| [ { "id": 111, "name": "child v2-111", "parent_id": null } ]|] `shouldRespondWith` [json|[{ "id": 111, "name": "child v2-111", "parent_id": null }]|] diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 05574e75d..4e3d1260c 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -237,7 +237,6 @@ spec actualPgVersion = do [json| [{"myId":1}] |] { matchHeaders = [matchContentTypeJson] } - it "one simple column with casting (text)" $ get "/complex_items?select=id::text" `shouldRespondWith` [json| [{"id":"1"},{"id":"2"},{"id":"3"}] |] @@ -327,6 +326,30 @@ spec actualPgVersion = do [json|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|] { matchHeaders = [matchContentTypeJson] } + describe "computed columns" $ do + it "computed column on table" $ + get "/items?id=eq.1&select=id,always_true" `shouldRespondWith` + [json|[{"id":1,"always_true":true}]|] + { matchHeaders = [matchContentTypeJson] } + + it "computed column on rpc" $ + get "/rpc/search?id=1&select=id,always_true" `shouldRespondWith` + [json|[{"id":1,"always_true":true}]|] + { matchHeaders = [matchContentTypeJson] } + + it "overloaded computed columns on both tables" $ do + get "/items?id=eq.1&select=id,computed_overload" `shouldRespondWith` + [json|[{"id":1,"computed_overload":true}]|] + { matchHeaders = [matchContentTypeJson] } + get "/items2?id=eq.1&select=id,computed_overload" `shouldRespondWith` + [json|[{"id":1,"computed_overload":true}]|] + { matchHeaders = [matchContentTypeJson] } + + it "overloaded computed column on rpc" $ + get "/rpc/search?id=1&select=id,computed_overload" `shouldRespondWith` + [json|[{"id":1,"computed_overload":true}]|] + { 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 @@ -482,18 +505,18 @@ spec actualPgVersion = do "designTasks":[ ] } ]|] { matchHeaders = [matchContentTypeJson] } - it "works with two aliased childs embeds plus and/or" $ - get "/entities?select=id,childs:child_entities(id,gChilds:grandchild_entities(id))&childs.and=(id.in.(1,2,3))&childs.gChilds.or=(id.eq.1,id.eq.2)" `shouldRespondWith` + it "works with two aliased children embeds plus and/or" $ + get "/entities?select=id,children:child_entities(id,gChildren:grandchild_entities(id))&children.and=(id.in.(1,2,3))&children.gChildren.or=(id.eq.1,id.eq.2)" `shouldRespondWith` [json|[ { "id":1, - "childs":[ - {"id":1,"gChilds":[{"id":1}, {"id":2}]}, - {"id":2,"gChilds":[]}]}, + "children":[ + {"id":1,"gChildren":[{"id":1}, {"id":2}]}, + {"id":2,"gChildren":[]}]}, { "id":2, - "childs":[ - {"id":3,"gChilds":[]}]}, - { "id":3,"childs":[]}, - { "id":4,"childs":[]} + "children":[ + {"id":3,"gChildren":[]}]}, + { "id":3,"children":[]}, + { "id":4,"children":[]} ]|] { matchHeaders = [matchContentTypeJson] } describe "ordering response" $ do diff --git a/test/QueryCost.hs b/test/QueryCost.hs index 8fd27faec..81c5860be 100644 --- a/test/QueryCost.hs +++ b/test/QueryCost.hs @@ -29,33 +29,33 @@ main = do context "call proc query" $ do it "should not exceed cost when calling setof composite proc" $ do cost <- exec pool [str| {"id": 3} |] $ - requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [PgArg "id" "int" True] False Nothing + requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [PgArg "id" "int" True] False Nothing [] liftIO $ cost `shouldSatisfy` (< Just 40) it "should not exceed cost when calling setof composite proc with empty params" $ do cost <- exec pool mempty $ - requestToCallProcQuery (QualifiedIdentifier "test" "getallprojects") [] False Nothing + requestToCallProcQuery (QualifiedIdentifier "test" "getallprojects") [] False Nothing [] liftIO $ cost `shouldSatisfy` (< Just 30) it "should not exceed cost when calling scalar proc" $ do cost <- exec pool [str| {"a": 3, "b": 4} |] $ - requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [PgArg "a" "int" True, PgArg "b" "int" True] True Nothing + requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [PgArg "a" "int" True, PgArg "b" "int" True] True Nothing [] liftIO $ cost `shouldSatisfy` (< Just 10) context "params=multiple-objects" $ do it "should not exceed cost when calling setof composite proc" $ do cost <- exec pool [str| [{"id": 1}, {"id": 4}] |] $ - requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [PgArg "id" "int" True] False (Just MultipleObjects) + requestToCallProcQuery (QualifiedIdentifier "test" "get_projects_below") [PgArg "id" "int" True] False (Just MultipleObjects) [] liftIO $ do cost `shouldSatisfy` (> Just 2000) cost `shouldSatisfy` (< Just 2100) it "should not exceed cost when calling scalar proc" $ do cost <- exec pool [str| [{"a": 3, "b": 4}, {"a": 1, "b": 2}, {"a": 8, "b": 7}] |] $ - requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [PgArg "a" "int" True, PgArg "b" "int" True] True Nothing + requestToCallProcQuery (QualifiedIdentifier "test" "add_them") [PgArg "a" "int" True, PgArg "b" "int" True] True Nothing [] liftIO $ cost `shouldSatisfy` (< Just 10) diff --git a/test/fixtures/data.sql b/test/fixtures/data.sql index 5eff88712..594fadab0 100644 --- a/test/fixtures/data.sql +++ b/test/fixtures/data.sql @@ -207,6 +207,35 @@ INSERT INTO items VALUES (15); SELECT pg_catalog.setval('items_id_seq', 15, true); +-- +-- Data for Name: items2; Type: TABLE DATA; Schema: test; Owner: - +-- + +TRUNCATE TABLE items2 CASCADE; +INSERT INTO items2 VALUES (1); +INSERT INTO items2 VALUES (2); +INSERT INTO items2 VALUES (3); +INSERT INTO items2 VALUES (4); +INSERT INTO items2 VALUES (5); +INSERT INTO items2 VALUES (6); +INSERT INTO items2 VALUES (7); +INSERT INTO items2 VALUES (8); +INSERT INTO items2 VALUES (9); +INSERT INTO items2 VALUES (10); +INSERT INTO items2 VALUES (11); +INSERT INTO items2 VALUES (12); +INSERT INTO items2 VALUES (13); +INSERT INTO items2 VALUES (14); +INSERT INTO items2 VALUES (15); + + +-- +-- Name: items_id_seq; Type: SEQUENCE SET; Schema: test; Owner: - +-- + +SELECT pg_catalog.setval('items2_id_seq', 15, true); + + -- -- Data for Name: json; Type: TABLE DATA; Schema: test; Owner: - -- diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index 34be7600b..70e531100 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -15,6 +15,7 @@ SET search_path = test, "تست", pg_catalog; GRANT ALL ON TABLE items + , items2 , "articleStars" , articles , auto_incrementing_pk @@ -127,8 +128,8 @@ GRANT ALL ON TABLE , v1.parents , v2.parents , v2.another_table - , v1.childs - , v2.childs + , v1.children + , v2.children TO postgrest_test_anonymous; GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous; @@ -138,8 +139,8 @@ GRANT USAGE ON SEQUENCE , items_id_seq , callcounter_count , leak_id_seq - , v1.childs_id_seq - , v2.childs_id_seq + , v1.children_id_seq + , v2.children_id_seq TO postgrest_test_anonymous; -- Privileges for non anonymous users diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index 0fb270403..c6721bfc2 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -109,10 +109,28 @@ CREATE TABLE items ( id bigserial primary key ); +CREATE TABLE items2 ( + id bigserial primary key +); + +CREATE FUNCTION search(id BIGINT) RETURNS SETOF items + LANGUAGE plpgsql + AS $$BEGIN + RETURN QUERY SELECT items.id FROM items WHERE items.id=search.id; + END$$; + CREATE FUNCTION always_true(test.items) RETURNS boolean LANGUAGE sql STABLE AS $$ SELECT true $$; +CREATE FUNCTION computed_overload(test.items) RETURNS boolean + LANGUAGE sql STABLE + AS $$ SELECT true $$; + +CREATE FUNCTION computed_overload(test.items2) RETURNS boolean + LANGUAGE sql STABLE + AS $$ SELECT true $$; + CREATE FUNCTION is_first(test.items) RETURNS boolean LANGUAGE sql STABLE AS $$ SELECT $1.id = 1 $$; @@ -1702,7 +1720,7 @@ create table v1.parents ( , name text ); -create table v1.childs ( +create table v1.children ( id serial primary key , name text , parent_id int @@ -1720,7 +1738,7 @@ create table v2.parents ( , name text ); -create table v2.childs ( +create table v2.children ( id serial primary key , name text , parent_id int