refactor: add the returningCols function
* separate fieldNames from getting fkCols * Put binaryField inside readSqlParts * Move scalar proc logic to binaryField * Move logic for the "SELECT *" default to DbRequestBuilder
This commit is contained in:
committed by
Steve Chávez
parent
0183d32c7f
commit
f080159268
@@ -104,7 +104,7 @@ data ApiRequest = ApiRequest {
|
|||||||
-- | &and and &or parameters used for complex boolean logic
|
-- | &and and &or parameters used for complex boolean logic
|
||||||
, iLogic :: [(Text, Text)]
|
, iLogic :: [(Text, Text)]
|
||||||
-- | &select parameter used to shape the response
|
-- | &select parameter used to shape the response
|
||||||
, iSelect :: Text
|
, iSelect :: Maybe Text
|
||||||
-- | &columns parameter used to shape the payload
|
-- | &columns parameter used to shape the payload
|
||||||
, iColumns :: Maybe Text
|
, iColumns :: Maybe Text
|
||||||
-- | &order parameters for each level
|
-- | &order parameters for each level
|
||||||
@@ -145,7 +145,7 @@ userApiRequest schema rootSpec req reqBody
|
|||||||
| otherwise -> Nothing
|
| otherwise -> Nothing
|
||||||
, iFilters = filters
|
, iFilters = filters
|
||||||
, iLogic = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["and", "or"] k ]
|
, iLogic = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["and", "or"] k ]
|
||||||
, iSelect = toS $ fromMaybe "*" $ join $ lookup "select" qParams
|
, iSelect = toS <$> join (lookup "select" qParams)
|
||||||
, iColumns = columns
|
, iColumns = columns
|
||||||
, iOrder = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["order"] k ]
|
, iOrder = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["order"] k ]
|
||||||
, iCanonicalQS = toS $ urlEncodeVars
|
, iCanonicalQS = toS $ urlEncodeVars
|
||||||
|
|||||||
+43
-39
@@ -55,10 +55,10 @@ import PostgREST.Error (PgError (..), SimpleError (..),
|
|||||||
import PostgREST.Middleware
|
import PostgREST.Middleware
|
||||||
import PostgREST.OpenAPI
|
import PostgREST.OpenAPI
|
||||||
import PostgREST.Parsers (pRequestColumns)
|
import PostgREST.Parsers (pRequestColumns)
|
||||||
import PostgREST.QueryBuilder (limitedQuery,
|
import PostgREST.QueryBuilder (limitedQuery, mutateRequestToQuery,
|
||||||
requestToCallProcQuery,
|
|
||||||
readRequestToCountQuery,
|
readRequestToCountQuery,
|
||||||
readRequestToQuery, mutateRequestToQuery)
|
readRequestToQuery,
|
||||||
|
requestToCallProcQuery)
|
||||||
import PostgREST.RangeQuery (allRange, contentRangeH,
|
import PostgREST.RangeQuery (allRange, contentRangeH,
|
||||||
rangeStatusHeader)
|
rangeStatusHeader)
|
||||||
import PostgREST.Statements (callProcStatement,
|
import PostgREST.Statements (callProcStatement,
|
||||||
@@ -119,17 +119,16 @@ transactionMode proc action =
|
|||||||
|
|
||||||
app :: DbStructure -> Maybe ProcDescription -> S.Set FieldName -> AppConfig -> ApiRequest -> H.Transaction Response
|
app :: DbStructure -> Maybe ProcDescription -> S.Set FieldName -> AppConfig -> ApiRequest -> H.Transaction Response
|
||||||
app dbStructure proc cols conf apiRequest =
|
app dbStructure proc cols conf apiRequest =
|
||||||
|
let rawContentTypes = (decodeContentType <$> configRawMediaTypes conf) `L.union` [ CTOctetStream, CTTextPlain ] in
|
||||||
case responseContentTypeOrError (iAccepts apiRequest) rawContentTypes (iAction apiRequest) (iTarget apiRequest) of
|
case responseContentTypeOrError (iAccepts apiRequest) rawContentTypes (iAction apiRequest) (iTarget apiRequest) of
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right contentType ->
|
Right contentType ->
|
||||||
case (iAction apiRequest, iTarget apiRequest, iPayload apiRequest) of
|
case (iAction apiRequest, iTarget apiRequest, iPayload apiRequest) of
|
||||||
|
|
||||||
(ActionRead headersOnly, TargetIdent (QualifiedIdentifier _ tName), Nothing) ->
|
(ActionRead headersOnly, TargetIdent (QualifiedIdentifier _ tName), Nothing) ->
|
||||||
let partsField = (,) <$> readSqlParts tName
|
case readSqlParts tName of
|
||||||
<*> (binaryField contentType rawContentTypes =<< fldNames tName) in
|
|
||||||
case partsField of
|
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right ((q, cq), bField) -> do
|
Right (q, cq, bField) -> do
|
||||||
let cQuery = if estimatedCount
|
let cQuery = if estimatedCount
|
||||||
then limitedQuery cq ((+ 1) <$> maxRows) -- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed
|
then limitedQuery cq ((+ 1) <$> maxRows) -- LIMIT maxRows + 1 so we can determine below that maxRows was surpassed
|
||||||
else cq
|
else cq
|
||||||
@@ -283,15 +282,10 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
return $ responseLBS status200 [allOrigins, allowH] mempty
|
return $ responseLBS status200 [allOrigins, allowH] mempty
|
||||||
|
|
||||||
(ActionInvoke invMethod, TargetProc qi@(QualifiedIdentifier _ pName) _, Just pJson) ->
|
(ActionInvoke invMethod, TargetProc qi@(QualifiedIdentifier _ pName) _, Just pJson) ->
|
||||||
let returnsScalar = maybe False procReturnsScalar proc
|
let tName = fromMaybe pName $ procTableName =<< proc in
|
||||||
tName = fromMaybe pName $ procTableName =<< proc
|
case readSqlParts tName of
|
||||||
rpcBinaryField = if returnsScalar
|
|
||||||
then Right Nothing
|
|
||||||
else binaryField contentType rawContentTypes =<< fldNames tName
|
|
||||||
parts = (,) <$> readSqlParts tName <*> rpcBinaryField in
|
|
||||||
case parts of
|
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right ((q, cq), bField) -> do
|
Right (q, cq, bField) -> do
|
||||||
let
|
let
|
||||||
preferParams = iPreferParameters apiRequest
|
preferParams = iPreferParameters apiRequest
|
||||||
pq = requestToCallProcQuery qi (specifiedProcArgs cols proc) returnsScalar preferParams
|
pq = requestToCallProcQuery qi (specifiedProcArgs cols proc) returnsScalar preferParams
|
||||||
@@ -328,28 +322,35 @@ app dbStructure proc cols conf apiRequest =
|
|||||||
|
|
||||||
_ -> return notFound
|
_ -> return notFound
|
||||||
|
|
||||||
where
|
where
|
||||||
notFound = responseLBS status404 [] ""
|
notFound = responseLBS status404 [] ""
|
||||||
schema = toS $ configSchema conf
|
schema = toS $ configSchema conf
|
||||||
maxRows = configMaxRows conf
|
maxRows = configMaxRows conf
|
||||||
exactCount = iPreferCount apiRequest == Just ExactCount
|
exactCount = iPreferCount apiRequest == Just ExactCount
|
||||||
estimatedCount = iPreferCount apiRequest == Just EstimatedCount
|
estimatedCount = iPreferCount apiRequest == Just EstimatedCount
|
||||||
plannedCount = iPreferCount apiRequest == Just PlannedCount
|
plannedCount = iPreferCount apiRequest == Just PlannedCount
|
||||||
shouldCount = exactCount || estimatedCount
|
shouldCount = exactCount || estimatedCount
|
||||||
topLevelRange = iTopLevelRange apiRequest
|
topLevelRange = iTopLevelRange apiRequest
|
||||||
readReq tableName = readRequest schema tableName maxRows (dbRelations dbStructure) apiRequest
|
returnsScalar = maybe False procReturnsScalar proc
|
||||||
fldNames tableName = fieldNames <$> readReq tableName
|
|
||||||
readReqst tableName = readReq tableName
|
selectQuery = readRequestToQuery schema False
|
||||||
selectQuery tableName = readRequestToQuery schema False <$> readReqst tableName
|
countQuery = readRequestToCountQuery schema
|
||||||
countQuery tableName = readRequestToCountQuery schema <$> readReqst tableName
|
readSqlParts tableName =
|
||||||
readSqlParts tableName = (,) <$> selectQuery tableName <*> countQuery tableName
|
let
|
||||||
mutationRequest s t = mutateRequest apiRequest t cols (tablePKCols dbStructure s t) =<< fldNames t
|
readReq = readRequest schema tableName maxRows (dbRelations dbStructure) apiRequest
|
||||||
mutateSqlParts s t =
|
in
|
||||||
(,) <$> selectQuery t
|
(,,) <$>
|
||||||
<*> (mutateRequestToQuery schema <$> mutationRequest s t)
|
(selectQuery <$> readReq) <*>
|
||||||
rawContentTypes =
|
(countQuery <$> readReq) <*>
|
||||||
(decodeContentType <$> configRawMediaTypes conf) `L.union`
|
(binaryField contentType rawContentTypes returnsScalar =<< readReq)
|
||||||
[ CTOctetStream, CTTextPlain ]
|
mutateSqlParts s t =
|
||||||
|
let
|
||||||
|
readReq = readRequest s t maxRows (dbRelations dbStructure) apiRequest
|
||||||
|
mutReq = mutateRequest apiRequest t cols (tablePKCols dbStructure s t) =<< readReq
|
||||||
|
in
|
||||||
|
(,) <$>
|
||||||
|
(selectQuery <$> readReq) <*>
|
||||||
|
(mutateRequestToQuery s <$> mutReq)
|
||||||
|
|
||||||
responseContentTypeOrError :: [ContentType] -> [ContentType] -> Action -> Target -> Either Response ContentType
|
responseContentTypeOrError :: [ContentType] -> [ContentType] -> Action -> Target -> Either Response ContentType
|
||||||
responseContentTypeOrError accepts rawContentTypes action target = serves contentTypesForRequest accepts
|
responseContentTypeOrError accepts rawContentTypes action target = serves contentTypesForRequest accepts
|
||||||
@@ -375,14 +376,17 @@ responseContentTypeOrError accepts rawContentTypes action target = serves conten
|
|||||||
| If raw(binary) output is requested, check that ContentType is one of the admitted rawContentTypes and that
|
| If raw(binary) output is requested, check that ContentType is one of the admitted rawContentTypes and that
|
||||||
| `?select=...` contains only one field other than `*`
|
| `?select=...` contains only one field other than `*`
|
||||||
-}
|
-}
|
||||||
binaryField :: ContentType -> [ContentType]-> [FieldName] -> Either Response (Maybe FieldName)
|
binaryField :: ContentType -> [ContentType] -> Bool -> ReadRequest -> Either Response (Maybe FieldName)
|
||||||
binaryField ct rawContentTypes fldNames
|
binaryField ct rawContentTypes isScalarProc readReq
|
||||||
|
| isScalarProc = Right Nothing
|
||||||
| ct `elem` rawContentTypes =
|
| ct `elem` rawContentTypes =
|
||||||
let fieldName = headMay fldNames in
|
let fieldName = headMay fldNames in
|
||||||
if length fldNames == 1 && fieldName /= Just "*"
|
if length fldNames == 1 && fieldName /= Just "*"
|
||||||
then Right fieldName
|
then Right fieldName
|
||||||
else Left . errorResponseFor $ BinaryFieldError ct
|
else Left . errorResponseFor $ BinaryFieldError ct
|
||||||
| otherwise = Right Nothing
|
| otherwise = Right Nothing
|
||||||
|
where
|
||||||
|
fldNames = fstFieldNames readReq
|
||||||
|
|
||||||
locationH :: TableName -> [BS.ByteString] -> Header
|
locationH :: TableName -> [BS.ByteString] -> Header
|
||||||
locationH tName fields =
|
locationH tName fields =
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ readRequest schema rootTableName maxRows allRels apiRequest =
|
|||||||
treeRestrictRange maxRows =<<
|
treeRestrictRange maxRows =<<
|
||||||
augumentRequestWithJoin schema rootRels =<<
|
augumentRequestWithJoin schema rootRels =<<
|
||||||
addFiltersOrdersRanges apiRequest <*>
|
addFiltersOrdersRanges apiRequest <*>
|
||||||
(initReadRequest rootName <$> pRequestSelect (iSelect apiRequest))
|
(initReadRequest rootName <$> pRequestSelect sel)
|
||||||
where
|
where
|
||||||
|
sel = fromMaybe "*" $ iSelect apiRequest -- default to all columns requested (SELECT *) for a non existent ?select querystring param
|
||||||
(rootName, rootRels) = rootWithRelations rootTableName allRels (iAction apiRequest)
|
(rootName, rootRels) = rootWithRelations rootTableName allRels (iAction apiRequest)
|
||||||
|
|
||||||
-- Get the root table name with its relations according to the Action type.
|
-- Get the root table name with its relations according to the Action type.
|
||||||
@@ -296,8 +297,8 @@ addProperty f (targetNodeName:remainingPath, a) (Node rn forest) =
|
|||||||
where
|
where
|
||||||
pathNode = find (\(Node (_,(nodeName,_,alias,_,_)) _) -> nodeName == targetNodeName || alias == Just targetNodeName) forest
|
pathNode = find (\(Node (_,(nodeName,_,alias,_,_)) _) -> nodeName == targetNodeName || alias == Just targetNodeName) forest
|
||||||
|
|
||||||
mutateRequest :: ApiRequest -> TableName -> S.Set FieldName -> [FieldName] -> [FieldName] -> Either Response MutateRequest
|
mutateRequest :: ApiRequest -> TableName -> S.Set FieldName -> [FieldName] -> ReadRequest -> Either Response MutateRequest
|
||||||
mutateRequest apiRequest tName cols pkCols fldNames = mapLeft errorResponseFor $
|
mutateRequest apiRequest tName cols pkCols readReq = mapLeft errorResponseFor $
|
||||||
case action of
|
case action of
|
||||||
ActionCreate -> Right $ Insert tName cols ((,) <$> iPreferResolution apiRequest <*> Just pkCols) [] returnings
|
ActionCreate -> Right $ Insert tName cols ((,) <$> iPreferResolution apiRequest <*> Just pkCols) [] returnings
|
||||||
ActionUpdate -> Update tName cols <$> combinedLogic <*> pure returnings
|
ActionUpdate -> Update tName cols <$> combinedLogic <*> pure returnings
|
||||||
@@ -316,7 +317,10 @@ mutateRequest apiRequest tName cols pkCols fldNames = mapLeft errorResponseFor $
|
|||||||
_ -> Left UnsupportedVerb
|
_ -> Left UnsupportedVerb
|
||||||
where
|
where
|
||||||
action = iAction apiRequest
|
action = iAction apiRequest
|
||||||
returnings = if iPreferRepresentation apiRequest == None then [] else fldNames
|
returnings =
|
||||||
|
if iPreferRepresentation apiRequest == None
|
||||||
|
then []
|
||||||
|
else returningCols readReq
|
||||||
filters = map snd <$> mapM pRequestFilter mutateFilters
|
filters = map snd <$> mapM pRequestFilter mutateFilters
|
||||||
logic = map snd <$> mapM pRequestLogicTree logicFilters
|
logic = map snd <$> mapM pRequestLogicTree logicFilters
|
||||||
combinedLogic = foldr addFilterToLogicForest <$> logic <*> filters
|
combinedLogic = foldr addFilterToLogicForest <$> logic <*> filters
|
||||||
@@ -324,6 +328,18 @@ mutateRequest apiRequest tName cols pkCols fldNames = mapLeft errorResponseFor $
|
|||||||
(mutateFilters, logicFilters) = join (***) onlyRoot (iFilters apiRequest, iLogic apiRequest)
|
(mutateFilters, logicFilters) = join (***) onlyRoot (iFilters apiRequest, iLogic apiRequest)
|
||||||
onlyRoot = filter (not . ( "." `isInfixOf` ) . fst)
|
onlyRoot = filter (not . ( "." `isInfixOf` ) . fst)
|
||||||
|
|
||||||
|
returningCols :: ReadRequest -> [FieldName]
|
||||||
|
returningCols rr@(Node _ forest) = fstFieldNames rr ++ (colName <$> fkCols)
|
||||||
|
where
|
||||||
|
-- Without fkCols, when a mutateRequest to /projects?select=name,clients(name) occurs, the RETURNING SQL part would be
|
||||||
|
-- `RETURNING name`(see QueryBuilder).
|
||||||
|
-- This would make the embedding fail because the following JOIN would need the "client_id" column from projects.
|
||||||
|
-- So this adds the foreign key columns to ensure the embedding succeeds, result would be `RETURNING name, client_id`.
|
||||||
|
fkCols = concat $ mapMaybe (\case
|
||||||
|
Node (_, (_, Just Relation{relFColumns=cols, relType=Parent}, _, _, _)) _ -> Just cols
|
||||||
|
_ -> Nothing
|
||||||
|
) forest
|
||||||
|
|
||||||
-- Traditional filters(e.g. id=eq.1) are added as root nodes of the LogicTree
|
-- Traditional filters(e.g. id=eq.1) are added as root nodes of the LogicTree
|
||||||
-- they are later concatenated with AND in the QueryBuilder
|
-- they are later concatenated with AND in the QueryBuilder
|
||||||
addFilterToLogicForest :: Filter -> [LogicTree] -> [LogicTree]
|
addFilterToLogicForest :: Filter -> [LogicTree] -> [LogicTree]
|
||||||
|
|||||||
@@ -434,13 +434,10 @@ type MutateRequest = MutateQuery
|
|||||||
type ReadNode = (ReadQuery, (NodeName, Maybe Relation, Maybe Alias, Maybe RelationDetail, Depth))
|
type ReadNode = (ReadQuery, (NodeName, Maybe Relation, Maybe Alias, Maybe RelationDetail, Depth))
|
||||||
type Depth = Integer
|
type Depth = Integer
|
||||||
|
|
||||||
fieldNames :: ReadRequest -> [FieldName]
|
-- First level FieldNames(e.g get a,b from /table?select=a,b,other(c,d))
|
||||||
fieldNames (Node (sel, _) forest) =
|
fstFieldNames :: ReadRequest -> [FieldName]
|
||||||
map (fst . view _1) (select sel) ++ map colName fks
|
fstFieldNames (Node (sel, _) _) =
|
||||||
where
|
fst . view _1 <$> select sel
|
||||||
fks = concatMap (fromMaybe [] . f) forest
|
|
||||||
f (Node (_, (_, Just Relation{relFColumns=cols, relType=Parent}, _, _, _)) _) = Just cols
|
|
||||||
f _ = Nothing
|
|
||||||
|
|
||||||
data PgVersion = PgVersion {
|
data PgVersion = PgVersion {
|
||||||
pgvNum :: Int32
|
pgvNum :: Int32
|
||||||
|
|||||||
Reference in New Issue
Block a user