refactor: separate SchemaCacheError from ApiRequestError (#4010)
This commit is contained in:
+65
-40
@@ -10,6 +10,7 @@ module PostgREST.Error
|
|||||||
, ApiRequestError(..)
|
, ApiRequestError(..)
|
||||||
, QPError(..)
|
, QPError(..)
|
||||||
, RangeError(..)
|
, RangeError(..)
|
||||||
|
, SchemaCacheError(..)
|
||||||
, PgError(..)
|
, PgError(..)
|
||||||
, Error(..)
|
, Error(..)
|
||||||
, JwtError (..)
|
, JwtError (..)
|
||||||
@@ -69,16 +70,12 @@ class ErrorBody a where
|
|||||||
|
|
||||||
data ApiRequestError
|
data ApiRequestError
|
||||||
= AggregatesNotAllowed
|
= AggregatesNotAllowed
|
||||||
| AmbiguousRelBetween Text Text [Relationship]
|
|
||||||
| AmbiguousRpc [Routine]
|
|
||||||
| MediaTypeError [ByteString]
|
| MediaTypeError [ByteString]
|
||||||
| InvalidBody ByteString
|
| InvalidBody ByteString
|
||||||
| InvalidFilters
|
| InvalidFilters
|
||||||
| InvalidPreferences [ByteString]
|
| InvalidPreferences [ByteString]
|
||||||
| InvalidRange RangeError
|
| InvalidRange RangeError
|
||||||
| InvalidRpcMethod ByteString
|
| InvalidRpcMethod ByteString
|
||||||
| NoRelBetween Text Text (Maybe Text) Text RelationshipsMap
|
|
||||||
| NoRpc Text Text [Text] MediaType Bool [QualifiedIdentifier] [Routine]
|
|
||||||
| NotEmbedded Text
|
| NotEmbedded Text
|
||||||
| NotImplemented Text
|
| NotImplemented Text
|
||||||
| PutLimitNotAllowedError
|
| PutLimitNotAllowedError
|
||||||
@@ -87,8 +84,6 @@ data ApiRequestError
|
|||||||
| UnacceptableFilter Text
|
| UnacceptableFilter Text
|
||||||
| UnacceptableSchema [Text]
|
| UnacceptableSchema [Text]
|
||||||
| UnsupportedMethod ByteString
|
| UnsupportedMethod ByteString
|
||||||
| ColumnNotFound Text Text
|
|
||||||
| TableNotFound Text Text [Table]
|
|
||||||
| GucHeadersError
|
| GucHeadersError
|
||||||
| GucStatusError
|
| GucStatusError
|
||||||
| PutMatchingPkError
|
| PutMatchingPkError
|
||||||
@@ -116,8 +111,6 @@ data RangeError
|
|||||||
|
|
||||||
instance PgrstError ApiRequestError where
|
instance PgrstError ApiRequestError where
|
||||||
status AggregatesNotAllowed{} = HTTP.status400
|
status AggregatesNotAllowed{} = HTTP.status400
|
||||||
status AmbiguousRelBetween{} = HTTP.status300
|
|
||||||
status AmbiguousRpc{} = HTTP.status300
|
|
||||||
status MediaTypeError{} = HTTP.status406
|
status MediaTypeError{} = HTTP.status406
|
||||||
status InvalidBody{} = HTTP.status400
|
status InvalidBody{} = HTTP.status400
|
||||||
status InvalidFilters = HTTP.status405
|
status InvalidFilters = HTTP.status405
|
||||||
@@ -125,8 +118,6 @@ instance PgrstError ApiRequestError where
|
|||||||
status InvalidRpcMethod{} = HTTP.status405
|
status InvalidRpcMethod{} = HTTP.status405
|
||||||
status InvalidRange{} = HTTP.status416
|
status InvalidRange{} = HTTP.status416
|
||||||
|
|
||||||
status NoRelBetween{} = HTTP.status400
|
|
||||||
status NoRpc{} = HTTP.status404
|
|
||||||
status NotEmbedded{} = HTTP.status400
|
status NotEmbedded{} = HTTP.status400
|
||||||
status NotImplemented{} = HTTP.status400
|
status NotImplemented{} = HTTP.status400
|
||||||
status PutLimitNotAllowedError = HTTP.status400
|
status PutLimitNotAllowedError = HTTP.status400
|
||||||
@@ -135,8 +126,6 @@ instance PgrstError ApiRequestError where
|
|||||||
status UnacceptableFilter{} = HTTP.status400
|
status UnacceptableFilter{} = HTTP.status400
|
||||||
status UnacceptableSchema{} = HTTP.status406
|
status UnacceptableSchema{} = HTTP.status406
|
||||||
status UnsupportedMethod{} = HTTP.status405
|
status UnsupportedMethod{} = HTTP.status405
|
||||||
status ColumnNotFound{} = HTTP.status400
|
|
||||||
status TableNotFound{} = HTTP.status404
|
|
||||||
status GucHeadersError = HTTP.status500
|
status GucHeadersError = HTTP.status500
|
||||||
status GucStatusError = HTTP.status500
|
status GucStatusError = HTTP.status500
|
||||||
status PutMatchingPkError = HTTP.status400
|
status PutMatchingPkError = HTTP.status400
|
||||||
@@ -192,13 +181,6 @@ instance ErrorBody ApiRequestError where
|
|||||||
code OpenAPIDisabled = "PGRST126"
|
code OpenAPIDisabled = "PGRST126"
|
||||||
code NotImplemented{} = "PGRST127"
|
code NotImplemented{} = "PGRST127"
|
||||||
|
|
||||||
code NoRelBetween{} = "PGRST200"
|
|
||||||
code AmbiguousRelBetween{} = "PGRST201"
|
|
||||||
code NoRpc{} = "PGRST202"
|
|
||||||
code AmbiguousRpc{} = "PGRST203"
|
|
||||||
code ColumnNotFound{} = "PGRST204"
|
|
||||||
code TableNotFound{} = "PGRST205"
|
|
||||||
|
|
||||||
-- MESSAGE: Text
|
-- MESSAGE: Text
|
||||||
message (QueryParamError (QPError msg _)) = msg
|
message (QueryParamError (QPError msg _)) = msg
|
||||||
message (InvalidRpcMethod method) = "Cannot use the " <> T.decodeUtf8 method <> " method on RPC"
|
message (InvalidRpcMethod method) = "Cannot use the " <> T.decodeUtf8 method <> " method on RPC"
|
||||||
@@ -224,19 +206,6 @@ instance ErrorBody ApiRequestError where
|
|||||||
message OpenAPIDisabled = "Root endpoint metadata is disabled"
|
message OpenAPIDisabled = "Root endpoint metadata is disabled"
|
||||||
message (NotImplemented _) = "Feature not implemented"
|
message (NotImplemented _) = "Feature not implemented"
|
||||||
|
|
||||||
message (NoRelBetween parent child _ _ _) = "Could not find a relationship between '" <> parent <> "' and '" <> child <> "' in the schema cache"
|
|
||||||
message (AmbiguousRelBetween parent child _) = "Could not embed because more than one relationship was found for '" <> parent <> "' and '" <> child <> "'"
|
|
||||||
message (NoRpc schema procName argumentKeys contentType isInvPost _ _) = "Could not find the function " <> func <> (if onlySingleParams then "" else fmtPrms prmsMsg) <> " in the schema cache"
|
|
||||||
where
|
|
||||||
onlySingleParams = isInvPost && contentType `elem` [MTTextPlain, MTTextXML, MTOctetStream]
|
|
||||||
func = schema <> "." <> procName
|
|
||||||
prms = T.intercalate ", " argumentKeys
|
|
||||||
prmsMsg = "(" <> prms <> ")"
|
|
||||||
fmtPrms p = if null argumentKeys then " without parameters" else p
|
|
||||||
message (AmbiguousRpc procs) = "Could not choose the best candidate function between: " <> T.intercalate ", " [pdSchema p <> "." <> pdName p <> "(" <> T.intercalate ", " [ppName a <> " => " <> ppType a | a <- pdParams p] <> ")" | p <- procs]
|
|
||||||
message (ColumnNotFound rel col) = "Could not find the '" <> col <> "' column of '" <> rel <> "' in the schema cache"
|
|
||||||
message (TableNotFound schemaName relName _) = "Could not find the table '" <> schemaName <> "." <> relName <> "' in the schema cache"
|
|
||||||
|
|
||||||
-- DETAILS: Maybe JSON.Value
|
-- DETAILS: Maybe JSON.Value
|
||||||
details (QueryParamError (QPError _ dets)) = Just $ JSON.String dets
|
details (QueryParamError (QPError _ dets)) = Just $ JSON.String dets
|
||||||
details (InvalidRange rangeError) = Just $
|
details (InvalidRange rangeError) = Just $
|
||||||
@@ -252,6 +221,58 @@ instance ErrorBody ApiRequestError where
|
|||||||
details (MaxAffectedViolationError n) = Just $ JSON.String $ T.unwords ["The query affects", show n, "rows"]
|
details (MaxAffectedViolationError n) = Just $ JSON.String $ T.unwords ["The query affects", show n, "rows"]
|
||||||
details (NotImplemented details') = Just $ JSON.String details'
|
details (NotImplemented details') = Just $ JSON.String details'
|
||||||
|
|
||||||
|
details _ = Nothing
|
||||||
|
|
||||||
|
-- HINT: Maybe JSON.Value
|
||||||
|
hint (NotEmbedded resource) = Just $ JSON.String $ "Verify that '" <> resource <> "' is included in the 'select' query parameter."
|
||||||
|
hint (PGRSTParseError raiseErr) = Just $ JSON.String $ pgrstParseErrorHint raiseErr
|
||||||
|
|
||||||
|
hint _ = Nothing
|
||||||
|
|
||||||
|
instance JSON.ToJSON ApiRequestError where
|
||||||
|
toJSON err = toJsonPgrstError
|
||||||
|
(code err) (message err) (details err) (hint err)
|
||||||
|
|
||||||
|
data SchemaCacheError
|
||||||
|
= AmbiguousRelBetween Text Text [Relationship]
|
||||||
|
| AmbiguousRpc [Routine]
|
||||||
|
| NoRelBetween Text Text (Maybe Text) Text RelationshipsMap
|
||||||
|
| NoRpc Text Text [Text] MediaType Bool [QualifiedIdentifier] [Routine]
|
||||||
|
| ColumnNotFound Text Text
|
||||||
|
| TableNotFound Text Text [Table]
|
||||||
|
deriving Show
|
||||||
|
|
||||||
|
instance PgrstError SchemaCacheError where
|
||||||
|
status AmbiguousRelBetween{} = HTTP.status300
|
||||||
|
status AmbiguousRpc{} = HTTP.status300
|
||||||
|
status NoRelBetween{} = HTTP.status400
|
||||||
|
status NoRpc{} = HTTP.status404
|
||||||
|
status ColumnNotFound{} = HTTP.status400
|
||||||
|
status TableNotFound{} = HTTP.status404
|
||||||
|
|
||||||
|
headers _ = mempty
|
||||||
|
|
||||||
|
instance ErrorBody SchemaCacheError where
|
||||||
|
code NoRelBetween{} = "PGRST200"
|
||||||
|
code AmbiguousRelBetween{} = "PGRST201"
|
||||||
|
code NoRpc{} = "PGRST202"
|
||||||
|
code AmbiguousRpc{} = "PGRST203"
|
||||||
|
code ColumnNotFound{} = "PGRST204"
|
||||||
|
code TableNotFound{} = "PGRST205"
|
||||||
|
|
||||||
|
message (NoRelBetween parent child _ _ _) = "Could not find a relationship between '" <> parent <> "' and '" <> child <> "' in the schema cache"
|
||||||
|
message (AmbiguousRelBetween parent child _) = "Could not embed because more than one relationship was found for '" <> parent <> "' and '" <> child <> "'"
|
||||||
|
message (NoRpc schema procName argumentKeys contentType isInvPost _ _) = "Could not find the function " <> func <> (if onlySingleParams then "" else fmtPrms prmsMsg) <> " in the schema cache"
|
||||||
|
where
|
||||||
|
onlySingleParams = isInvPost && contentType `elem` [MTTextPlain, MTTextXML, MTOctetStream]
|
||||||
|
func = schema <> "." <> procName
|
||||||
|
prms = T.intercalate ", " argumentKeys
|
||||||
|
prmsMsg = "(" <> prms <> ")"
|
||||||
|
fmtPrms p = if null argumentKeys then " without parameters" else p
|
||||||
|
message (AmbiguousRpc procs) = "Could not choose the best candidate function between: " <> T.intercalate ", " [pdSchema p <> "." <> pdName p <> "(" <> T.intercalate ", " [ppName a <> " => " <> ppType a | a <- pdParams p] <> ")" | p <- procs]
|
||||||
|
message (ColumnNotFound rel col) = "Could not find the '" <> col <> "' column of '" <> rel <> "' in the schema cache"
|
||||||
|
message (TableNotFound schemaName relName _) = "Could not find the table '" <> schemaName <> "." <> relName <> "' in the schema cache"
|
||||||
|
|
||||||
details (NoRelBetween parent child embedHint schema _) = Just $ JSON.String $ "Searched for a foreign key relationship between '" <> parent <> "' and '" <> child <> maybe mempty ("' using the hint '" <>) embedHint <> "' in the schema '" <> schema <> "', but no matches were found."
|
details (NoRelBetween parent child embedHint schema _) = Just $ JSON.String $ "Searched for a foreign key relationship between '" <> parent <> "' and '" <> child <> maybe mempty ("' using the hint '" <>) embedHint <> "' in the schema '" <> schema <> "', but no matches were found."
|
||||||
details (AmbiguousRelBetween _ _ rels) = Just $ JSON.toJSONList (compressedRel <$> rels)
|
details (AmbiguousRelBetween _ _ rels) = Just $ JSON.toJSONList (compressedRel <$> rels)
|
||||||
details (NoRpc schema procName argumentKeys contentType isInvPost _ _) =
|
details (NoRpc schema procName argumentKeys contentType isInvPost _ _) =
|
||||||
@@ -271,10 +292,6 @@ instance ErrorBody ApiRequestError where
|
|||||||
|
|
||||||
details _ = Nothing
|
details _ = Nothing
|
||||||
|
|
||||||
-- HINT: Maybe JSON.Value
|
|
||||||
hint (NotEmbedded resource) = Just $ JSON.String $ "Verify that '" <> resource <> "' is included in the 'select' query parameter."
|
|
||||||
hint (PGRSTParseError raiseErr) = Just $ JSON.String $ pgrstParseErrorHint raiseErr
|
|
||||||
|
|
||||||
hint (NoRelBetween parent child _ schema allRels) = JSON.String <$> noRelBetweenHint parent child schema allRels
|
hint (NoRelBetween parent child _ schema allRels) = JSON.String <$> noRelBetweenHint parent child schema allRels
|
||||||
hint (AmbiguousRelBetween _ child rels) = Just $ JSON.String $ "Try changing '" <> child <> "' to one of the following: " <> relHint rels <> ". Find the desired relationship in the 'details' key."
|
hint (AmbiguousRelBetween _ child rels) = Just $ JSON.String $ "Try changing '" <> child <> "' to one of the following: " <> relHint rels <> ". Find the desired relationship in the 'details' key."
|
||||||
-- The hint will be null in the case of single unnamed parameter functions
|
-- The hint will be null in the case of single unnamed parameter functions
|
||||||
@@ -289,8 +306,7 @@ instance ErrorBody ApiRequestError where
|
|||||||
|
|
||||||
hint _ = Nothing
|
hint _ = Nothing
|
||||||
|
|
||||||
|
instance JSON.ToJSON SchemaCacheError where
|
||||||
instance JSON.ToJSON ApiRequestError where
|
|
||||||
toJSON err = toJsonPgrstError
|
toJSON err = toJsonPgrstError
|
||||||
(code err) (message err) (details err) (hint err)
|
(code err) (message err) (details err) (hint err)
|
||||||
|
|
||||||
@@ -461,6 +477,8 @@ pgrstParseErrorHint err = case err of
|
|||||||
_ -> "DETAIL must be a JSON object with obligatory keys: 'status', 'headers' and optional key: 'status_text'."
|
_ -> "DETAIL must be a JSON object with obligatory keys: 'status', 'headers' and optional key: 'status_text'."
|
||||||
|
|
||||||
data PgError = PgError Authenticated SQL.UsageError
|
data PgError = PgError Authenticated SQL.UsageError
|
||||||
|
deriving Show
|
||||||
|
|
||||||
type Authenticated = Bool
|
type Authenticated = Bool
|
||||||
|
|
||||||
instance PgrstError PgError where
|
instance PgrstError PgError where
|
||||||
@@ -608,27 +626,30 @@ pgErrorStatus authed (SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ResultError
|
|||||||
_ -> HTTP.status500
|
_ -> HTTP.status500
|
||||||
|
|
||||||
|
|
||||||
-- TODO: separate "SchemaCacheError" from ApiRequestError similar to how we
|
|
||||||
-- group them in docs
|
|
||||||
data Error
|
data Error
|
||||||
= ApiRequestError ApiRequestError
|
= ApiRequestError ApiRequestError
|
||||||
|
| SchemaCacheErr SchemaCacheError
|
||||||
| JwtErr JwtError
|
| JwtErr JwtError
|
||||||
| NoSchemaCacheError
|
| NoSchemaCacheError
|
||||||
| PgErr PgError
|
| PgErr PgError
|
||||||
|
deriving Show
|
||||||
|
|
||||||
data JwtError
|
data JwtError
|
||||||
= JwtDecodeError Text
|
= JwtDecodeError Text
|
||||||
| JwtSecretMissing
|
| JwtSecretMissing
|
||||||
| JwtTokenRequired
|
| JwtTokenRequired
|
||||||
| JwtClaimsError Text
|
| JwtClaimsError Text
|
||||||
|
deriving Show
|
||||||
|
|
||||||
instance PgrstError Error where
|
instance PgrstError Error where
|
||||||
status (ApiRequestError err) = status err
|
status (ApiRequestError err) = status err
|
||||||
|
status (SchemaCacheErr err) = status err
|
||||||
status (JwtErr err) = status err
|
status (JwtErr err) = status err
|
||||||
status NoSchemaCacheError = HTTP.status503
|
status NoSchemaCacheError = HTTP.status503
|
||||||
status (PgErr err) = status err
|
status (PgErr err) = status err
|
||||||
|
|
||||||
headers (ApiRequestError err) = proxyStatusHeader (code err) : headers err
|
headers (ApiRequestError err) = proxyStatusHeader (code err) : headers err
|
||||||
|
headers (SchemaCacheErr err) = proxyStatusHeader (code err) : headers err
|
||||||
headers (JwtErr err) = proxyStatusHeader (code err) : headers err
|
headers (JwtErr err) = proxyStatusHeader (code err) : headers err
|
||||||
headers (PgErr err) = proxyStatusHeader (code err) : headers err
|
headers (PgErr err) = proxyStatusHeader (code err) : headers err
|
||||||
headers err@NoSchemaCacheError = proxyStatusHeader (code err) : mempty
|
headers err@NoSchemaCacheError = proxyStatusHeader (code err) : mempty
|
||||||
@@ -639,21 +660,25 @@ instance JSON.ToJSON Error where
|
|||||||
|
|
||||||
instance ErrorBody Error where
|
instance ErrorBody Error where
|
||||||
code (ApiRequestError err) = code err
|
code (ApiRequestError err) = code err
|
||||||
|
code (SchemaCacheErr err) = code err
|
||||||
code (JwtErr err) = code err
|
code (JwtErr err) = code err
|
||||||
code NoSchemaCacheError = "PGRST002"
|
code NoSchemaCacheError = "PGRST002"
|
||||||
code (PgErr err) = code err
|
code (PgErr err) = code err
|
||||||
|
|
||||||
message (ApiRequestError err) = message err
|
message (ApiRequestError err) = message err
|
||||||
|
message (SchemaCacheErr err) = message err
|
||||||
message (JwtErr err) = message err
|
message (JwtErr err) = message err
|
||||||
message NoSchemaCacheError = "Could not query the database for the schema cache. Retrying."
|
message NoSchemaCacheError = "Could not query the database for the schema cache. Retrying."
|
||||||
message (PgErr err) = message err
|
message (PgErr err) = message err
|
||||||
|
|
||||||
details (ApiRequestError err) = details err
|
details (ApiRequestError err) = details err
|
||||||
|
details (SchemaCacheErr err) = details err
|
||||||
details (JwtErr err) = details err
|
details (JwtErr err) = details err
|
||||||
details NoSchemaCacheError = Nothing
|
details NoSchemaCacheError = Nothing
|
||||||
details (PgErr err) = details err
|
details (PgErr err) = details err
|
||||||
|
|
||||||
hint (ApiRequestError err) = hint err
|
hint (ApiRequestError err) = hint err
|
||||||
|
hint (SchemaCacheErr err) = hint err
|
||||||
hint (JwtErr err) = hint err
|
hint (JwtErr err) = hint err
|
||||||
hint NoSchemaCacheError = Nothing
|
hint NoSchemaCacheError = Nothing
|
||||||
hint (PgErr err) = hint err
|
hint (PgErr err) = hint err
|
||||||
|
|||||||
+43
-43
@@ -45,7 +45,8 @@ import PostgREST.ApiRequest (Action (..),
|
|||||||
Payload (..))
|
Payload (..))
|
||||||
import PostgREST.Config (AppConfig (..))
|
import PostgREST.Config (AppConfig (..))
|
||||||
import PostgREST.Error (ApiRequestError (..),
|
import PostgREST.Error (ApiRequestError (..),
|
||||||
Error (..))
|
Error (..),
|
||||||
|
SchemaCacheError (..))
|
||||||
import PostgREST.MediaType (MediaType (..))
|
import PostgREST.MediaType (MediaType (..))
|
||||||
import PostgREST.Query.SqlFragment (sourceCTEName)
|
import PostgREST.Query.SqlFragment (sourceCTEName)
|
||||||
import PostgREST.RangeQuery (NonnegRange, allRange,
|
import PostgREST.RangeQuery (NonnegRange, allRange,
|
||||||
@@ -155,7 +156,7 @@ dbActionPlan dbAct conf apiReq sCache = case dbAct of
|
|||||||
|
|
||||||
wrappedReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> Bool -> Either Error CrudPlan
|
wrappedReadPlan :: QualifiedIdentifier -> AppConfig -> SchemaCache -> ApiRequest -> Bool -> Either Error CrudPlan
|
||||||
wrappedReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferences{..},..} headersOnly = do
|
wrappedReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferences{..},..} headersOnly = do
|
||||||
qi <- mapLeft ApiRequestError $ findTable identifier (dbTables sCache)
|
qi <- findTable identifier (dbTables sCache)
|
||||||
rPlan <- readPlan qi conf sCache apiRequest
|
rPlan <- readPlan qi conf sCache apiRequest
|
||||||
(handler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest qi iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect rPlan)
|
(handler, mediaType) <- mapLeft ApiRequestError $ negotiateContent conf apiRequest qi iAcceptMediaType (dbMediaHandlers sCache) (hasDefaultSelect rPlan)
|
||||||
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
|
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
|
||||||
@@ -163,7 +164,7 @@ wrappedReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Prefe
|
|||||||
|
|
||||||
mutateReadPlan :: Mutation -> ApiRequest -> QualifiedIdentifier -> AppConfig -> SchemaCache -> Either Error CrudPlan
|
mutateReadPlan :: Mutation -> ApiRequest -> QualifiedIdentifier -> AppConfig -> SchemaCache -> Either Error CrudPlan
|
||||||
mutateReadPlan mutation apiRequest@ApiRequest{iPreferences=Preferences{..},..} identifier conf sCache = do
|
mutateReadPlan mutation apiRequest@ApiRequest{iPreferences=Preferences{..},..} identifier conf sCache = do
|
||||||
qi <- mapLeft ApiRequestError $ findTable identifier (dbTables sCache)
|
qi <- findTable identifier (dbTables sCache)
|
||||||
rPlan <- readPlan qi conf sCache apiRequest
|
rPlan <- readPlan qi conf sCache apiRequest
|
||||||
mPlan <- mutatePlan mutation qi apiRequest sCache rPlan
|
mPlan <- mutatePlan mutation qi apiRequest sCache rPlan
|
||||||
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
|
if not (null invalidPrefs) && preferHandling == Just Strict then Left $ ApiRequestError $ InvalidPreferences invalidPrefs else Right ()
|
||||||
@@ -175,7 +176,7 @@ callReadPlan identifier conf sCache apiRequest@ApiRequest{iPreferences=Preferenc
|
|||||||
let paramKeys = case invMethod of
|
let paramKeys = case invMethod of
|
||||||
InvRead _ -> S.fromList $ fst <$> qsParams'
|
InvRead _ -> S.fromList $ fst <$> qsParams'
|
||||||
Inv -> iColumns
|
Inv -> iColumns
|
||||||
proc@Function{..} <- mapLeft ApiRequestError $
|
proc@Function{..} <- mapLeft SchemaCacheErr $
|
||||||
findProc identifier paramKeys (dbRoutines sCache) iContentMediaType (invMethod == Inv)
|
findProc identifier paramKeys (dbRoutines sCache) iContentMediaType (invMethod == Inv)
|
||||||
let relIdentifier = QualifiedIdentifier pdSchema (fromMaybe pdName $ Routine.funcTableName proc) -- done so a set returning function can embed other relations
|
let relIdentifier = QualifiedIdentifier pdSchema (fromMaybe pdName $ Routine.funcTableName proc) -- done so a set returning function can embed other relations
|
||||||
rPlan <- readPlan relIdentifier conf sCache apiRequest
|
rPlan <- readPlan relIdentifier conf sCache apiRequest
|
||||||
@@ -212,7 +213,7 @@ inspectPlan apiRequest headersOnly schema = do
|
|||||||
Search a pg proc by matching name and arguments keys to parameters. Since a function can be overloaded,
|
Search a pg proc by matching name and arguments keys to parameters. Since a function can be overloaded,
|
||||||
the name is not enough to find it. An overloaded function can have a different volatility or even a different return type.
|
the name is not enough to find it. An overloaded function can have a different volatility or even a different return type.
|
||||||
-}
|
-}
|
||||||
findProc :: QualifiedIdentifier -> S.Set Text -> RoutineMap -> MediaType -> Bool -> Either ApiRequestError Routine
|
findProc :: QualifiedIdentifier -> S.Set Text -> RoutineMap -> MediaType -> Bool -> Either SchemaCacheError Routine
|
||||||
findProc qi argumentsKeys allProcs contentMediaType isInvPost =
|
findProc qi argumentsKeys allProcs contentMediaType isInvPost =
|
||||||
case matchProc of
|
case matchProc of
|
||||||
([], []) -> Left $ NoRpc (qiSchema qi) (qiName qi) (S.toList argumentsKeys) contentMediaType isInvPost (HM.keys allProcs) lookupProcName
|
([], []) -> Left $ NoRpc (qiSchema qi) (qiName qi) (S.toList argumentsKeys) contentMediaType isInvPost (HM.keys allProcs) lookupProcName
|
||||||
@@ -337,7 +338,6 @@ readPlan qi@QualifiedIdentifier{..} AppConfig{configDbMaxRows, configDbAggregate
|
|||||||
-- JSON output format hardcoded for now. In the future we might want to support other output mappings such as CSV.
|
-- JSON output format hardcoded for now. In the future we might want to support other output mappings such as CSV.
|
||||||
ctx = ResolverContext dbTables dbRepresentations qi "json"
|
ctx = ResolverContext dbTables dbRepresentations qi "json"
|
||||||
in
|
in
|
||||||
mapLeft ApiRequestError $
|
|
||||||
treeRestrictRange configDbMaxRows (iAction apiRequest) =<<
|
treeRestrictRange configDbMaxRows (iAction apiRequest) =<<
|
||||||
addToManyOrderSelects =<<
|
addToManyOrderSelects =<<
|
||||||
hoistSpreadAggFunctions =<<
|
hoistSpreadAggFunctions =<<
|
||||||
@@ -382,7 +382,7 @@ initReadRequest ctx@ResolverContext{qi=QualifiedIdentifier{..}} =
|
|||||||
-- * A select term with a JSON path
|
-- * A select term with a JSON path
|
||||||
-- * Domain representations
|
-- * Domain representations
|
||||||
-- * Aggregates in spread relationships
|
-- * Aggregates in spread relationships
|
||||||
addAliases :: ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addAliases :: ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addAliases = Right . fmap addAliasToPlan
|
addAliases = Right . fmap addAliasToPlan
|
||||||
where
|
where
|
||||||
addAliasToPlan rp@ReadPlan{select=sel, relSpread=spr} = rp{select=map (aliasSelectField $ isJust spr) sel}
|
addAliasToPlan rp@ReadPlan{select=sel, relSpread=spr} = rp{select=map (aliasSelectField $ isJust spr) sel}
|
||||||
@@ -433,7 +433,7 @@ knownColumnsInContext ResolverContext{..} =
|
|||||||
-- * When there are data representations present.
|
-- * When there are data representations present.
|
||||||
-- * When there is an aggregate function in a given ReadPlan or its parent.
|
-- * When there is an aggregate function in a given ReadPlan or its parent.
|
||||||
-- * When the ReadPlan is a to-many spread relationship
|
-- * When the ReadPlan is a to-many spread relationship
|
||||||
expandStars :: ResolverContext -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
expandStars :: ResolverContext -> ReadPlanTree -> Either Error ReadPlanTree
|
||||||
expandStars ctx rPlanTree = Right $ expandStarsForReadPlan False rPlanTree
|
expandStars ctx rPlanTree = Right $ expandStarsForReadPlan False rPlanTree
|
||||||
where
|
where
|
||||||
expandStarsForReadPlan :: Bool -> ReadPlanTree -> ReadPlanTree
|
expandStarsForReadPlan :: Bool -> ReadPlanTree -> ReadPlanTree
|
||||||
@@ -478,7 +478,7 @@ expandStarsForTable ctx@ResolverContext{representations, outputType} hasAgg rp@R
|
|||||||
expandStarSelectField _ _ selectField = [selectField]
|
expandStarSelectField _ _ selectField = [selectField]
|
||||||
|
|
||||||
-- | Enforces the `max-rows` config on the result
|
-- | Enforces the `max-rows` config on the result
|
||||||
treeRestrictRange :: Maybe Integer -> Action -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
treeRestrictRange :: Maybe Integer -> Action -> ReadPlanTree -> Either Error ReadPlanTree
|
||||||
treeRestrictRange _ (ActDb (ActRelationMut _ _)) request = Right request
|
treeRestrictRange _ (ActDb (ActRelationMut _ _)) request = Right request
|
||||||
treeRestrictRange maxRows _ request = pure $ nodeRestrictRange maxRows <$> request
|
treeRestrictRange maxRows _ request = pure $ nodeRestrictRange maxRows <$> request
|
||||||
where
|
where
|
||||||
@@ -487,7 +487,7 @@ treeRestrictRange maxRows _ request = pure $ nodeRestrictRange maxRows <$> reque
|
|||||||
|
|
||||||
-- add relationships to the nodes of the tree by traversing the forest while keeping track of the parentNode(https://stackoverflow.com/questions/22721064/get-the-parent-of-a-node-in-data-tree-haskell#comment34627048_22721064)
|
-- add relationships to the nodes of the tree by traversing the forest while keeping track of the parentNode(https://stackoverflow.com/questions/22721064/get-the-parent-of-a-node-in-data-tree-haskell#comment34627048_22721064)
|
||||||
-- also adds aliasing
|
-- also adds aliasing
|
||||||
addRels :: Schema -> Action -> RelationshipsMap -> Maybe ReadPlanTree -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addRels :: Schema -> Action -> RelationshipsMap -> Maybe ReadPlanTree -> ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addRels schema action allRels parentNode (Node rPlan@ReadPlan{relName,relHint,relAlias,relSpread,depth} forest) =
|
addRels schema action allRels parentNode (Node rPlan@ReadPlan{relName,relHint,relAlias,relSpread,depth} forest) =
|
||||||
case parentNode of
|
case parentNode of
|
||||||
Just (Node ReadPlan{from=parentNodeQi, fromAlias=parentAlias} _) ->
|
Just (Node ReadPlan{from=parentNodeQi, fromAlias=parentAlias} _) ->
|
||||||
@@ -523,7 +523,7 @@ addRels schema action allRels parentNode (Node rPlan@ReadPlan{relName,relHint,re
|
|||||||
in
|
in
|
||||||
Node newReadPlan <$> updateForest (Just $ Node newReadPlan forest)
|
Node newReadPlan <$> updateForest (Just $ Node newReadPlan forest)
|
||||||
where
|
where
|
||||||
updateForest :: Maybe ReadPlanTree -> Either ApiRequestError [ReadPlanTree]
|
updateForest :: Maybe ReadPlanTree -> Either Error [ReadPlanTree]
|
||||||
updateForest rq = addRels schema action allRels rq `traverse` forest
|
updateForest rq = addRels schema action allRels rq `traverse` forest
|
||||||
|
|
||||||
getJoinConditions :: Maybe Alias -> Maybe Alias -> Relationship -> [JoinCondition]
|
getJoinConditions :: Maybe Alias -> Maybe Alias -> Relationship -> [JoinCondition]
|
||||||
@@ -553,12 +553,12 @@ getJoinConditions tblAlias parentAlias Relationship{relTable=qi,relForeignTable=
|
|||||||
-- request is ambiguous and we return an error. In that case the request can
|
-- request is ambiguous and we return an error. In that case the request can
|
||||||
-- be disambiguated by adding precision to the target or by using a hint:
|
-- be disambiguated by adding precision to the target or by using a hint:
|
||||||
-- /origin?select=target!hint(*). The origin can be a table or view.
|
-- /origin?select=target!hint(*). The origin can be a table or view.
|
||||||
findRel :: Schema -> RelationshipsMap -> NodeName -> NodeName -> Maybe Hint -> Either ApiRequestError Relationship
|
findRel :: Schema -> RelationshipsMap -> NodeName -> NodeName -> Maybe Hint -> Either Error Relationship
|
||||||
findRel schema allRels origin target hint =
|
findRel schema allRels origin target hint =
|
||||||
case rels of
|
case rels of
|
||||||
[] -> Left $ NoRelBetween origin target hint schema allRels
|
[] -> Left $ SchemaCacheErr $ NoRelBetween origin target hint schema allRels
|
||||||
[r] -> Right r
|
[r] -> Right r
|
||||||
rs -> Left $ AmbiguousRelBetween origin target rs
|
rs -> Left $ SchemaCacheErr $ AmbiguousRelBetween origin target rs
|
||||||
where
|
where
|
||||||
matchFKSingleCol hint_ card = case card of
|
matchFKSingleCol hint_ card = case card of
|
||||||
O2M{relColumns=[(col, _)]} -> hint_ == col
|
O2M{relColumns=[(col, _)]} -> hint_ == col
|
||||||
@@ -634,7 +634,7 @@ findRel schema allRels origin target hint =
|
|||||||
) $ fromMaybe mempty $ HM.lookup (QualifiedIdentifier schema origin, schema) allRels
|
) $ fromMaybe mempty $ HM.lookup (QualifiedIdentifier schema origin, schema) allRels
|
||||||
|
|
||||||
|
|
||||||
addRelSelects :: ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addRelSelects :: ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addRelSelects node@(Node rp forest)
|
addRelSelects node@(Node rp forest)
|
||||||
| null forest = Right node
|
| null forest = Right node
|
||||||
| otherwise =
|
| otherwise =
|
||||||
@@ -699,7 +699,7 @@ generateSpreadSelectFields ReadPlan{select, relSelect} =
|
|||||||
-- No hoisting is done for to-many spreads
|
-- No hoisting is done for to-many spreads
|
||||||
type HoistedAgg = ((Alias, FieldName), (AggregateFunction, Maybe Cast, Maybe Alias))
|
type HoistedAgg = ((Alias, FieldName), (AggregateFunction, Maybe Cast, Maybe Alias))
|
||||||
|
|
||||||
hoistSpreadAggFunctions :: ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
hoistSpreadAggFunctions :: ReadPlanTree -> Either Error ReadPlanTree
|
||||||
hoistSpreadAggFunctions tree = Right $ fst $ applySpreadAggHoistingToNode tree
|
hoistSpreadAggFunctions tree = Right $ fst $ applySpreadAggHoistingToNode tree
|
||||||
|
|
||||||
applySpreadAggHoistingToNode :: ReadPlanTree -> (ReadPlanTree, [HoistedAgg])
|
applySpreadAggHoistingToNode :: ReadPlanTree -> (ReadPlanTree, [HoistedAgg])
|
||||||
@@ -761,9 +761,9 @@ hoistIntoRelSelectFields _ r = r
|
|||||||
-- * It removes the ordering done in the ReadPlan and moves it to the SpreadType.
|
-- * It removes the ordering done in the ReadPlan and moves it to the SpreadType.
|
||||||
-- We also select the ordering columns and alias them to avoid collisions. This is because it would be impossible
|
-- We also select the ordering columns and alias them to avoid collisions. This is because it would be impossible
|
||||||
-- to order once it's aggregated if it's not selected in the inner query beforehand.
|
-- to order once it's aggregated if it's not selected in the inner query beforehand.
|
||||||
addToManyOrderSelects :: ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addToManyOrderSelects :: ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addToManyOrderSelects (Node rp@ReadPlan{order, select, relAggAlias, relSelect, relSpread = Just ToManySpread {}} forest)
|
addToManyOrderSelects (Node rp@ReadPlan{order, select, relAggAlias, relSelect, relSpread = Just ToManySpread {}} forest)
|
||||||
| anyAggSel || anyAggRelSel = Left $ NotImplemented "Aggregates are not implemented for one-to-many or many-to-many spreads."
|
| anyAggSel || anyAggRelSel = Left $ ApiRequestError $ NotImplemented "Aggregates are not implemented for one-to-many or many-to-many spreads."
|
||||||
| otherwise = Node rp { order = [], relSpread = newRelSpread } <$> addToManyOrderSelects `traverse` forest
|
| otherwise = Node rp { order = [], relSpread = newRelSpread } <$> addToManyOrderSelects `traverse` forest
|
||||||
where
|
where
|
||||||
newRelSpread = Just ToManySpread { stExtraSelect = addSprExtraSelects, stOrder = addSprOrder}
|
newRelSpread = Just ToManySpread { stExtraSelect = addSprExtraSelects, stOrder = addSprOrder}
|
||||||
@@ -783,19 +783,19 @@ addToManyOrderSelects (Node rp@ReadPlan{order, select, relAggAlias, relSelect, r
|
|||||||
selOrdAlias name i = relAggAlias <> "_" <> name <> "_" <> show i -- add index to avoid collisions in aliases
|
selOrdAlias name i = relAggAlias <> "_" <> name <> "_" <> show i -- add index to avoid collisions in aliases
|
||||||
addToManyOrderSelects (Node rp forest) = Node rp <$> addToManyOrderSelects `traverse` forest
|
addToManyOrderSelects (Node rp forest) = Node rp <$> addToManyOrderSelects `traverse` forest
|
||||||
|
|
||||||
validateAggFunctions :: Bool -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
validateAggFunctions :: Bool -> ReadPlanTree -> Either Error ReadPlanTree
|
||||||
validateAggFunctions aggFunctionsAllowed (Node rp@ReadPlan {select} forest)
|
validateAggFunctions aggFunctionsAllowed (Node rp@ReadPlan {select} forest)
|
||||||
| not aggFunctionsAllowed && any (isJust . csAggFunction) select = Left AggregatesNotAllowed
|
| not aggFunctionsAllowed && any (isJust . csAggFunction) select = Left $ ApiRequestError AggregatesNotAllowed
|
||||||
| otherwise = Node rp <$> traverse (validateAggFunctions aggFunctionsAllowed) forest
|
| otherwise = Node rp <$> traverse (validateAggFunctions aggFunctionsAllowed) forest
|
||||||
|
|
||||||
-- | Lookup table in the schema cache before creating read plan
|
-- | Lookup table in the schema cache before creating read plan
|
||||||
findTable :: QualifiedIdentifier -> TablesMap -> Either ApiRequestError QualifiedIdentifier
|
findTable :: QualifiedIdentifier -> TablesMap -> Either Error QualifiedIdentifier
|
||||||
findTable qi@QualifiedIdentifier{..} tableMap =
|
findTable qi@QualifiedIdentifier{..} tableMap =
|
||||||
case HM.lookup qi tableMap of
|
case HM.lookup qi tableMap of
|
||||||
Nothing -> Left (TableNotFound qiSchema qiName (HM.elems tableMap))
|
Nothing -> Left $ SchemaCacheErr $ TableNotFound qiSchema qiName (HM.elems tableMap)
|
||||||
Just _ -> Right qi
|
Just _ -> Right qi
|
||||||
|
|
||||||
addFilters :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addFilters :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addFilters ctx ApiRequest{..} rReq =
|
addFilters ctx ApiRequest{..} rReq =
|
||||||
foldr addFilterToNode (Right rReq) flts
|
foldr addFilterToNode (Right rReq) flts
|
||||||
where
|
where
|
||||||
@@ -806,16 +806,16 @@ addFilters ctx ApiRequest{..} rReq =
|
|||||||
ActDb (ActRoutine _ _) -> qsFilters
|
ActDb (ActRoutine _ _) -> qsFilters
|
||||||
_ -> qsFiltersNotRoot
|
_ -> qsFiltersNotRoot
|
||||||
|
|
||||||
addFilterToNode :: (EmbedPath, Filter) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addFilterToNode :: (EmbedPath, Filter) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addFilterToNode =
|
addFilterToNode =
|
||||||
updateNode (\flt (Node q@ReadPlan{from=fromTable, where_=lf} f) -> Node q{ReadPlan.where_=addFilterToLogicForest (resolveFilter ctx{qi=fromTable} flt) lf} f)
|
updateNode (\flt (Node q@ReadPlan{from=fromTable, where_=lf} f) -> Node q{ReadPlan.where_=addFilterToLogicForest (resolveFilter ctx{qi=fromTable} flt) lf} f)
|
||||||
|
|
||||||
addOrders :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addOrders :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addOrders ctx ApiRequest{..} rReq = foldr addOrderToNode (Right rReq) qsOrder
|
addOrders ctx ApiRequest{..} rReq = foldr addOrderToNode (Right rReq) qsOrder
|
||||||
where
|
where
|
||||||
QueryParams.QueryParams{..} = iQueryParams
|
QueryParams.QueryParams{..} = iQueryParams
|
||||||
|
|
||||||
addOrderToNode :: (EmbedPath, [OrderTerm]) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addOrderToNode :: (EmbedPath, [OrderTerm]) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addOrderToNode = updateNode (\o (Node q f) -> Node q{order=resolveOrder ctx <$> o} f)
|
addOrderToNode = updateNode (\o (Node q f) -> Node q{order=resolveOrder ctx <$> o} f)
|
||||||
|
|
||||||
resolveOrder :: ResolverContext -> OrderTerm -> CoercibleOrderTerm
|
resolveOrder :: ResolverContext -> OrderTerm -> CoercibleOrderTerm
|
||||||
@@ -825,7 +825,7 @@ resolveOrder ctx (OrderTerm fld dir nulls) = CoercibleOrderTerm (resolveTypeOrUn
|
|||||||
-- Validates that the related resource on the order is an embedded resource,
|
-- Validates that the related resource on the order is an embedded resource,
|
||||||
-- e.g. if `clients` is inside the `select` in /projects?order=clients(id)&select=*,clients(*),
|
-- e.g. if `clients` is inside the `select` in /projects?order=clients(id)&select=*,clients(*),
|
||||||
-- and if it's a to-one relationship, it adds the right alias to the OrderRelationTerm so the generated query can succeed.
|
-- and if it's a to-one relationship, it adds the right alias to the OrderRelationTerm so the generated query can succeed.
|
||||||
addRelatedOrders :: ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addRelatedOrders :: ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addRelatedOrders (Node rp@ReadPlan{order,from} forest) = do
|
addRelatedOrders (Node rp@ReadPlan{order,from} forest) = do
|
||||||
newOrder <- newRelOrder `traverse` order
|
newOrder <- newRelOrder `traverse` order
|
||||||
Node rp{order=newOrder} <$> addRelatedOrders `traverse` forest
|
Node rp{order=newOrder} <$> addRelatedOrders `traverse` forest
|
||||||
@@ -839,9 +839,9 @@ addRelatedOrders (Node rp@ReadPlan{order,from} forest) = do
|
|||||||
name = fromMaybe relName relAlias in
|
name = fromMaybe relName relAlias in
|
||||||
if isToOne == Just True
|
if isToOne == Just True
|
||||||
then Right $ cot{coRelation=relAggAlias}
|
then Right $ cot{coRelation=relAggAlias}
|
||||||
else Left $ RelatedOrderNotToOne (qiName from) name
|
else Left $ ApiRequestError $ RelatedOrderNotToOne (qiName from) name
|
||||||
Nothing ->
|
Nothing ->
|
||||||
Left $ NotEmbedded coRelation
|
Left $ ApiRequestError $ NotEmbedded coRelation
|
||||||
|
|
||||||
-- | Searches for null filters on embeds, e.g. `projects=not.is.null` on `GET /clients?select=*,projects(*)&projects=not.is.null`
|
-- | Searches for null filters on embeds, e.g. `projects=not.is.null` on `GET /clients?select=*,projects(*)&projects=not.is.null`
|
||||||
--
|
--
|
||||||
@@ -910,13 +910,13 @@ addRelatedOrders (Node rp@ReadPlan{order,from} forest) = do
|
|||||||
--
|
--
|
||||||
-- >>> ReadPlan.where_ . rootLabel <$> addNullEmbedFilters (readPlanTree nonNullOp subForestPlan)
|
-- >>> ReadPlan.where_ . rootLabel <$> addNullEmbedFilters (readPlanTree nonNullOp subForestPlan)
|
||||||
-- Right [CoercibleStmnt (CoercibleFilterNullEmbed False "clients_projects_1")]
|
-- Right [CoercibleStmnt (CoercibleFilterNullEmbed False "clients_projects_1")]
|
||||||
addNullEmbedFilters :: ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addNullEmbedFilters :: ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addNullEmbedFilters (Node rp@ReadPlan{where_=curLogic} forest) = do
|
addNullEmbedFilters (Node rp@ReadPlan{where_=curLogic} forest) = do
|
||||||
let forestReadPlans = rootLabel <$> forest
|
let forestReadPlans = rootLabel <$> forest
|
||||||
newLogic <- newNullFilters forestReadPlans `traverse` curLogic
|
newLogic <- newNullFilters forestReadPlans `traverse` curLogic
|
||||||
Node rp{ReadPlan.where_= newLogic} <$> (addNullEmbedFilters `traverse` forest)
|
Node rp{ReadPlan.where_= newLogic} <$> (addNullEmbedFilters `traverse` forest)
|
||||||
where
|
where
|
||||||
newNullFilters :: [ReadPlan] -> CoercibleLogicTree -> Either ApiRequestError CoercibleLogicTree
|
newNullFilters :: [ReadPlan] -> CoercibleLogicTree -> Either Error CoercibleLogicTree
|
||||||
newNullFilters rPlans = \case
|
newNullFilters rPlans = \case
|
||||||
(CoercibleExpr b lOp trees) ->
|
(CoercibleExpr b lOp trees) ->
|
||||||
CoercibleExpr b lOp <$> (newNullFilters rPlans `traverse` trees)
|
CoercibleExpr b lOp <$> (newNullFilters rPlans `traverse` trees)
|
||||||
@@ -928,25 +928,25 @@ addNullEmbedFilters (Node rp@ReadPlan{where_=curLogic} forest) = do
|
|||||||
flt@(CoercibleStmnt _) ->
|
flt@(CoercibleStmnt _) ->
|
||||||
Right flt
|
Right flt
|
||||||
|
|
||||||
addRanges :: ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addRanges :: ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addRanges ApiRequest{..} rReq =
|
addRanges ApiRequest{..} rReq =
|
||||||
case iAction of
|
case iAction of
|
||||||
ActDb (ActRelationMut _ _) -> Right rReq
|
ActDb (ActRelationMut _ _) -> Right rReq
|
||||||
_ -> foldr addRangeToNode (Right rReq) =<< ranges
|
_ -> foldr addRangeToNode (Right rReq) =<< ranges
|
||||||
where
|
where
|
||||||
ranges :: Either ApiRequestError [(EmbedPath, NonnegRange)]
|
ranges :: Either Error [(EmbedPath, NonnegRange)]
|
||||||
ranges = first QueryParamError $ QueryParams.pRequestRange `traverse` HM.toList iRange
|
ranges = first (ApiRequestError . QueryParamError) $ QueryParams.pRequestRange `traverse` HM.toList iRange
|
||||||
|
|
||||||
addRangeToNode :: (EmbedPath, NonnegRange) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addRangeToNode :: (EmbedPath, NonnegRange) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addRangeToNode = updateNode (\r (Node q f) -> Node q{range_=r} f)
|
addRangeToNode = updateNode (\r (Node q f) -> Node q{range_=r} f)
|
||||||
|
|
||||||
addLogicTrees :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addLogicTrees :: ResolverContext -> ApiRequest -> ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addLogicTrees ctx ApiRequest{..} rReq =
|
addLogicTrees ctx ApiRequest{..} rReq =
|
||||||
foldr addLogicTreeToNode (Right rReq) qsLogic
|
foldr addLogicTreeToNode (Right rReq) qsLogic
|
||||||
where
|
where
|
||||||
QueryParams.QueryParams{..} = iQueryParams
|
QueryParams.QueryParams{..} = iQueryParams
|
||||||
|
|
||||||
addLogicTreeToNode :: (EmbedPath, LogicTree) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
addLogicTreeToNode :: (EmbedPath, LogicTree) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
|
||||||
addLogicTreeToNode = updateNode (\t (Node q@ReadPlan{from=fromTable, where_=lf} f) -> Node q{ReadPlan.where_=resolveLogicTree ctx{qi=fromTable} t:lf} f)
|
addLogicTreeToNode = updateNode (\t (Node q@ReadPlan{from=fromTable, where_=lf} f) -> Node q{ReadPlan.where_=resolveLogicTree ctx{qi=fromTable} t:lf} f)
|
||||||
|
|
||||||
resolveLogicTree :: ResolverContext -> LogicTree -> CoercibleLogicTree
|
resolveLogicTree :: ResolverContext -> LogicTree -> CoercibleLogicTree
|
||||||
@@ -957,12 +957,12 @@ resolveFilter :: ResolverContext -> Filter -> CoercibleFilter
|
|||||||
resolveFilter ctx (Filter fld opExpr) = CoercibleFilter{field=resolveQueryInputField ctx fld opExpr, opExpr=opExpr}
|
resolveFilter ctx (Filter fld opExpr) = CoercibleFilter{field=resolveQueryInputField ctx fld opExpr, opExpr=opExpr}
|
||||||
|
|
||||||
-- Find a Node of the Tree and apply a function to it
|
-- Find a Node of the Tree and apply a function to it
|
||||||
updateNode :: (a -> ReadPlanTree -> ReadPlanTree) -> (EmbedPath, a) -> Either ApiRequestError ReadPlanTree -> Either ApiRequestError ReadPlanTree
|
updateNode :: (a -> ReadPlanTree -> ReadPlanTree) -> (EmbedPath, a) -> Either Error ReadPlanTree -> Either Error ReadPlanTree
|
||||||
updateNode f ([], a) rr = f a <$> rr
|
updateNode f ([], a) rr = f a <$> rr
|
||||||
updateNode _ _ (Left e) = Left e
|
updateNode _ _ (Left e) = Left e
|
||||||
updateNode f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) =
|
updateNode f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) =
|
||||||
case findNode of
|
case findNode of
|
||||||
Nothing -> Left $ NotEmbedded targetNodeName
|
Nothing -> Left $ ApiRequestError $ NotEmbedded targetNodeName
|
||||||
Just target ->
|
Just target ->
|
||||||
(\node -> Node rootNode $ node : delete target forest) <$>
|
(\node -> Node rootNode $ node : delete target forest) <$>
|
||||||
updateNode f (remainingPath, a) (Right target)
|
updateNode f (remainingPath, a) (Right target)
|
||||||
@@ -971,7 +971,7 @@ updateNode f (targetNodeName:remainingPath, a) (Right (Node rootNode forest)) =
|
|||||||
findNode = find (\(Node ReadPlan{relName, relAlias} _) -> relName == targetNodeName || relAlias == Just targetNodeName) forest
|
findNode = find (\(Node ReadPlan{relName, relAlias} _) -> relName == targetNodeName || relAlias == Just targetNodeName) forest
|
||||||
|
|
||||||
mutatePlan :: Mutation -> QualifiedIdentifier -> ApiRequest -> SchemaCache -> ReadPlanTree -> Either Error MutatePlan
|
mutatePlan :: Mutation -> QualifiedIdentifier -> ApiRequest -> SchemaCache -> ReadPlanTree -> Either Error MutatePlan
|
||||||
mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{dbTables, dbRepresentations} readReq = mapLeft ApiRequestError $
|
mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{dbTables, dbRepresentations} readReq =
|
||||||
case mutation of
|
case mutation of
|
||||||
MutationCreate ->
|
MutationCreate ->
|
||||||
mapRight (\typedColumns -> Insert qi typedColumns body ((,) <$> preferResolution <*> Just confCols) [] returnings pkCols applyDefaults) typedColumnsOrError
|
mapRight (\typedColumns -> Insert qi typedColumns body ((,) <$> preferResolution <*> Just confCols) [] returnings pkCols applyDefaults) typedColumnsOrError
|
||||||
@@ -986,7 +986,7 @@ mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{
|
|||||||
_ -> False) qsFiltersRoot
|
_ -> False) qsFiltersRoot
|
||||||
then mapRight (\typedColumns -> Insert qi typedColumns body (Just (MergeDuplicates, pkCols)) combinedLogic returnings mempty False) typedColumnsOrError
|
then mapRight (\typedColumns -> Insert qi typedColumns body (Just (MergeDuplicates, pkCols)) combinedLogic returnings mempty False) typedColumnsOrError
|
||||||
else
|
else
|
||||||
Left InvalidFilters
|
Left $ ApiRequestError InvalidFilters
|
||||||
MutationDelete -> Right $ Delete qi combinedLogic returnings
|
MutationDelete -> Right $ Delete qi combinedLogic returnings
|
||||||
where
|
where
|
||||||
ctx = ResolverContext dbTables dbRepresentations qi "json"
|
ctx = ResolverContext dbTables dbRepresentations qi "json"
|
||||||
@@ -1006,9 +1006,9 @@ mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{
|
|||||||
applyDefaults = preferMissing == Just ApplyDefaults
|
applyDefaults = preferMissing == Just ApplyDefaults
|
||||||
typedColumnsOrError = resolveOrError ctx tbl `traverse` S.toList iColumns
|
typedColumnsOrError = resolveOrError ctx tbl `traverse` S.toList iColumns
|
||||||
|
|
||||||
resolveOrError :: ResolverContext -> Table -> FieldName -> Either ApiRequestError CoercibleField
|
resolveOrError :: ResolverContext -> Table -> FieldName -> Either Error CoercibleField
|
||||||
resolveOrError ctx table field = case resolveTableFieldName table field Nothing of
|
resolveOrError ctx table field = case resolveTableFieldName table field Nothing of
|
||||||
CoercibleField{cfIRType=""} -> Left $ ColumnNotFound (tableName table) field
|
CoercibleField{cfIRType=""} -> Left $ SchemaCacheErr $ ColumnNotFound (tableName table) field
|
||||||
cf -> Right $ withJsonParse ctx cf
|
cf -> Right $ withJsonParse ctx cf
|
||||||
|
|
||||||
callPlan :: Routine -> ApiRequest -> S.Set FieldName -> CallArgs -> ReadPlanTree -> CallPlan
|
callPlan :: Routine -> ApiRequest -> S.Set FieldName -> CallArgs -> ReadPlanTree -> CallPlan
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ actionResponse (MaybeDbResult InspectPlan{ipHdrsOnly=headersOnly} body) _ versio
|
|||||||
actionResponse (NoDbResult (RelInfoPlan qi@QualifiedIdentifier{..})) _ _ _ SchemaCache{dbTables} _ _ =
|
actionResponse (NoDbResult (RelInfoPlan qi@QualifiedIdentifier{..})) _ _ _ SchemaCache{dbTables} _ _ =
|
||||||
case HM.lookup qi dbTables of
|
case HM.lookup qi dbTables of
|
||||||
Just tbl -> respondInfo $ allowH tbl
|
Just tbl -> respondInfo $ allowH tbl
|
||||||
Nothing -> Left $ Error.ApiRequestError $ Error.TableNotFound qiSchema qiName (HM.elems dbTables)
|
Nothing -> Left $ Error.SchemaCacheErr $ Error.TableNotFound qiSchema qiName (HM.elems dbTables)
|
||||||
where
|
where
|
||||||
allowH table =
|
allowH table =
|
||||||
let hasPK = not . null $ tablePKCols table in
|
let hasPK = not . null $ tablePKCols table in
|
||||||
|
|||||||
Reference in New Issue
Block a user