diff --git a/src/ports/catalog/component.ts b/src/ports/catalog/component.ts index cc345bc..0943aa8 100644 --- a/src/ports/catalog/component.ts +++ b/src/ports/catalog/component.ts @@ -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( - 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 diff --git a/src/ports/catalog/queries.ts b/src/ports/catalog/queries.ts index 1aa2c3f..e8d6694 100644 --- a/src/ports/catalog/queries.ts +++ b/src/ports/catalog/queries.ts @@ -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 `) @@ -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};`) diff --git a/src/tests/ports/catalog-queries.spec.ts b/src/tests/ports/catalog-queries.spec.ts index 97b42ed..349716e 100644 --- a/src/tests/ports/catalog-queries.spec.ts +++ b/src/tests/ports/catalog-queries.spec.ts @@ -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, ]) @@ -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, ]) @@ -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, ]) diff --git a/src/tests/ports/catalog.spec.ts b/src/tests/ports/catalog.spec.ts index 1e2db3b..a1b4aae 100644 --- a/src/tests/ports/catalog.spec.ts +++ b/src/tests/ports/catalog.spec.ts @@ -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, ]) @@ -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, ])