Skip to content

Commit

Permalink
fix: improve fuzzy search sorting (#358)
Browse files Browse the repository at this point in the history
* fix: improve fuzzy search sorting

* test: fix tests
  • Loading branch information
juanmahidalgo authored Oct 5, 2023
1 parent 280c23b commit 4d3d1e6
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 19 deletions.
18 changes: 11 additions & 7 deletions src/ports/catalog/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,19 @@ export function createCatalogComponent(options: {
{}
)
if (filters.search) {
const filteredItems = []
for (const schema of Object.values(reducedSchemas)) {
const filteredItemsById = await client.query<CollectionsItemDBResult>(
getItemIdsBySearchTextQuery(schema, filters)
)
filters.ids = [
...(filters.ids ?? []),
...filteredItemsById.rows.map(({ id }) => id),
]
const filteredItemsById = await client.query<{
id: string
similarity: number
}>(getItemIdsBySearchTextQuery(schema, filters))
filteredItems.push(...filteredItemsById.rows)
}
filteredItems?.sort((a, b) => b.similarity - a.similarity)
filters.ids = [
...(filters.ids ?? []),
...filteredItems.map(({ id }) => id),
]

if (filters.ids?.length === 0) {
// if no items matched the search text, return empty result
Expand Down
7 changes: 5 additions & 2 deletions src/ports/catalog/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,10 @@ export const getItemIdsBySearchTextQuery = (
filters: CatalogQueryFilters
) => {
const { category, search, limit, offset } = filters
const query = SQL`SELECT items.id`
const query = SQL`SELECT
items.id,
GREATEST(similarity(word, ${search}), similarity(metadata_wearable.name, ${search}),
similarity(metadata_emote.name, ${search})) as similarity`
.append(` FROM `)
.append(schemaVersion)
.append(`.item_active AS items `)
Expand All @@ -469,7 +472,7 @@ export const getItemIdsBySearchTextQuery = (
.append(getSearchWhere({ search, category }))
.append(
category
? SQL` ORDER BY GREATEST(similarity(word, ${search})) DESC`
? SQL` ORDER BY GREATEST(similarity(word, ${search}), similarity(metadata_wearable.name, ${search}), similarity(metadata_emote.name, ${search})) DESC`
: SQL` ORDER BY GREATEST(similarity(word_wearable, ${search}), similarity(word_emote, ${search})) DESC`
)
.append(SQL` LIMIT ${limit} OFFSET ${offset};`)
Expand Down
14 changes: 7 additions & 7 deletions src/tests/ports/catalog-queries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,10 +526,10 @@ test('catalog utils', () => {
expect(query.text).toContain(
`LEFT JOIN LATERAL unnest(string_to_array(metadata_emote.name, ' ')) AS word_emote ON TRUE `
)
expect(query.text).toContain(`word_wearable % $1 OR word_emote % $2 `)
expect(query.text).toContain(`word_wearable % $4 OR word_emote % $5 `)
// it appears four times `JOIN LATERAL unnest(string_to_array(metadata_emote.name, ' ')) AS word ON TRUE WHERE word % $1 ORDER BY GREATEST(similarity(word, $2))`
expect(query.values).toStrictEqual([
...Array(4).fill(search),
...Array(7).fill(search),
limit,
offset,
])
Expand All @@ -550,10 +550,10 @@ test('catalog utils', () => {
expect(query.text).toContain(
`JOIN LATERAL unnest(string_to_array(metadata_wearable.name, ' ')) AS word ON TRUE `
)
expect(query.text).toContain(`word % $1 `)
expect(query.text).toContain(`word % $4 `)
// it appears twice `JOIN LATERAL unnest(string_to_array(metadata_wearable.name, ' ')) AS word ON TRUE WHERE word % $1 ORDER BY GREATEST(similarity(word, $2))`
expect(query.values).toStrictEqual([
...Array(2).fill(search),
...Array(7).fill(search),
limit,
offset,
])
Expand All @@ -574,10 +574,10 @@ test('catalog utils', () => {
expect(query.text).toContain(
`JOIN LATERAL unnest(string_to_array(metadata_emote.name, ' ')) AS word ON TRUE `
)
expect(query.text).toContain(`word % $1 `)
// it appears twice `JOIN LATERAL unnest(string_to_array(metadata_emote.name, ' ')) AS word ON TRUE WHERE word % $1 ORDER BY GREATEST(similarity(word, $2))`
expect(query.text).toContain(`word % $4 `)
// it appears twice `JOIN LATERAL unnest(string_to_array(metadata_emote.name, ' ')) AS word ON TRUE WHERE word % $1 ORDER BY GREATEST(similarity(word, $2), similarity(metadata_wearable.name, $3), similarity(metadata_emote.name, $4))`
expect(query.values).toStrictEqual([
...Array(2).fill(search),
...Array(7).fill(search),
limit,
offset,
])
Expand Down
6 changes: 3 additions & 3 deletions src/tests/ports/catalog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,9 @@ test('catalog component', function () {
expect(dbClientQueryMock.mock.calls[1][0]).toEqual(
itemIdsBySearchTextQuery
)
// It's repeated 4 times due to this WHERE statement: `WHERE word_wearable % $1 OR word_emote % $2 ORDER BY GREATEST(similarity(word_wearable, $3), similarity(word_emote, $4)) DESC;`
// It's repeated 7 times due to the SELECT + WHERE statements: `WHERE word_wearable % $1 OR word_emote % $2 ORDER BY GREATEST(similarity(word_wearable, $3), similarity(word_emote, $4)) DESC;`
expect(dbClientQueryMock.mock.calls[1][0].values).toEqual([
...Array(4).fill(search),
...Array(7).fill(search),
limit,
offset,
])
Expand Down Expand Up @@ -388,7 +388,7 @@ test('catalog component', function () {
)
// It's repeated 4 times due to this WHERE statement: `WHERE word_wearable % $1 OR word_emote % $2 ORDER BY GREATEST(similarity(word_wearable, $3), similarity(word_emote, $4)) DESC;`
expect(dbClientQueryMock.mock.calls[1][0].values).toEqual([
...Array(4).fill(search),
...Array(7).fill(search),
limit,
offset,
])
Expand Down

0 comments on commit 4d3d1e6

Please sign in to comment.