Skip to content

Commit 715e003

Browse files
authored
Merge pull request #76 from unisoncomputing/cp/find-fixups
Some basic fixups for search
2 parents f4ab255 + 9d4077d commit 715e003

File tree

6 files changed

+136
-100
lines changed

6 files changed

+136
-100
lines changed

share-utils/src/Share/Debug.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
module Share.Debug
66
( debug,
7+
debugOther,
78
debugM,
89
whenDebug,
910
debugLog,
@@ -61,6 +62,14 @@ debug flag msg a =
6162
then (trace (msg <> ":\n" <> into @String (pShow a)) a)
6263
else a
6364

65+
-- | Use for trace-style selective debugging.
66+
-- Like 'debug' but allows debugging something other than the result.
67+
debugOther :: (Show x) => DebugFlag -> String -> x -> a -> a
68+
debugOther flag msg x a =
69+
if shouldDebug flag
70+
then (trace (msg <> ":\n" <> into @String (pShow x)) a)
71+
else a
72+
6473
-- | Use for selective debug logging in monadic contexts.
6574
-- E.g.
6675
-- do

src/Share/Postgres/NameLookups/Ops.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import U.Codebase.Reference (Reference)
3636
import U.Codebase.Referent (ConstructorType, Referent)
3737
import Unison.Codebase.Path (Path)
3838
import Unison.Codebase.Path qualified as Path
39-
import Unison.Debug qualified as Debug
4039
import Unison.Name (Name)
4140
import Unison.Name qualified as Name
4241
import Unison.NameSegment.Internal (NameSegment (..))
@@ -109,7 +108,6 @@ relocateToNameRoot perspective query rootBh = do
109108
& Path.fromList
110109
Nothing -> mempty
111110
let fullPath = perspective <> nameLocation
112-
Debug.debugM Debug.Server "relocateToNameRoot fullPath" fullPath
113111
namesPerspective@NamesPerspective {relativePerspective} <- namesPerspectiveForRootAndPath rootBh (PathSegments . fmap NameSegment.toUnescapedText . Path.toList $ fullPath)
114112
let reprefixName name = Name.fromReverseSegments $ (NonEmpty.head $ Name.reverseSegments name) NonEmpty.:| (reverse $ coerce relativePerspective)
115113
pure (namesPerspective, reprefixName <$> query)
@@ -123,18 +121,20 @@ fuzzySearchDefinitions ::
123121
-- | Will return at most n terms and n types; i.e. max number of results is 2n
124122
Int ->
125123
NonEmpty Text ->
124+
Text ->
126125
m ([(Q.FuzzySearchScore, NameLookups.NamedRef (Referent, Maybe ConstructorType))], [(Q.FuzzySearchScore, NamedRef Reference)])
127-
fuzzySearchDefinitions includeDependencies NamesPerspective {nameLookupBranchHashId, relativePerspective, nameLookupReceipt} limit querySegments = do
126+
fuzzySearchDefinitions includeDependencies NamesPerspective {nameLookupBranchHashId, relativePerspective, nameLookupReceipt} limit querySegments lastQuerySegment = do
128127
pgTermNames <-
129-
Q.fuzzySearchTerms nameLookupReceipt includeDependencies nameLookupBranchHashId (into @Int64 limit) relativePerspective querySegments
128+
Q.fuzzySearchTerms nameLookupReceipt includeDependencies nameLookupBranchHashId (into @Int64 limit) relativePerspective querySegments lastQuerySegment
130129
<&> fmap \termName ->
131130
termName
132131
& second (stripPrefixFromNamedRef relativePerspective)
133132
pgTypeNames <-
134-
Q.fuzzySearchTypes nameLookupReceipt includeDependencies nameLookupBranchHashId (into @Int64 limit) relativePerspective querySegments
133+
Q.fuzzySearchTypes nameLookupReceipt includeDependencies nameLookupBranchHashId (into @Int64 limit) relativePerspective querySegments lastQuerySegment
135134
<&> fmap \typeName ->
136135
typeName
137136
& second (stripPrefixFromNamedRef relativePerspective)
137+
138138
termNames <- pgTermNames & (traversed . _2 . traversed . _1) %%~ CV.referentPGTo2
139139
typeNames <- pgTypeNames & (traversed . _2 . traversed) %%~ CV.referencePGTo2
140140
pure (termNames, typeNames)

src/Share/Postgres/NameLookups/Queries.hs

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ where
2525

2626
import Control.Lens hiding (from)
2727
import Data.Foldable qualified as Foldable
28-
import Data.List.NonEmpty qualified as NEL
2928
import Data.Text qualified as Text
3029
import Share.Postgres
3130
import Share.Postgres qualified as PG
@@ -307,13 +306,35 @@ listNameLookupMounts !_nameLookupReceipt rootBranchHashId =
307306
in (mountPath, mountedRootBranchHashId)
308307

309308
-- | Larger is better.
310-
type FuzzySearchScore = (Bool, Bool, Int64, Int64)
309+
data FuzzySearchScore
310+
= FuzzySearchScore
311+
{ exactLastSegmentMatch :: Bool,
312+
lastSegmentInfixMatch :: Bool,
313+
lastSegmentMatchPos :: Int64,
314+
inverseNameLength :: Int64
315+
}
316+
deriving (Show, Eq)
317+
318+
instance Ord FuzzySearchScore where
319+
compare (FuzzySearchScore exact1 infix1 pos1 len1) (FuzzySearchScore exact2 infix2 pos2 len2) =
320+
exact1 `compare` exact2
321+
<> infix1 `compare` infix2
322+
<> pos1 `compare` pos2
323+
<> len1 `compare` len2
324+
325+
instance DecodeRow FuzzySearchScore where
326+
decodeRow =
327+
FuzzySearchScore
328+
<$> PG.decodeField
329+
<*> PG.decodeField
330+
<*> PG.decodeField
331+
<*> PG.decodeField
311332

312333
-- | Searches for all names within the given name lookup which contain the provided list of segments
313334
-- in order.
314335
-- Search is case insensitive.
315-
fuzzySearchTerms :: (PG.QueryM m) => NameLookupReceipt -> Bool -> BranchHashId -> Int64 -> PathSegments -> NonEmpty Text -> m [(FuzzySearchScore, NamedRef (PGReferent, Maybe ConstructorType))]
316-
fuzzySearchTerms !_nameLookupReceipt includeDependencies bhId limit namespace querySegments = do
336+
fuzzySearchTerms :: (PG.QueryM m) => NameLookupReceipt -> Bool -> BranchHashId -> Int64 -> PathSegments -> NonEmpty Text -> Text -> m [(FuzzySearchScore, NamedRef (PGReferent, Maybe ConstructorType))]
337+
fuzzySearchTerms !_nameLookupReceipt includeDependencies bhId limit namespace querySegments lastSearchTerm = do
317338
fmap unRow
318339
<$> PG.queryListRows
319340
[PG.sql|
@@ -322,7 +343,7 @@ fuzzySearchTerms !_nameLookupReceipt includeDependencies bhId limit namespace qu
322343
FROM (
323344
SELECT reversed_name, referent_builtin, referent_component_hash_id, referent_component_index, referent_constructor_index, referent_constructor_type, last_name_segment,
324345
(last_name_segment = #{lastSearchTerm}) AS exact_last_segment_match,
325-
(#{lastSearchTerm} ILIKE like_escape('%' || last_name_segment) || '%') AS last_segment_infix_match,
346+
(last_name_segment ILIKE ('%' || like_escape(#{lastSearchTerm}) || '%')) AS last_segment_infix_match,
326347
(-POSITION(#{lastSearchTerm} IN last_name_segment)) AS last_segment_match_pos,
327348
(-length(reversed_name)) AS inverse_name_length
328349
FROM scoped_term_name_lookup
@@ -335,22 +356,21 @@ fuzzySearchTerms !_nameLookupReceipt includeDependencies bhId limit namespace qu
335356
-- Exact last-segment matches first, then last-segment infix matches sorting prefix
336357
-- matches first, then prefer shorter names.
337358
ORDER BY (last_name_segment = #{lastSearchTerm},
338-
(#{lastSearchTerm} ILIKE like_escape('%' || last_name_segment) || '%'),
359+
(#{lastSearchTerm} ILIKE ('%' || like_escape(last_name_segment) || '%')),
339360
(-POSITION(#{lastSearchTerm} IN last_name_segment)),
340361
(-length(reversed_name))
341362
) DESC
342363
LIMIT #{limit}
343364
|]
344365
where
345-
lastSearchTerm = NEL.last querySegments
346366
namespacePrefix = toNamespacePrefix namespace
347367
-- Union in the dependencies if required.
348368
dependenciesSql =
349369
[PG.sql|
350370
UNION ALL
351371
SELECT (names.reversed_name || mount.reversed_mount_path) AS reversed_name, referent_builtin, referent_component_hash_id, referent_component_index, referent_constructor_index, referent_constructor_type, last_name_segment,
352372
(last_name_segment = #{lastSearchTerm}) AS exact_last_segment_match,
353-
(#{lastSearchTerm} ILIKE like_escape('%' || last_name_segment) || '%') AS last_segment_infix_match,
373+
(last_name_segment ILIKE ('%' || like_escape(#{lastSearchTerm}) || '%')) AS last_segment_infix_match,
354374
(-POSITION(#{lastSearchTerm} IN last_name_segment)) AS last_segment_match_pos,
355375
(-length(reversed_name)) AS inverse_name_length
356376
FROM name_lookup_mounts mount
@@ -372,8 +392,8 @@ fuzzySearchTerms !_nameLookupReceipt includeDependencies bhId limit namespace qu
372392
-- in order.
373393
--
374394
-- Search is case insensitive.
375-
fuzzySearchTypes :: (PG.QueryM m) => NameLookupReceipt -> Bool -> BranchHashId -> Int64 -> PathSegments -> NonEmpty Text -> m [(FuzzySearchScore, NamedRef PGReference)]
376-
fuzzySearchTypes !_nameLookupReceipt includeDependencies bhId limit namespace querySegments = do
395+
fuzzySearchTypes :: (PG.QueryM m) => NameLookupReceipt -> Bool -> BranchHashId -> Int64 -> PathSegments -> NonEmpty Text -> Text -> m [(FuzzySearchScore, NamedRef PGReference)]
396+
fuzzySearchTypes !_nameLookupReceipt includeDependencies bhId limit namespace querySegments lastSearchTerm = do
377397
fmap unRow
378398
<$> PG.queryListRows
379399
[PG.sql|
@@ -383,7 +403,7 @@ fuzzySearchTypes !_nameLookupReceipt includeDependencies bhId limit namespace qu
383403
FROM (
384404
SELECT reversed_name, reference_builtin, reference_component_hash_id, reference_component_index, last_name_segment,
385405
(last_name_segment = #{lastSearchTerm}) AS exact_last_segment_match,
386-
(#{lastSearchTerm} ILIKE like_escape('%' || last_name_segment) || '%') AS last_segment_infix_match,
406+
(last_name_segment ILIKE ('%' || like_escape(#{lastSearchTerm}) || '%')) AS last_segment_infix_match,
387407
(-POSITION(#{lastSearchTerm} IN last_name_segment)) AS last_segment_match_pos,
388408
(-length(reversed_name)) AS inverse_name_length
389409
FROM scoped_type_name_lookup
@@ -396,14 +416,13 @@ fuzzySearchTypes !_nameLookupReceipt includeDependencies bhId limit namespace qu
396416
-- Exact last-segment matches first, then last-segment prefix matches, then prefer
397417
-- shorter names.
398418
ORDER BY (last_name_segment = #{lastSearchTerm},
399-
(#{lastSearchTerm} ILIKE like_escape('%' || last_name_segment) || '%'),
419+
(#{lastSearchTerm} ILIKE ('%' || like_escape(last_name_segment) || '%')),
400420
(-POSITION(#{lastSearchTerm} IN last_name_segment)),
401421
(-length(reversed_name))
402422
) DESC
403423
LIMIT #{limit}
404424
|]
405425
where
406-
lastSearchTerm = NEL.last querySegments
407426
unRow :: (NamedRef PGReference PG.:. FuzzySearchScore) -> (FuzzySearchScore, NamedRef PGReference)
408427
unRow (namedRef PG.:. score) = (score, namedRef)
409428
namespacePrefix = toNamespacePrefix namespace
@@ -413,7 +432,7 @@ fuzzySearchTypes !_nameLookupReceipt includeDependencies bhId limit namespace qu
413432
UNION ALL
414433
SELECT (names.reversed_name || mount.reversed_mount_path) AS reversed_name, reference_builtin, reference_component_hash_id, reference_component_index, last_name_segment,
415434
(last_name_segment = #{lastSearchTerm}) AS exact_last_segment_match,
416-
(#{lastSearchTerm} ILIKE like_escape('%' || last_name_segment) || '%') AS last_segment_infix_match,
435+
(last_name_segment ILIKE ('%' || like_escape('%' || #{lastSearchTerm}) || '%')) AS last_segment_infix_match,
417436
(-POSITION(#{lastSearchTerm} IN last_name_segment)) AS last_segment_match_pos,
418437
(-length(reversed_name)) AS inverse_name_length
419438
FROM name_lookup_mounts mount

src/Unison/Server/Share/FuzzyFind.hs

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Data.List qualified as List
1818
import Data.List.NonEmpty qualified as NonEmpty
1919
import Data.Ord qualified as Ord
2020
import Data.Text qualified as Text
21+
import Safe (lastMay)
2122
import Servant
2223
( QueryParam,
2324
(:>),
@@ -37,9 +38,11 @@ import Unison.Codebase.Editor.DisplayObject
3738
import Unison.Codebase.Path qualified as Path
3839
import Unison.Codebase.ShortCausalHash qualified as SCH
3940
import Unison.Codebase.SqliteCodebase.Conversions qualified as Cv
41+
import Unison.HashQualifiedPrime qualified as HQ'
4042
import Unison.NameSegment.Internal (NameSegment (..))
4143
import Unison.PrettyPrintEnvDecl qualified as PPED
4244
import Unison.Server.Backend (termEntryLabeledDependencies, typeEntryLabeledDependencies)
45+
import Unison.Server.Backend qualified as UBackend
4346
import Unison.Server.Syntax (SyntaxText)
4447
import Unison.Server.Types
4548
( APIGet,
@@ -48,9 +51,8 @@ import Unison.Server.Types
4851
NamedTerm,
4952
NamedType,
5053
UnisonName,
51-
mayDefaultWidth,
5254
)
53-
import Unison.Symbol (Symbol)
55+
import Unison.Syntax.Name qualified as Name
5456
import Unison.Util.Pretty (Width)
5557

5658
type FuzzyFindAPI =
@@ -122,16 +124,18 @@ serveFuzzyFind inScratch searchDependencies rootCausal perspective mayLimit type
122124
-- Include dependencies if they were explicitly requested OR if we're running a search
123125
-- from a scratch root
124126
let includeDependencies = isScratchRootSearch || searchDependencies
125-
(dbTermMatches, dbTypeMatches) <- case NonEmpty.nonEmpty preparedQuery of
127+
let (querySegments, mayLastQuerySegment) = prepareQuery (Text.unpack query)
128+
(dbTermMatches, dbTypeMatches) <- case (NonEmpty.nonEmpty querySegments, mayLastQuerySegment) of
126129
-- Just return no results if the query is empty
127-
Nothing -> empty
128-
Just preparedQuery -> do
129-
(terms, types) <- NameLookupOps.fuzzySearchDefinitions includeDependencies namesPerspective limit preparedQuery
130+
(Nothing, _) -> empty
131+
(_, Nothing) -> empty
132+
(Just preparedQuery, Just lastSegment) -> do
133+
(terms, types) <- NameLookupOps.fuzzySearchDefinitions includeDependencies namesPerspective limit preparedQuery lastSegment
130134
pure (terms, types)
131135
let prepareMatch :: NamedRef Backend.FoundRef -> (PathSegments, Alignment, UnisonName, [Backend.FoundRef])
132136
prepareMatch name@(NamedRef {reversedSegments}) =
133137
let renderedName = NameLookups.reversedNameToNamespaceText reversedSegments
134-
segments = computeMatchSegments preparedQuery name
138+
segments = computeMatchSegments querySegments name
135139
alignment =
136140
Alignment
137141
{ -- We used to return a score, but now we just sort all the results server-side.
@@ -168,7 +172,6 @@ serveFuzzyFind inScratch searchDependencies rootCausal perspective mayLimit type
168172
& fmap snd
169173
(join <$> traverse (lift . loadEntry includeDependencies bhId namesPerspective) alignments)
170174
where
171-
preparedQuery = prepareQuery (Text.unpack query)
172175
limit = fromMaybe 10 mayLimit
173176
loadEntry :: Bool -> BranchHashId -> NameLookups.NamesPerspective -> (PathSegments, Alignment, Text, [Backend.FoundRef]) -> CodebaseM e [(Alignment, FoundResult)]
174177
loadEntry includeDependencies bhId searchPerspective (pathToMatch, a, n, refs) = do
@@ -194,17 +197,19 @@ serveFuzzyFind inScratch searchDependencies rootCausal perspective mayLimit type
194197
pped <- PPED.ppedForReferences namesPerspective allLabeledDependencies
195198
let ppe = PPED.suffixifiedPPE pped
196199
for entries \case
197-
Left (r, termEntry) ->
200+
Left (_r, termEntry) ->
198201
pure
199202
( a,
200203
FoundTermResult
201204
. FoundTerm
202-
(Backend.bestNameForTerm @Symbol ppe (mayDefaultWidth typeWidth) r)
205+
-- Use the name from the search here rather than the pretty printer best-name
206+
(Name.toText $ HQ'.toName $ UBackend.termEntryHQName termEntry)
203207
$ Backend.termEntryToNamedTerm ppe typeWidth termEntry
204208
)
205209
Right (r, typeEntry) -> do
206210
let namedType = Backend.typeEntryToNamedType typeEntry
207-
let typeName = Backend.bestNameForType @Symbol ppe (mayDefaultWidth typeWidth) r
211+
-- Use the name from the search here rather than the pretty printer best-name
212+
let typeName = (Name.toText $ HQ'.toName $ UBackend.typeEntryHQName typeEntry)
208213
typeHeader <- Backend.typeDeclHeader ppe r
209214
let ft = FoundType typeName typeHeader namedType
210215
pure (a, FoundTypeResult ft)
@@ -296,39 +301,42 @@ computeMatchSegments query (NamedRef {reversedSegments}) =
296301
-- names.
297302
--
298303
-- >>> prepareQuery "foo bar baz"
299-
-- ["foo","bar","baz"]
304+
-- (["foo","bar","baz"],Just "baz")
300305
--
301306
-- Split camel-case style words into segments.
302307
-- >>> prepareQuery "fMap"
303-
-- ["f","Map"]
308+
-- (["f","Map"],Just "fMap")
304309
--
305310
-- Collapse multiple spaces
306311
-- >>> prepareQuery "foo barBaz boom"
307-
-- ["foo","bar","Baz","boom"]
312+
-- (["foo","bar","Baz","boom"],Just "boom")
308313
--
309314
-- Split namespaces into segments with a required dot in between.
310315
-- >>> prepareQuery "List.map"
311-
-- ["List",".","map"]
316+
-- (["List",".","map"],Just "map")
312317
--
313318
-- Shouldn't get multiple splits for capitalized letters
314319
-- >>> prepareQuery "List.Map"
315-
-- ["List",".","Map"]
316-
prepareQuery :: String -> [Text]
317-
prepareQuery query = do
318-
word <- words query
319-
xs <-
320-
word
321-
& List.foldl'
322-
( \acc next -> case next of
323-
c
324-
| Char.isUpper c -> [c] : acc
325-
| Char.isSpace c -> "" : acc
326-
| c == '.' -> "" : "." : acc
327-
| otherwise -> case acc of
328-
[] -> [[c]]
329-
(last : rest) -> (last ++ [c]) : rest
330-
)
331-
[]
332-
& reverse
333-
& filter (not . null)
334-
pure $ Text.pack xs
320+
-- (["List",".","Map"],Just "Map")
321+
prepareQuery :: String -> ([Text], Maybe Text)
322+
prepareQuery query = (querySegments, lastSegment)
323+
where
324+
lastSegment = lastMay (Text.words (Text.pack query) >>= Text.splitOn ".")
325+
querySegments = do
326+
word <- words query
327+
xs <-
328+
word
329+
& List.foldl'
330+
( \acc next -> case next of
331+
c
332+
| Char.isUpper c -> [c] : acc
333+
| Char.isSpace c -> "" : acc
334+
| c == '.' -> "" : "." : acc
335+
| otherwise -> case acc of
336+
[] -> [[c]]
337+
(last : rest) -> (last ++ [c]) : rest
338+
)
339+
[]
340+
& reverse
341+
& filter (not . null)
342+
pure $ (Text.pack xs)

0 commit comments

Comments
 (0)