Merge pull request #424 from calebmer/feature/select-column
Enable selection by column
This commit is contained in:
@@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
### Added
|
### Added
|
||||||
- Allow order by computed columns - @diogob
|
- Allow order by computed columns - @diogob
|
||||||
- Set max rows in response with --max-rows - @begriffs
|
- Set max rows in response with --max-rows - @begriffs
|
||||||
|
- Selection by column name (can detect if `_id` is not included) - @calebmer
|
||||||
|
|
||||||
## [0.3.0.1] - 2015-11-27
|
## [0.3.0.1] - 2015-11-27
|
||||||
|
|
||||||
|
|||||||
+32
-4
@@ -227,10 +227,11 @@ Content-Range → 0-14/*
|
|||||||
|
|
||||||
### Embedding Foreign Entities
|
### Embedding Foreign Entities
|
||||||
|
|
||||||
Suppose you have a `projects` table which references `clients` through
|
To help you make fewer requests, PostgREST allows the embedding of
|
||||||
a foreign key called `client_id`. When listing projects through the
|
traditional SQL relationships into a response. Suppose you have a
|
||||||
API you can have it embed the client within each project response.
|
`projects` table which references `clients` through a foreign key
|
||||||
For example,
|
called `client_id`. When listing projects through the API you can
|
||||||
|
have it embed the client within each project response. For example,
|
||||||
|
|
||||||
```HTTP
|
```HTTP
|
||||||
GET /projects?id=eq.1&select=id, name, clients{*}
|
GET /projects?id=eq.1&select=id, name, clients{*}
|
||||||
@@ -257,6 +258,33 @@ but the the select query is recursive. You could for instance specify
|
|||||||
GET /foo?select=x, y, bar{z, w, baz{*}}
|
GET /foo?select=x, y, bar{z, w, baz{*}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can select not only using table names, but also column names!
|
||||||
|
To embed the same foreign key row from our client example earlier
|
||||||
|
you could do the following:
|
||||||
|
|
||||||
|
```HTTP
|
||||||
|
GET /projects?id=eq.1&select=id, name, client_id{*}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the response there will be a `client_id` object containing all
|
||||||
|
the data for that row.
|
||||||
|
|
||||||
|
However, a `client_id` object doesn't make a lot of sense, so you
|
||||||
|
could do one of two things. Create a view which renames `client_id`
|
||||||
|
to just `client` (this is the hard way), or just try `client{*}`
|
||||||
|
in the select parameter! PostgREST supports smart ducktype checking
|
||||||
|
for common foreign key names, so if your column name ends with
|
||||||
|
`_id`, `_fk`, or any variation of the two (including camelcase)
|
||||||
|
you can embed a row with just the name's beginning.
|
||||||
|
|
||||||
|
So for a complete example:
|
||||||
|
|
||||||
|
```HTTP
|
||||||
|
GET /projects?id=eq.1&select=id, name, client{*}
|
||||||
|
```
|
||||||
|
|
||||||
|
Would embed in the `client` key the row referenced with `client_id`.
|
||||||
|
|
||||||
### Response Format
|
### Response Format
|
||||||
|
|
||||||
Query responses default to JSON but you can get them in CSV as well. Just make your request with the header
|
Query responses default to JSON but you can get them in CSV as well. Just make your request with the header
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE TupleSections #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE TupleSections #-}
|
||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
{-|
|
{-|
|
||||||
Module : PostgREST.QueryBuilder
|
Module : PostgREST.QueryBuilder
|
||||||
@@ -47,6 +48,8 @@ import Data.Tree (Tree(..))
|
|||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
import Text.Regex.TDFA ((=~))
|
||||||
|
import qualified Data.ByteString.Char8 as BS
|
||||||
import Data.Scientific ( FPFormat (..)
|
import Data.Scientific ( FPFormat (..)
|
||||||
, formatScientific
|
, formatScientific
|
||||||
, isInteger
|
, isInteger
|
||||||
@@ -120,20 +123,29 @@ createWriteStatement qi selectQuery mutateQuery isSingle Full
|
|||||||
| otherwise = asJsonF
|
| otherwise = asJsonF
|
||||||
|
|
||||||
addRelations :: Schema -> [Relation] -> Maybe ReadRequest -> ReadRequest -> Either Text ReadRequest
|
addRelations :: Schema -> [Relation] -> Maybe ReadRequest -> ReadRequest -> Either Text ReadRequest
|
||||||
addRelations schema allRelations parentNode node@(Node n@(query, (table, _)) forest) =
|
addRelations schema allRelations parentNode node@(Node readNode@(query, (name, _)) forest) =
|
||||||
case parentNode of
|
case parentNode of
|
||||||
Nothing -> Node (query, (table, Nothing)) <$> updatedForest
|
(Just (Node (Select{from=[parentTable]}, (_, _)) _)) -> Node <$> (addRel readNode <$> rel) <*> updatedForest
|
||||||
(Just (Node (_, (parentTable, _)) _)) -> Node <$> (addRel n <$> rel) <*> updatedForest
|
|
||||||
where
|
where
|
||||||
rel = note ("no relation between " <> table <> " and " <> parentTable)
|
rel = note ("no relation between " <> parentTable <> " and " <> name)
|
||||||
$ findRelation schema table parentTable
|
$ findRelationByTable schema name parentTable
|
||||||
<|> findRelation schema parentTable table
|
<|> findRelationByTable schema parentTable name
|
||||||
|
<|> findRelationByColumn schema parentTable name
|
||||||
addRel :: (ReadQuery, (NodeName, Maybe Relation)) -> Relation -> (ReadQuery, (NodeName, Maybe Relation))
|
addRel :: (ReadQuery, (NodeName, Maybe Relation)) -> Relation -> (ReadQuery, (NodeName, Maybe Relation))
|
||||||
addRel (q, (t, _)) r = (q, (t, Just r))
|
addRel (q, (n, _)) r = (q {from=fromRelation}, (n, Just r))
|
||||||
|
where fromRelation = map (\t -> if t == n then tableName (relTable r) else t) (from q)
|
||||||
|
|
||||||
|
_ -> Node (query, (name, Nothing)) <$> updatedForest
|
||||||
where
|
where
|
||||||
updatedForest = mapM (addRelations schema allRelations (Just node)) forest
|
updatedForest = mapM (addRelations schema allRelations (Just node)) forest
|
||||||
findRelation s t1 t2 =
|
-- Searches through all the relations and returns a match given the parameter conditions.
|
||||||
find (\r -> s == (tableSchema . relTable) r && t1 == (tableName . relTable) r && t2 == (tableName . relFTable) r) allRelations
|
-- Will only find a relation where both schemas are in the PostgREST schema.
|
||||||
|
-- `findRelationByColumn` also does a ducktype check to see if the column name has any variation of `id` or `fk`. If so then the relation is returned as a match.
|
||||||
|
findRelationByTable s t1 t2 =
|
||||||
|
find (\r -> s == tableSchema (relTable r) && s == tableSchema (relFTable r) && t1 == tableName (relTable r) && t2 == tableName (relFTable r)) allRelations
|
||||||
|
findRelationByColumn s t c =
|
||||||
|
find (\r -> s == tableSchema (relTable r) && s == tableSchema (relFTable r) && t == tableName (relFTable r) && length (relFColumns r) == 1 && c `colMatches` (colName . head . relFColumns) r) allRelations
|
||||||
|
where n `colMatches` rc = (cs ("^" <> rc <> "_?(?:|[iI][dD]|[fF][kK])$") :: BS.ByteString) =~ (cs n :: BS.ByteString)
|
||||||
|
|
||||||
addJoinConditions :: Schema -> ReadRequest -> Either Text ReadRequest
|
addJoinConditions :: Schema -> ReadRequest -> Either Text ReadRequest
|
||||||
addJoinConditions schema (Node (query, (n, r)) forest) =
|
addJoinConditions schema (Node (query, (n, r)) forest) =
|
||||||
@@ -218,11 +230,12 @@ requestToCountQuery schema (DbRead (Node (Select _ _ conditions _, (mainTbl, _))
|
|||||||
requestToQuery :: Schema -> DbRequest -> SqlQuery
|
requestToQuery :: Schema -> DbRequest -> SqlQuery
|
||||||
requestToQuery _ (DbMutate (Insert _ (PayloadParseError _))) = undefined
|
requestToQuery _ (DbMutate (Insert _ (PayloadParseError _))) = undefined
|
||||||
requestToQuery _ (DbMutate (Update _ (PayloadParseError _) _)) = undefined
|
requestToQuery _ (DbMutate (Update _ (PayloadParseError _) _)) = undefined
|
||||||
requestToQuery schema (DbRead (Node (Select colSelects tbls conditions ord, (mainTbl, _)) forest)) =
|
requestToQuery schema (DbRead (Node (Select colSelects tbls conditions ord, (nodeName, maybeRelation)) forest)) =
|
||||||
query
|
query
|
||||||
where
|
where
|
||||||
-- TODO! the folloing helper functions are just to remove the "schema" part when the table is "source" which is the name
|
-- TODO! the folloing helper functions are just to remove the "schema" part when the table is "source" which is the name
|
||||||
-- of our WITH query part
|
-- of our WITH query part
|
||||||
|
mainTbl = fromMaybe nodeName (tableName . relTable <$> maybeRelation)
|
||||||
tblSchema tbl = if tbl == sourceCTEName then "" else schema
|
tblSchema tbl = if tbl == sourceCTEName then "" else schema
|
||||||
qi = QualifiedIdentifier (tblSchema mainTbl) mainTbl
|
qi = QualifiedIdentifier (tblSchema mainTbl) mainTbl
|
||||||
toQi t = QualifiedIdentifier (tblSchema t) t
|
toQi t = QualifiedIdentifier (tblSchema t) t
|
||||||
@@ -253,28 +266,27 @@ requestToQuery schema (DbRead (Node (Select colSelects tbls conditions ord, (mai
|
|||||||
intercalate " AND " ( map (pgFmtCondition qi ) joinConditions )
|
intercalate " AND " ( map (pgFmtCondition qi ) joinConditions )
|
||||||
where
|
where
|
||||||
joinConditions = filter (filterParentConditions t) conditions
|
joinConditions = filter (filterParentConditions t) conditions
|
||||||
filterParentConditions parentTable (Filter _ _ (VForeignKey (QualifiedIdentifier "" t) _)) =
|
filterParentConditions parentTable (Filter _ _ (VForeignKey (QualifiedIdentifier "" t) _)) = parentTable == t
|
||||||
parentTable == t
|
|
||||||
filterParentConditions _ _ = False
|
filterParentConditions _ _ = False
|
||||||
getQueryParts :: Tree ReadNode -> ([(SqlFragment, TableName)], [SqlFragment]) -> ([(SqlFragment,TableName)], [SqlFragment])
|
getQueryParts :: Tree ReadNode -> ([(SqlFragment, TableName)], [SqlFragment]) -> ([(SqlFragment,TableName)], [SqlFragment])
|
||||||
getQueryParts (Node n@(_, (table, Just (Relation {relType=Child}))) forst) (j,s) = (j,sel:s)
|
getQueryParts (Node n@(_, (name, Just (Relation {relType=Child,relTable=Table{tableName=table}}))) forst) (j,s) = (j,sel:s)
|
||||||
where
|
where
|
||||||
sel = "COALESCE(("
|
sel = "COALESCE(("
|
||||||
<> "SELECT array_to_json(array_agg(row_to_json("<>table<>"))) "
|
<> "SELECT array_to_json(array_agg(row_to_json("<>table<>"))) "
|
||||||
<> "FROM (" <> subquery <> ") " <> table
|
<> "FROM (" <> subquery <> ") " <> table
|
||||||
<> "), '[]') AS " <> table
|
<> "), '[]') AS " <> pgFmtIdent name
|
||||||
where subquery = requestToQuery schema (DbRead (Node n forst))
|
where subquery = requestToQuery schema (DbRead (Node n forst))
|
||||||
getQueryParts (Node n@(_, (table, Just (Relation {relType=Parent}))) forst) (j,s) = (joi:j,sel:s)
|
getQueryParts (Node n@(_, (name, Just (Relation {relType=Parent,relTable=Table{tableName=table}}))) forst) (j,s) = (joi:j,sel:s)
|
||||||
where
|
where
|
||||||
sel = "row_to_json(" <> table <> ".*) AS "<>table --TODO must be singular
|
sel = "row_to_json(" <> table <> ".*) AS "<>pgFmtIdent name --TODO must be singular
|
||||||
joi = ("( " <> subquery <> " ) AS " <> table, table)
|
joi = ("( " <> subquery <> " ) AS " <> table, table)
|
||||||
where subquery = requestToQuery schema (DbRead (Node n forst))
|
where subquery = requestToQuery schema (DbRead (Node n forst))
|
||||||
getQueryParts (Node n@(_, (table, Just (Relation {relType=Many}))) forst) (j,s) = (j,sel:s)
|
getQueryParts (Node n@(_, (name, Just (Relation {relType=Many,relTable=Table{tableName=table}}))) forst) (j,s) = (j,sel:s)
|
||||||
where
|
where
|
||||||
sel = "COALESCE (("
|
sel = "COALESCE (("
|
||||||
<> "SELECT array_to_json(array_agg(row_to_json("<>table<>"))) "
|
<> "SELECT array_to_json(array_agg(row_to_json("<>table<>"))) "
|
||||||
<> "FROM (" <> subquery <> ") " <> table
|
<> "FROM (" <> subquery <> ") " <> table
|
||||||
<> "), '[]') AS " <> table
|
<> "), '[]') AS " <> pgFmtIdent name
|
||||||
where subquery = requestToQuery schema (DbRead (Node n forst))
|
where subquery = requestToQuery schema (DbRead (Node n forst))
|
||||||
--the following is just to remove the warning
|
--the following is just to remove the warning
|
||||||
--getQueryParts is not total but requestToQuery is called only after addJoinConditions which ensures the only
|
--getQueryParts is not total but requestToQuery is called only after addJoinConditions which ensures the only
|
||||||
|
|||||||
@@ -106,10 +106,10 @@ type Cast = Text
|
|||||||
type NodeName = Text
|
type NodeName = Text
|
||||||
type SelectItem = (Field, Maybe Cast)
|
type SelectItem = (Field, Maybe Cast)
|
||||||
type Path = [Text]
|
type Path = [Text]
|
||||||
data ReadQuery = Select { select::[SelectItem], from::[Text], flt_::[Filter], order::Maybe [OrderTerm] } deriving (Show, Eq)
|
data ReadQuery = Select { select::[SelectItem], from::[TableName], flt_::[Filter], order::Maybe [OrderTerm] } deriving (Show, Eq)
|
||||||
data MutateQuery = Insert { in_::Text, qPayload::Payload }
|
data MutateQuery = Insert { in_::TableName, qPayload::Payload }
|
||||||
| Delete { in_::Text, where_::[Filter] }
|
| Delete { in_::TableName, where_::[Filter] }
|
||||||
| Update { in_::Text, qPayload::Payload, where_::[Filter] } deriving (Show, Eq)
|
| Update { in_::TableName, qPayload::Payload, where_::[Filter] } deriving (Show, Eq)
|
||||||
data Filter = Filter {field::Field, operator::Operator, value::FValue} deriving (Show, Eq)
|
data Filter = Filter {field::Field, operator::Operator, value::FValue} deriving (Show, Eq)
|
||||||
type ReadNode = (ReadQuery, (NodeName, Maybe Relation))
|
type ReadNode = (ReadQuery, (NodeName, Maybe Relation))
|
||||||
type ReadRequest = Tree ReadNode
|
type ReadRequest = Tree ReadNode
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ module Feature.DeleteSpec where
|
|||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Test.Hspec.Wai
|
import Test.Hspec.Wai
|
||||||
|
import Text.Heredoc
|
||||||
|
|
||||||
import Hasql as H
|
import Hasql as H
|
||||||
import Hasql.Postgres as P
|
import Hasql.Postgres as P
|
||||||
@@ -28,7 +29,7 @@ spec struct pool = beforeAll resetDb
|
|||||||
_ <- request methodDelete "/items?id=lt.15" [] ""
|
_ <- request methodDelete "/items?id=lt.15" [] ""
|
||||||
get "/items"
|
get "/items"
|
||||||
`shouldRespondWith` ResponseMatcher {
|
`shouldRespondWith` ResponseMatcher {
|
||||||
matchBody = Just "[{\"id\":15}]"
|
matchBody = Just [str|[{"id":15}]|]
|
||||||
, matchStatus = 200
|
, matchStatus = 200
|
||||||
, matchHeaders = ["Content-Range" <:> "0-0/1"]
|
, matchHeaders = ["Content-Range" <:> "0-0/1"]
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-17
@@ -90,25 +90,25 @@ spec struct pool = around (withApp cfgDefault struct pool) $ do
|
|||||||
get "/no_pk?a=is.null" `shouldRespondWith`
|
get "/no_pk?a=is.null" `shouldRespondWith`
|
||||||
[json| [{"a": null, "b": null}] |]
|
[json| [{"a": null, "b": null}] |]
|
||||||
|
|
||||||
get "/nullable_integer?a=is.null" `shouldRespondWith` "[{\"a\":null}]"
|
get "/nullable_integer?a=is.null" `shouldRespondWith` [str|[{"a":null}]|]
|
||||||
|
|
||||||
it "matches with like" $ do
|
it "matches with like" $ do
|
||||||
get "/simple_pk?k=like.*yx" `shouldRespondWith`
|
get "/simple_pk?k=like.*yx" `shouldRespondWith`
|
||||||
"[{\"k\":\"xyyx\",\"extra\":\"u\"}]"
|
[str|[{"k":"xyyx","extra":"u"}]|]
|
||||||
get "/simple_pk?k=like.xy*" `shouldRespondWith`
|
get "/simple_pk?k=like.xy*" `shouldRespondWith`
|
||||||
"[{\"k\":\"xyyx\",\"extra\":\"u\"}]"
|
[str|[{"k":"xyyx","extra":"u"}]|]
|
||||||
get "/simple_pk?k=like.*YY*" `shouldRespondWith`
|
get "/simple_pk?k=like.*YY*" `shouldRespondWith`
|
||||||
"[{\"k\":\"xYYx\",\"extra\":\"v\"}]"
|
[str|[{"k":"xYYx","extra":"v"}]|]
|
||||||
|
|
||||||
it "matches with like using not operator" $
|
it "matches with like using not operator" $
|
||||||
get "/simple_pk?k=not.like.*yx" `shouldRespondWith`
|
get "/simple_pk?k=not.like.*yx" `shouldRespondWith`
|
||||||
"[{\"k\":\"xYYx\",\"extra\":\"v\"}]"
|
[str|[{"k":"xYYx","extra":"v"}]|]
|
||||||
|
|
||||||
it "matches with ilike" $ do
|
it "matches with ilike" $ do
|
||||||
get "/simple_pk?k=ilike.xy*&order=extra.asc" `shouldRespondWith`
|
get "/simple_pk?k=ilike.xy*&order=extra.asc" `shouldRespondWith`
|
||||||
"[{\"k\":\"xyyx\",\"extra\":\"u\"},{\"k\":\"xYYx\",\"extra\":\"v\"}]"
|
[str|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|]
|
||||||
get "/simple_pk?k=ilike.*YY*&order=extra.asc" `shouldRespondWith`
|
get "/simple_pk?k=ilike.*YY*&order=extra.asc" `shouldRespondWith`
|
||||||
"[{\"k\":\"xyyx\",\"extra\":\"u\"},{\"k\":\"xYYx\",\"extra\":\"v\"}]"
|
[str|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|]
|
||||||
|
|
||||||
it "matches with ilike using not operator" $
|
it "matches with ilike using not operator" $
|
||||||
get "/simple_pk?k=not.ilike.xy*&order=extra.asc" `shouldRespondWith` "[]"
|
get "/simple_pk?k=not.ilike.xy*&order=extra.asc" `shouldRespondWith` "[]"
|
||||||
@@ -131,7 +131,7 @@ spec struct pool = around (withApp cfgDefault struct pool) $ do
|
|||||||
|
|
||||||
it "matches filtering nested items" $
|
it "matches filtering nested items" $
|
||||||
get "/clients?select=id,projects{id,tasks{id,name}}&projects.tasks.name=like.Design*" `shouldRespondWith`
|
get "/clients?select=id,projects{id,tasks{id,name}}&projects.tasks.name=like.Design*" `shouldRespondWith`
|
||||||
"[{\"id\":1,\"projects\":[{\"id\":1,\"tasks\":[{\"id\":1,\"name\":\"Design w7\"}]},{\"id\":2,\"tasks\":[{\"id\":3,\"name\":\"Design w10\"}]}]},{\"id\":2,\"projects\":[{\"id\":3,\"tasks\":[{\"id\":5,\"name\":\"Design IOS\"}]},{\"id\":4,\"tasks\":[{\"id\":7,\"name\":\"Design OSX\"}]}]}]"
|
[str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1,"name":"Design w7"}]},{"id":2,"tasks":[{"id":3,"name":"Design w10"}]}]},{"id":2,"projects":[{"id":3,"tasks":[{"id":5,"name":"Design IOS"}]},{"id":4,"tasks":[{"id":7,"name":"Design OSX"}]}]}]|]
|
||||||
|
|
||||||
it "matches with @> operator" $
|
it "matches with @> operator" $
|
||||||
get "/complex_items?select=id&arr_data=@>.{2}" `shouldRespondWith`
|
get "/complex_items?select=id&arr_data=@>.{2}" `shouldRespondWith`
|
||||||
@@ -192,15 +192,15 @@ spec struct pool = around (withApp cfgDefault struct pool) $ do
|
|||||||
|
|
||||||
it "requesting parents and children" $
|
it "requesting parents and children" $
|
||||||
get "/projects?id=eq.1&select=id, name, clients{*}, tasks{id, name}" `shouldRespondWith`
|
get "/projects?id=eq.1&select=id, name, clients{*}, tasks{id, name}" `shouldRespondWith`
|
||||||
"[{\"id\":1,\"name\":\"Windows 7\",\"clients\":{\"id\":1,\"name\":\"Microsoft\"},\"tasks\":[{\"id\":1,\"name\":\"Design w7\"},{\"id\":2,\"name\":\"Code w7\"}]}]"
|
[str|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|]
|
||||||
|
|
||||||
it "requesting parents and filtering parent columns" $
|
it "requesting parents and filtering parent columns" $
|
||||||
get "/projects?id=eq.1&select=id, name, clients{id}" `shouldRespondWith`
|
get "/projects?id=eq.1&select=id, name, clients{id}" `shouldRespondWith`
|
||||||
"[{\"id\":1,\"name\":\"Windows 7\",\"clients\":{\"id\":1}}]"
|
[str|[{"id":1,"name":"Windows 7","clients":{"id":1}}]|]
|
||||||
|
|
||||||
it "rows with missing parents are included" $
|
it "rows with missing parents are included" $
|
||||||
get "/projects?id=in.1,5&select=id,clients{id}" `shouldRespondWith`
|
get "/projects?id=in.1,5&select=id,clients{id}" `shouldRespondWith`
|
||||||
"[{\"id\":1,\"clients\":{\"id\":1}},{\"id\":5,\"clients\":null}]"
|
[str|[{"id":1,"clients":{"id":1}},{"id":5,"clients":null}]|]
|
||||||
|
|
||||||
it "rows with no children return [] instead of null" $
|
it "rows with no children return [] instead of null" $
|
||||||
get "/projects?id=in.5&select=id,tasks{id}" `shouldRespondWith`
|
get "/projects?id=in.5&select=id,tasks{id}" `shouldRespondWith`
|
||||||
@@ -208,23 +208,31 @@ spec struct pool = around (withApp cfgDefault struct pool) $ do
|
|||||||
|
|
||||||
it "requesting children 2 levels" $
|
it "requesting children 2 levels" $
|
||||||
get "/clients?id=eq.1&select=id,projects{id,tasks{id}}" `shouldRespondWith`
|
get "/clients?id=eq.1&select=id,projects{id,tasks{id}}" `shouldRespondWith`
|
||||||
"[{\"id\":1,\"projects\":[{\"id\":1,\"tasks\":[{\"id\":1},{\"id\":2}]},{\"id\":2,\"tasks\":[{\"id\":3},{\"id\":4}]}]}]"
|
[str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|]
|
||||||
|
|
||||||
it "requesting many<->many relation" $
|
it "requesting many<->many relation" $
|
||||||
get "/tasks?select=id,users{id}" `shouldRespondWith`
|
get "/tasks?select=id,users{id}" `shouldRespondWith`
|
||||||
"[{\"id\":1,\"users\":[{\"id\":1},{\"id\":3}]},{\"id\":2,\"users\":[{\"id\":1}]},{\"id\":3,\"users\":[{\"id\":1}]},{\"id\":4,\"users\":[{\"id\":1}]},{\"id\":5,\"users\":[{\"id\":2},{\"id\":3}]},{\"id\":6,\"users\":[{\"id\":2}]},{\"id\":7,\"users\":[{\"id\":2}]},{\"id\":8,\"users\":[]}]"
|
[str|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|]
|
||||||
|
|
||||||
it "requesting parents and children on views" $
|
it "requesting parents and children on views" $
|
||||||
get "/projects_view?id=eq.1&select=id, name, clients{*}, tasks{id, name}" `shouldRespondWith`
|
get "/projects_view?id=eq.1&select=id, name, clients{*}, tasks{id, name}" `shouldRespondWith`
|
||||||
"[{\"id\":1,\"name\":\"Windows 7\",\"clients\":{\"id\":1,\"name\":\"Microsoft\"},\"tasks\":[{\"id\":1,\"name\":\"Design w7\"},{\"id\":2,\"name\":\"Code w7\"}]}]"
|
[str|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|]
|
||||||
|
|
||||||
it "requesting children with composite key" $
|
it "requesting children with composite key" $
|
||||||
get "/users_tasks?user_id=eq.2&task_id=eq.6&select=*, comments{content}" `shouldRespondWith`
|
get "/users_tasks?user_id=eq.2&task_id=eq.6&select=*, comments{content}" `shouldRespondWith`
|
||||||
"[{\"user_id\":2,\"task_id\":6,\"comments\":[{\"content\":\"Needs to be delivered ASAP\"}]}]"
|
[str|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|]
|
||||||
|
|
||||||
it "detect relations in views from exposed schema that are based on tables in private schema and have columns renames" $
|
it "detect relations in views from exposed schema that are based on tables in private schema and have columns renames" $
|
||||||
get "/articles?id=eq.1&select=id,articleStars{users{*}}" `shouldRespondWith`
|
get "/articles?id=eq.1&select=id,articleStars{users{*}}" `shouldRespondWith`
|
||||||
[str|[{"id":1,"articlestars":[{"users":{"id":1,"name":"Angela Martin"}},{"users":{"id":2,"name":"Michael Scott"}},{"users":{"id":3,"name":"Dwight Schrute"}}]}]|]
|
[str|[{"id":1,"articleStars":[{"users":{"id":1,"name":"Angela Martin"}},{"users":{"id":2,"name":"Michael Scott"}},{"users":{"id":3,"name":"Dwight Schrute"}}]}]|]
|
||||||
|
|
||||||
|
it "can select by column name" $
|
||||||
|
get "/projects?id=in.1,3&select=id,name,client_id,client_id{id,name}" `shouldRespondWith`
|
||||||
|
[str|[{"id":1,"name":"Windows 7","client_id":1,"client_id":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client_id":{"id":2,"name":"Apple"}}]|]
|
||||||
|
|
||||||
|
it "can select by column name sans id" $
|
||||||
|
get "/projects?id=in.1,3&select=id,name,client_id,client{id,name}" `shouldRespondWith`
|
||||||
|
[str|[{"id":1,"name":"Windows 7","client_id":1,"client":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client":{"id":2,"name":"Apple"}}]|]
|
||||||
|
|
||||||
|
|
||||||
describe "Plurality singular" $ do
|
describe "Plurality singular" $ do
|
||||||
@@ -253,7 +261,7 @@ spec struct pool = around (withApp cfgDefault struct pool) $ do
|
|||||||
it "can shape plurality singular object routes" $
|
it "can shape plurality singular object routes" $
|
||||||
request methodGet "/projects_view?id=eq.1&select=id,name,clients{*},tasks{id,name}" [("Prefer","plurality=singular")] ""
|
request methodGet "/projects_view?id=eq.1&select=id,name,clients{*},tasks{id,name}" [("Prefer","plurality=singular")] ""
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
"{\"id\":1,\"name\":\"Windows 7\",\"clients\":{\"id\":1,\"name\":\"Microsoft\"},\"tasks\":[{\"id\":1,\"name\":\"Design w7\"},{\"id\":2,\"name\":\"Code w7\"}]}"
|
[str|{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}|]
|
||||||
|
|
||||||
|
|
||||||
describe "ordering response" $ do
|
describe "ordering response" $ do
|
||||||
|
|||||||
Reference in New Issue
Block a user