Fix #411, remove the need for pk in &select for parent embed
This commit is contained in:
committed by
Steve Chávez
parent
be630aa680
commit
fef7d949d9
@@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- #996, Fix embedded column conflicts table name - @grotsev
|
- #996, Fix embedded column conflicts table name - @grotsev
|
||||||
- #974, Fix RPC error when function has single OUT param - @steve-chavez
|
- #974, Fix RPC error when function has single OUT param - @steve-chavez
|
||||||
- #1021, Reduce join size in allColumns for faster program start - @nextstopsun
|
- #1021, Reduce join size in allColumns for faster program start - @nextstopsun
|
||||||
|
- #411, Remove the need for pk in &select for parent embed - @steve-chavez
|
||||||
|
|
||||||
## [0.4.3.0] - 2017-09-06
|
## [0.4.3.0] - 2017-09-06
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import PostgREST.ApiRequest ( ApiRequest(..)
|
|||||||
import PostgREST.Error (apiRequestError)
|
import PostgREST.Error (apiRequestError)
|
||||||
import PostgREST.Parsers
|
import PostgREST.Parsers
|
||||||
import PostgREST.RangeQuery (NonnegRange, restrictRange)
|
import PostgREST.RangeQuery (NonnegRange, restrictRange)
|
||||||
import PostgREST.QueryBuilder (getJoinFilters, sourceCTEName)
|
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
|
|
||||||
import Protolude hiding (from, dropWhile, drop)
|
import Protolude hiding (from, dropWhile, drop)
|
||||||
@@ -194,7 +193,7 @@ addJoinFilters :: Schema -> ReadRequest -> Either ApiRequestError ReadRequest
|
|||||||
addJoinFilters schema (Node node@(query, nodeProps@(_, relation, _, _)) forest) =
|
addJoinFilters schema (Node node@(query, nodeProps@(_, relation, _, _)) forest) =
|
||||||
case relation of
|
case relation of
|
||||||
Just Relation{relType=Root} -> Node node <$> updatedForest -- this is the root node
|
Just Relation{relType=Root} -> Node node <$> updatedForest -- this is the root node
|
||||||
Just Relation{relType=Parent} -> Node node <$> updatedForest
|
Just rel@Relation{relType=Parent} -> Node (augmentQuery rel, nodeProps) <$> updatedForest
|
||||||
Just rel@Relation{relType=Child} -> Node (augmentQuery rel, nodeProps) <$> updatedForest
|
Just rel@Relation{relType=Child} -> Node (augmentQuery rel, nodeProps) <$> updatedForest
|
||||||
Just rel@Relation{relType=Many, relLTable=(Just linkTable)} ->
|
Just rel@Relation{relType=Many, relLTable=(Just linkTable)} ->
|
||||||
let rq = augmentQuery rel in
|
let rq = augmentQuery rel in
|
||||||
@@ -205,6 +204,21 @@ addJoinFilters schema (Node node@(query, nodeProps@(_, relation, _, _)) forest)
|
|||||||
augmentQuery rel = foldr addFilterToReadQuery query (getJoinFilters rel)
|
augmentQuery rel = foldr addFilterToReadQuery query (getJoinFilters rel)
|
||||||
addFilterToReadQuery flt rq@Select{where_=lf} = rq{where_=addFilterToLogicForest flt lf}::ReadQuery
|
addFilterToReadQuery flt rq@Select{where_=lf} = rq{where_=addFilterToLogicForest flt lf}::ReadQuery
|
||||||
|
|
||||||
|
getJoinFilters :: Relation -> [Filter]
|
||||||
|
getJoinFilters (Relation t cols ft fcs typ lt lc1 lc2) =
|
||||||
|
case typ of
|
||||||
|
Child -> zipWith (toFilter tN ftN) cols fcs
|
||||||
|
Parent -> zipWith (toFilter tN ftN) cols fcs
|
||||||
|
Many -> zipWith (toFilter tN ltN) cols (fromMaybe [] lc1) ++ zipWith (toFilter ftN ltN) fcs (fromMaybe [] lc2)
|
||||||
|
Root -> undefined --error "undefined getJoinFilters"
|
||||||
|
where
|
||||||
|
s = if typ == Parent then "" else tableSchema t
|
||||||
|
tN = tableName t
|
||||||
|
ftN = tableName ft
|
||||||
|
ltN = fromMaybe "" (tableName <$> lt)
|
||||||
|
toFilter :: Text -> Text -> Column -> Column -> Filter
|
||||||
|
toFilter tb ftb c fc = Filter (colName c, Nothing) (OpExpr False (Join (QualifiedIdentifier s tb) (ForeignKey fc{colTable=(colTable fc){tableName=ftb}})))
|
||||||
|
|
||||||
addFiltersOrdersRanges :: ApiRequest -> Either ApiRequestError (ReadRequest -> ReadRequest)
|
addFiltersOrdersRanges :: ApiRequest -> Either ApiRequestError (ReadRequest -> ReadRequest)
|
||||||
addFiltersOrdersRanges apiRequest = foldr1 (liftA2 (.)) [
|
addFiltersOrdersRanges apiRequest = foldr1 (liftA2 (.)) [
|
||||||
flip (foldr addFilter) <$> filters,
|
flip (foldr addFilter) <$> filters,
|
||||||
|
|||||||
@@ -14,12 +14,10 @@ module PostgREST.QueryBuilder (
|
|||||||
callProc
|
callProc
|
||||||
, createReadStatement
|
, createReadStatement
|
||||||
, createWriteStatement
|
, createWriteStatement
|
||||||
, getJoinFilters
|
|
||||||
, pgFmtIdent
|
, pgFmtIdent
|
||||||
, pgFmtLit
|
, pgFmtLit
|
||||||
, requestToQuery
|
, requestToQuery
|
||||||
, requestToCountQuery
|
, requestToCountQuery
|
||||||
, sourceCTEName
|
|
||||||
, unquoted
|
, unquoted
|
||||||
, ResultsWithCount
|
, ResultsWithCount
|
||||||
, pgFmtEnvVar
|
, pgFmtEnvVar
|
||||||
@@ -255,15 +253,12 @@ requestToQuery schema isParent (DbRead (Node (Select colSelects tbls logicForest
|
|||||||
<> "FROM (" <> subquery <> ") " <> pgFmtIdent table
|
<> "FROM (" <> subquery <> ") " <> pgFmtIdent table
|
||||||
<> "), '[]') AS " <> pgFmtIdent (fromMaybe name alias)
|
<> "), '[]') AS " <> pgFmtIdent (fromMaybe name alias)
|
||||||
where subquery = requestToQuery schema False (DbRead (Node n forst))
|
where subquery = requestToQuery schema False (DbRead (Node n forst))
|
||||||
getQueryParts (Node n@(_, (name, Just r@Relation{relType=Parent,relTable=Table{tableName=table}}, alias, _)) forst) (j,s) = (joi:j,sel:s)
|
getQueryParts (Node n@(_, (name, Just Relation{relType=Parent,relTable=Table{tableName=table}}, alias, _)) forst) (j,s) = (joi:j,sel:s)
|
||||||
where
|
where
|
||||||
node_name = fromMaybe name alias
|
aliasOrName = fromMaybe name alias
|
||||||
local_table_name = table <> "_" <> node_name
|
localTableName = pgFmtIdent $ table <> "_" <> aliasOrName
|
||||||
replaceTableName localTableName (Filter a (OpExpr b (Join (QualifiedIdentifier "" _) c))) = Filter a (OpExpr b (Join (QualifiedIdentifier "" localTableName) c))
|
sel = "row_to_json(" <> localTableName <> ".*) AS " <> pgFmtIdent aliasOrName
|
||||||
replaceTableName _ x = x
|
joi = " LEFT JOIN LATERAL( " <> subquery <> " ) AS " <> localTableName <> " ON TRUE "
|
||||||
sel = "row_to_json(" <> pgFmtIdent local_table_name <> ".*) AS " <> pgFmtIdent node_name
|
|
||||||
joi = " LEFT OUTER JOIN ( " <> subquery <> " ) AS " <> pgFmtIdent local_table_name <>
|
|
||||||
" ON " <> intercalate " AND " ( map (pgFmtFilter qi . replaceTableName local_table_name) (getJoinFilters r) )
|
|
||||||
where subquery = requestToQuery schema True (DbRead (Node n forst))
|
where subquery = requestToQuery schema True (DbRead (Node n forst))
|
||||||
getQueryParts (Node n@(_, (name, Just Relation{relType=Many,relTable=Table{tableName=table}}, alias, _)) forst) (j,s) = (j,sel:s)
|
getQueryParts (Node n@(_, (name, Just Relation{relType=Many,relTable=Table{tableName=table}}, alias, _)) forst) (j,s) = (j,sel:s)
|
||||||
where
|
where
|
||||||
@@ -315,9 +310,6 @@ requestToQuery schema _ (DbMutate (Delete mainTbl logicForest returnings)) =
|
|||||||
("RETURNING " <> intercalate ", " (map (pgFmtColumn qi) returnings)) `emptyOnFalse` null returnings
|
("RETURNING " <> intercalate ", " (map (pgFmtColumn qi) returnings)) `emptyOnFalse` null returnings
|
||||||
]
|
]
|
||||||
|
|
||||||
sourceCTEName :: SqlFragment
|
|
||||||
sourceCTEName = "pg_source"
|
|
||||||
|
|
||||||
removeSourceCTESchema :: Schema -> TableName -> QualifiedIdentifier
|
removeSourceCTESchema :: Schema -> TableName -> QualifiedIdentifier
|
||||||
removeSourceCTESchema schema tbl = QualifiedIdentifier (if tbl == sourceCTEName then "" else schema) tbl
|
removeSourceCTESchema schema tbl = QualifiedIdentifier (if tbl == sourceCTEName then "" else schema) tbl
|
||||||
|
|
||||||
@@ -378,21 +370,6 @@ fromQi t = (if s == "" then "" else pgFmtIdent s <> ".") <> pgFmtIdent n
|
|||||||
n = qiName t
|
n = qiName t
|
||||||
s = qiSchema t
|
s = qiSchema t
|
||||||
|
|
||||||
getJoinFilters :: Relation -> [Filter]
|
|
||||||
getJoinFilters (Relation t cols ft fcs typ lt lc1 lc2) =
|
|
||||||
case typ of
|
|
||||||
Child -> zipWith (toFilter tN ftN) cols fcs
|
|
||||||
Parent -> zipWith (toFilter tN ftN) cols fcs
|
|
||||||
Many -> zipWith (toFilter tN ltN) cols (fromMaybe [] lc1) ++ zipWith (toFilter ftN ltN) fcs (fromMaybe [] lc2)
|
|
||||||
Root -> undefined --error "undefined getJoinFilters"
|
|
||||||
where
|
|
||||||
s = if typ == Parent then "" else tableSchema t
|
|
||||||
tN = tableName t
|
|
||||||
ftN = tableName ft
|
|
||||||
ltN = fromMaybe "" (tableName <$> lt)
|
|
||||||
toFilter :: Text -> Text -> Column -> Column -> Filter
|
|
||||||
toFilter tb ftb c fc = Filter (colName c, Nothing) (OpExpr False (Join (QualifiedIdentifier s tb) (ForeignKey fc{colTable=(colTable fc){tableName=ftb}})))
|
|
||||||
|
|
||||||
unicodeStatement :: Text -> HE.Params a -> HD.Result b -> Bool -> H.Query a b
|
unicodeStatement :: Text -> HE.Params a -> HD.Result b -> Bool -> H.Query a b
|
||||||
unicodeStatement = H.statement . T.encodeUtf8
|
unicodeStatement = H.statement . T.encodeUtf8
|
||||||
|
|
||||||
|
|||||||
@@ -277,3 +277,6 @@ data PgVersion = PgVersion {
|
|||||||
pgvNum :: Int32
|
pgvNum :: Int32
|
||||||
, pgvName :: Text
|
, pgvName :: Text
|
||||||
} deriving (Eq, Ord, Show)
|
} deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
sourceCTEName :: SqlFragment
|
||||||
|
sourceCTEName = "pg_source"
|
||||||
|
|||||||
@@ -281,6 +281,39 @@ spec = do
|
|||||||
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`
|
||||||
[str|[{"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 parent without specifying primary key" $ do
|
||||||
|
get "/projects?select=name,client{name}" `shouldRespondWith`
|
||||||
|
[json|[
|
||||||
|
{"name":"Windows 7","client":{"name": "Microsoft"}},
|
||||||
|
{"name":"Windows 10","client":{"name": "Microsoft"}},
|
||||||
|
{"name":"IOS","client":{"name": "Apple"}},
|
||||||
|
{"name":"OSX","client":{"name": "Apple"}},
|
||||||
|
{"name":"Orphan","client":null}
|
||||||
|
]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/articleStars?select=createdAt,article{owner},user{name}&limit=1" `shouldRespondWith`
|
||||||
|
[json|[{"createdAt":"2015-12-08T04:22:57.472738","article":{"owner": "postgrest_test_authenticator"},"user":{"name": "Angela Martin"}}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
it "requesting parent and renaming primary key" $
|
||||||
|
get "/projects?select=name,client{clientId:id,name}" `shouldRespondWith`
|
||||||
|
[json|[
|
||||||
|
{"name":"Windows 7","client":{"name": "Microsoft", "clientId": 1}},
|
||||||
|
{"name":"Windows 10","client":{"name": "Microsoft", "clientId": 1}},
|
||||||
|
{"name":"IOS","client":{"name": "Apple", "clientId": 2}},
|
||||||
|
{"name":"OSX","client":{"name": "Apple", "clientId": 2}},
|
||||||
|
{"name":"Orphan","client":null}
|
||||||
|
]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
it "requesting parent and specifying/renaming one key of the composite primary key" $ do
|
||||||
|
get "/comments?select=*,users_tasks{userId:user_id}" `shouldRespondWith`
|
||||||
|
[json|[{"id":1,"commenter_id":1,"user_id":2,"task_id":6,"content":"Needs to be delivered ASAP","users_tasks":{"userId": 2}}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/comments?select=*,users_tasks{taskId:task_id}" `shouldRespondWith`
|
||||||
|
[json|[{"id":1,"commenter_id":1,"user_id":2,"task_id":6,"content":"Needs to be delivered ASAP","users_tasks":{"taskId": 6}}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
it "embed data with two fk pointing to the same table" $
|
it "embed data with two fk pointing to the same table" $
|
||||||
get "/orders?id=eq.1&select=id, name, billing_address_id{id}, shipping_address_id{id}" `shouldRespondWith`
|
get "/orders?id=eq.1&select=id, name, billing_address_id{id}, shipping_address_id{id}" `shouldRespondWith`
|
||||||
[str|[{"id":1,"name":"order 1","billing_address_id":{"id":1},"shipping_address_id":{"id":2}}]|]
|
[str|[{"id":1,"name":"order 1","billing_address_id":{"id":1},"shipping_address_id":{"id":2}}]|]
|
||||||
|
|||||||
Reference in New Issue
Block a user