refactor: separate SchemaCacheError from ApiRequestError (#4010)

This commit is contained in:
Taimoor Zaeem
2025-04-13 10:34:17 -05:00
committed by GitHub
parent a6e81a5241
commit a1009d1bae
3 changed files with 109 additions and 84 deletions
+65 -40
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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