feat: null filters on embedded resources
This commit is contained in:
committed by
Steve Chavez
parent
52c011f896
commit
3773cce246
@@ -80,6 +80,7 @@ data ApiRequestError
|
||||
| QueryParamError QPError
|
||||
| RelatedOrderNotToOne Text Text
|
||||
| SpreadNotToOne Text Text
|
||||
| UnacceptableFilter Text
|
||||
| UnacceptableSchema [Text]
|
||||
| UnsupportedMethod ByteString
|
||||
|
||||
@@ -172,10 +173,12 @@ data LogicOperator
|
||||
| Or
|
||||
deriving Eq
|
||||
|
||||
data Filter = Filter
|
||||
data Filter
|
||||
= Filter
|
||||
{ field :: Field
|
||||
, opExpr :: OpExpr
|
||||
}
|
||||
| FilterNullEmbed Bool FieldName
|
||||
deriving (Eq)
|
||||
|
||||
data OpExpr =
|
||||
|
||||
@@ -74,6 +74,7 @@ instance PgrstError ApiRequestError where
|
||||
status QueryParamError{} = HTTP.status400
|
||||
status RelatedOrderNotToOne{} = HTTP.status400
|
||||
status SpreadNotToOne{} = HTTP.status400
|
||||
status UnacceptableFilter{} = HTTP.status400
|
||||
status UnacceptableSchema{} = HTTP.status406
|
||||
status UnsupportedMethod{} = HTTP.status405
|
||||
status LimitNoOrderError = HTTP.status400
|
||||
@@ -167,6 +168,12 @@ instance JSON.ToJSON ApiRequestError where
|
||||
"details" .= ("'" <> origin <> "' and '" <> target <> "' do not form a many-to-one or one-to-one relationship" :: Text),
|
||||
"hint" .= JSON.Null]
|
||||
|
||||
toJSON (UnacceptableFilter target) = JSON.object [
|
||||
"code" .= ApiRequestErrorCode20,
|
||||
"message" .= ("Bad operator on the '" <> target <> "' embedded resource":: Text),
|
||||
"details" .= ("Only is null or not is null filters are allowed on embedded resources":: Text),
|
||||
"hint" .= JSON.Null]
|
||||
|
||||
toJSON (NoRelBetween parent child schema) = JSON.object [
|
||||
"code" .= SchemaCacheErrorCode00,
|
||||
"message" .= ("Could not find a relationship between '" <> parent <> "' and '" <> child <> "' in the schema cache" :: Text),
|
||||
@@ -540,6 +547,7 @@ data ErrorCode
|
||||
| ApiRequestErrorCode17
|
||||
| ApiRequestErrorCode18
|
||||
| ApiRequestErrorCode19
|
||||
| ApiRequestErrorCode20
|
||||
-- Schema Cache errors
|
||||
| SchemaCacheErrorCode00
|
||||
| SchemaCacheErrorCode01
|
||||
@@ -583,6 +591,7 @@ buildErrorCode code = "PGRST" <> case code of
|
||||
ApiRequestErrorCode17 -> "117"
|
||||
ApiRequestErrorCode18 -> "118"
|
||||
ApiRequestErrorCode19 -> "119"
|
||||
ApiRequestErrorCode20 -> "120"
|
||||
|
||||
SchemaCacheErrorCode00 -> "200"
|
||||
SchemaCacheErrorCode01 -> "201"
|
||||
|
||||
@@ -97,6 +97,7 @@ readPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> Eit
|
||||
readPlan qi@QualifiedIdentifier{..} AppConfig{configDbMaxRows} SchemaCache{dbRelationships} apiRequest =
|
||||
mapLeft ApiRequestError $
|
||||
treeRestrictRange configDbMaxRows (iAction apiRequest) =<<
|
||||
addNullEmbedFilters =<<
|
||||
validateSpreadEmbeds =<<
|
||||
addRelatedOrders =<<
|
||||
addRels qiSchema (iAction apiRequest) dbRelationships Nothing =<<
|
||||
@@ -332,6 +333,25 @@ addRelatedOrders (Node rp@ReadPlan{order,from} forest) = do
|
||||
Nothing ->
|
||||
Left $ NotEmbedded otRelation
|
||||
|
||||
-- Searches for null filters on embeds, e.g. `clients` on /projects?select=*,clients()&clients=not.is.null.
|
||||
-- If these are found, it changes the filter to use the internal aggregate name(`projects_clients_1`) so the filter can succeed.
|
||||
-- It fails if operators other than is.null or not.is.null are used.
|
||||
addNullEmbedFilters :: ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
||||
addNullEmbedFilters (Node rp@ReadPlan{where_=oldLogic} forest) = do
|
||||
let readPlans = rootLabel <$> forest
|
||||
newLogic <- getFilters readPlans `traverse` oldLogic
|
||||
Node rp{ReadPlan.where_= newLogic} <$> (addNullEmbedFilters `traverse` forest)
|
||||
where
|
||||
getFilters :: [ReadPlan] -> LogicTree -> Either ApiRequestError LogicTree
|
||||
getFilters rPlans (Expr b lOp trees) = Expr b lOp <$> (getFilters rPlans `traverse` trees)
|
||||
getFilters rPlans flt@(Stmnt (Filter (fld, []) opExpr)) =
|
||||
let foundRP = find (\ReadPlan{relName, relAlias} -> Just fld `elem` [Just relName, relAlias]) rPlans in
|
||||
case (foundRP, opExpr) of
|
||||
(Just ReadPlan{relAggAlias}, OpExpr b (Is TriNull)) -> Right $ Stmnt $ FilterNullEmbed b relAggAlias
|
||||
(Just ReadPlan{relName}, _) -> Left $ UnacceptableFilter relName
|
||||
_ -> Right flt
|
||||
getFilters _ flt@(Stmnt _) = Right flt
|
||||
|
||||
addRanges :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
||||
addRanges ApiRequest{..} rReq =
|
||||
case iAction of
|
||||
|
||||
@@ -261,6 +261,7 @@ pgFmtOrderTerm qi ot =
|
||||
|
||||
|
||||
pgFmtFilter :: QualifiedIdentifier -> Filter -> SQL.Snippet
|
||||
pgFmtFilter _ (FilterNullEmbed hasNot fld) = SQL.sql (pgFmtIdent fld) <> " IS " <> (if hasNot then "NOT" else mempty) <> " NULL"
|
||||
pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper of
|
||||
Op op val -> pgFmtFieldOp op <> " " <> case op of
|
||||
OpLike -> unknownLiteral (T.map star val)
|
||||
|
||||
Reference in New Issue
Block a user