-
-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add suggestions with fuzzy text search when no relationship is found #2583
Changes from 3 commits
d957415
d0e2ba7
51774f5
cce368e
e15459e
f6dbada
f69f314
cf1fc3e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -41,7 +42,8 @@ import PostgREST.SchemaCache.Proc (ProcDescription (..), | |
ProcParam (..)) | ||
import PostgREST.SchemaCache.Relationship (Cardinality (..), | ||
Junction (..), | ||
Relationship (..)) | ||
Relationship (..), | ||
RelationshipsMap) | ||
import Protolude | ||
|
||
|
||
|
@@ -174,11 +176,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 +216,51 @@ 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 between a parent and a child, then it looks for the parent first. | ||
-- If the parent is not found then it does a fuzzy search to all the parents in the schema cache and | ||
-- gives the best match as suggestion. Otherwise, it does a fuzzy search to all the corresponding children | ||
-- of that parent and gives the best match as suggestion. If both are found, then no suggestion is given. | ||
-- | ||
-- >>> :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 -> Text -> RelationshipsMap -> Maybe Text | ||
noRelBetweenHint parent child schema allRels = ("Perhaps you meant '" <>) <$> | ||
steve-chavez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this is expensive if Maybe this could be tested with the apflora schema, by doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In apflora,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So parent takes longer than child because it searches on the full |
||
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. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to make sure I'm understanding this.
If no rel is found:
Is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, if both are found then there's an error with the hint, which is more complex to look for.