Add suggestions with fuzzy text search when no relationship is found (#2583)
This commit is contained in:
@@ -35,7 +35,8 @@ import PostgREST.MediaType (MediaType (..))
|
||||
import PostgREST.SchemaCache.Identifiers (FieldName,
|
||||
QualifiedIdentifier)
|
||||
import PostgREST.SchemaCache.Proc (ProcDescription (..))
|
||||
import PostgREST.SchemaCache.Relationship (Relationship)
|
||||
import PostgREST.SchemaCache.Relationship (Relationship,
|
||||
RelationshipsMap)
|
||||
|
||||
import Protolude
|
||||
|
||||
@@ -72,7 +73,7 @@ data ApiRequestError
|
||||
| InvalidRpcMethod ByteString
|
||||
| LimitNoOrderError
|
||||
| NotFound
|
||||
| NoRelBetween Text Text Text
|
||||
| NoRelBetween Text Text (Maybe Text) Text RelationshipsMap
|
||||
| NoRpc Text Text [Text] Bool MediaType Bool [QualifiedIdentifier] [ProcDescription]
|
||||
| NotEmbedded Text
|
||||
| ParseRequestError Text Text
|
||||
|
||||
+54
-5
@@ -18,6 +18,7 @@ module PostgREST.Error
|
||||
import qualified Data.Aeson as JSON
|
||||
import qualified Data.ByteString.Char8 as BS
|
||||
import qualified Data.FuzzySet as Fuzzy
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.Encoding as T
|
||||
import qualified Data.Text.Encoding.Error as T
|
||||
@@ -36,12 +37,14 @@ import PostgREST.ApiRequest.Types (ApiRequestError (..),
|
||||
import PostgREST.MediaType (MediaType (..))
|
||||
import qualified PostgREST.MediaType as MediaType
|
||||
|
||||
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
|
||||
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..),
|
||||
Schema)
|
||||
import PostgREST.SchemaCache.Proc (ProcDescription (..),
|
||||
ProcParam (..))
|
||||
import PostgREST.SchemaCache.Relationship (Cardinality (..),
|
||||
Junction (..),
|
||||
Relationship (..))
|
||||
Relationship (..),
|
||||
RelationshipsMap)
|
||||
import Protolude
|
||||
|
||||
|
||||
@@ -174,11 +177,11 @@ instance JSON.ToJSON ApiRequestError where
|
||||
"details" .= ("Only is null or not is null filters are allowed on embedded resources":: Text),
|
||||
"hint" .= JSON.Null]
|
||||
|
||||
toJSON (NoRelBetween parent child schema) = JSON.object [
|
||||
toJSON (NoRelBetween parent child embedHint schema allRels) = JSON.object [
|
||||
"code" .= SchemaCacheErrorCode00,
|
||||
"message" .= ("Could not find a relationship between '" <> parent <> "' and '" <> child <> "' in the schema cache" :: Text),
|
||||
"details" .= JSON.Null,
|
||||
"hint" .= ("Verify that '" <> parent <> "' and '" <> child <> "' exist in the schema '" <> schema <> "' and that there is a foreign key relationship between them. If a new relationship was created, try reloading the schema cache." :: Text)]
|
||||
"details" .= ("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."),
|
||||
"hint" .= noRelBetweenHint parent child schema allRels]
|
||||
|
||||
toJSON (AmbiguousRelBetween parent child rels) = JSON.object [
|
||||
"code" .= SchemaCacheErrorCode01,
|
||||
@@ -214,6 +217,52 @@ instance JSON.ToJSON ApiRequestError where
|
||||
"details" .= JSON.Null,
|
||||
"hint" .= ("Try renaming the parameters or the function itself in the database so function overloading can be resolved" :: Text)]
|
||||
|
||||
-- |
|
||||
-- If no relationship is found then:
|
||||
--
|
||||
-- Looks for parent suggestions if parent not found
|
||||
-- Looks for child suggestions if parent is found but child is not
|
||||
-- Gives no suggestions if both are found (it means that there is a problem with the embed hint)
|
||||
--
|
||||
-- >>> :set -Wno-missing-fields
|
||||
-- >>> let qi t = QualifiedIdentifier "api" t
|
||||
-- >>> let rel ft = Relationship{relForeignTable = qi ft}
|
||||
-- >>> let rels = HM.fromList [((qi "films", "api"), [rel "directors", rel "roles", rel "actors"])]
|
||||
--
|
||||
-- >>> noRelBetweenHint "film" "directors" "api" rels
|
||||
-- Just "Perhaps you meant 'films' instead of 'film'."
|
||||
--
|
||||
-- >>> noRelBetweenHint "films" "role" "api" rels
|
||||
-- Just "Perhaps you meant 'roles' instead of 'role'."
|
||||
--
|
||||
-- >>> noRelBetweenHint "films" "role" "api" rels
|
||||
-- Just "Perhaps you meant 'roles' instead of 'role'."
|
||||
--
|
||||
-- >>> noRelBetweenHint "films" "actors" "api" rels
|
||||
-- Nothing
|
||||
--
|
||||
-- >>> noRelBetweenHint "noclosealternative" "roles" "api" rels
|
||||
-- Nothing
|
||||
--
|
||||
-- >>> noRelBetweenHint "films" "noclosealternative" "api" rels
|
||||
-- Nothing
|
||||
--
|
||||
-- >>> noRelBetweenHint "films" "noclosealternative" "noclosealternative" rels
|
||||
-- Nothing
|
||||
--
|
||||
noRelBetweenHint :: Text -> Text -> Schema -> RelationshipsMap -> Maybe Text
|
||||
noRelBetweenHint parent child schema allRels = ("Perhaps you meant '" <>) <$>
|
||||
if isJust findParent
|
||||
then (<> "' instead of '" <> child <> "'.") <$> suggestChild
|
||||
else (<> "' instead of '" <> parent <> "'.") <$> suggestParent
|
||||
where
|
||||
findParent = HM.lookup (QualifiedIdentifier schema parent, schema) allRels
|
||||
fuzzySetOfParents = Fuzzy.fromList [qiName (fst p) | p <- HM.keys allRels, snd p == schema]
|
||||
fuzzySetOfChildren = Fuzzy.fromList [qiName (relForeignTable c) | c <- fromMaybe [] findParent]
|
||||
suggestParent = Fuzzy.getOne fuzzySetOfParents parent
|
||||
-- Do not give suggestion if the child is found in the relations (weight = 1.0)
|
||||
suggestChild = headMay [snd k | k <- Fuzzy.get fuzzySetOfChildren child, fst k < 1.0]
|
||||
|
||||
-- |
|
||||
-- If no function is found with the given name, it does a fuzzy search to all the functions
|
||||
-- in the same schema and shows the best match as hint.
|
||||
|
||||
@@ -208,7 +208,7 @@ getJoinConditions tblAlias parentAlias Relationship{relTable=qi,relForeignTable=
|
||||
findRel :: Schema -> RelationshipsMap -> NodeName -> NodeName -> Maybe Hint -> Either ApiRequestError Relationship
|
||||
findRel schema allRels origin target hint =
|
||||
case rels of
|
||||
[] -> Left $ NoRelBetween origin target schema
|
||||
[] -> Left $ NoRelBetween origin target hint schema allRels
|
||||
[r] -> Right r
|
||||
rs -> Left $ AmbiguousRelBetween origin target rs
|
||||
where
|
||||
|
||||
Reference in New Issue
Block a user