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
|
||||
- #974, Fix RPC error when function has single OUT param - @steve-chavez
|
||||
- #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
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import PostgREST.ApiRequest ( ApiRequest(..)
|
||||
import PostgREST.Error (apiRequestError)
|
||||
import PostgREST.Parsers
|
||||
import PostgREST.RangeQuery (NonnegRange, restrictRange)
|
||||
import PostgREST.QueryBuilder (getJoinFilters, sourceCTEName)
|
||||
import PostgREST.Types
|
||||
|
||||
import Protolude hiding (from, dropWhile, drop)
|
||||
@@ -194,7 +193,7 @@ addJoinFilters :: Schema -> ReadRequest -> Either ApiRequestError ReadRequest
|
||||
addJoinFilters schema (Node node@(query, nodeProps@(_, relation, _, _)) forest) =
|
||||
case relation of
|
||||
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=Many, relLTable=(Just linkTable)} ->
|
||||
let rq = augmentQuery rel in
|
||||
@@ -205,6 +204,21 @@ addJoinFilters schema (Node node@(query, nodeProps@(_, relation, _, _)) forest)
|
||||
augmentQuery rel = foldr addFilterToReadQuery query (getJoinFilters rel)
|
||||
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 = foldr1 (liftA2 (.)) [
|
||||
flip (foldr addFilter) <$> filters,
|
||||
|
||||
@@ -14,12 +14,10 @@ module PostgREST.QueryBuilder (
|
||||
callProc
|
||||
, createReadStatement
|
||||
, createWriteStatement
|
||||
, getJoinFilters
|
||||
, pgFmtIdent
|
||||
, pgFmtLit
|
||||
, requestToQuery
|
||||
, requestToCountQuery
|
||||
, sourceCTEName
|
||||
, unquoted
|
||||
, ResultsWithCount
|
||||
, pgFmtEnvVar
|
||||
@@ -255,15 +253,12 @@ requestToQuery schema isParent (DbRead (Node (Select colSelects tbls logicForest
|
||||
<> "FROM (" <> subquery <> ") " <> pgFmtIdent table
|
||||
<> "), '[]') AS " <> pgFmtIdent (fromMaybe name alias)
|
||||
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
|
||||
node_name = fromMaybe name alias
|
||||
local_table_name = table <> "_" <> node_name
|
||||
replaceTableName localTableName (Filter a (OpExpr b (Join (QualifiedIdentifier "" _) c))) = Filter a (OpExpr b (Join (QualifiedIdentifier "" localTableName) c))
|
||||
replaceTableName _ x = x
|
||||
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) )
|
||||
aliasOrName = fromMaybe name alias
|
||||
localTableName = pgFmtIdent $ table <> "_" <> aliasOrName
|
||||
sel = "row_to_json(" <> localTableName <> ".*) AS " <> pgFmtIdent aliasOrName
|
||||
joi = " LEFT JOIN LATERAL( " <> subquery <> " ) AS " <> localTableName <> " ON TRUE "
|
||||
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)
|
||||
where
|
||||
@@ -315,9 +310,6 @@ requestToQuery schema _ (DbMutate (Delete mainTbl logicForest returnings)) =
|
||||
("RETURNING " <> intercalate ", " (map (pgFmtColumn qi) returnings)) `emptyOnFalse` null returnings
|
||||
]
|
||||
|
||||
sourceCTEName :: SqlFragment
|
||||
sourceCTEName = "pg_source"
|
||||
|
||||
removeSourceCTESchema :: Schema -> TableName -> QualifiedIdentifier
|
||||
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
|
||||
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 = H.statement . T.encodeUtf8
|
||||
|
||||
|
||||
@@ -277,3 +277,6 @@ data PgVersion = PgVersion {
|
||||
pgvNum :: Int32
|
||||
, pgvName :: Text
|
||||
} 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`
|
||||
[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" $
|
||||
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}}]|]
|
||||
|
||||
Reference in New Issue
Block a user