Sfoglia il codice sorgente

fix(core): Handle substring search terms for postgres & mysql

Fixes #1277
Michael Bromley 4 anni fa
parent
commit
81e367244d

+ 23 - 3
packages/core/e2e/default-search-plugin.e2e-spec.ts

@@ -18,7 +18,7 @@ import gql from 'graphql-tag';
 import path from 'path';
 
 import { initialData } from '../../../e2e-common/e2e-initial-data';
-import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
 
 import {
     AssignProductsToChannel,
@@ -52,8 +52,8 @@ import {
 } from './graphql/generated-e2e-admin-types';
 import { LogicalOperator, SearchProductsShop } from './graphql/generated-e2e-shop-types';
 import {
-    ASSIGN_PRODUCT_TO_CHANNEL,
     ASSIGN_PRODUCTVARIANT_TO_CHANNEL,
+    ASSIGN_PRODUCT_TO_CHANNEL,
     CREATE_CHANNEL,
     CREATE_COLLECTION,
     CREATE_FACET,
@@ -62,8 +62,8 @@ import {
     DELETE_ASSET,
     DELETE_PRODUCT,
     DELETE_PRODUCT_VARIANT,
-    REMOVE_PRODUCT_FROM_CHANNEL,
     REMOVE_PRODUCTVARIANT_FROM_CHANNEL,
+    REMOVE_PRODUCT_FROM_CHANNEL,
     UPDATE_ASSET,
     UPDATE_COLLECTION,
     UPDATE_PRODUCT,
@@ -209,6 +209,22 @@ describe('Default search plugin', () => {
         ]);
     }
 
+    async function testMatchPartialSearchTerm(client: SimpleGraphQLClient) {
+        const result = await client.query<SearchProductsShop.Query, SearchProductShopVariables>(
+            SEARCH_PRODUCTS_SHOP,
+            {
+                input: {
+                    term: 'lap',
+                    groupByProduct: true,
+                    sort: {
+                        name: SortOrder.ASC,
+                    },
+                },
+            },
+        );
+        expect(result.search.items.map(i => i.productName)).toEqual(['Laptop']);
+    }
+
     async function testMatchFacetIdsAnd(client: SimpleGraphQLClient) {
         const result = await client.query<SearchProductsShop.Query, SearchProductShopVariables>(
             SEARCH_PRODUCTS_SHOP,
@@ -468,6 +484,8 @@ describe('Default search plugin', () => {
 
         it('matches search term', () => testMatchSearchTerm(shopClient));
 
+        it('matches partial search term', () => testMatchPartialSearchTerm(shopClient));
+
         it('matches by facetId with AND operator', () => testMatchFacetIdsAnd(shopClient));
 
         it('matches by facetId with OR operator', () => testMatchFacetIdsOr(shopClient));
@@ -765,6 +783,8 @@ describe('Default search plugin', () => {
 
         it('matches search term', () => testMatchSearchTerm(adminClient));
 
+        it('matches partial search term', () => testMatchPartialSearchTerm(adminClient));
+
         it('matches by facetId with AND operator', () => testMatchFacetIdsAnd(adminClient));
 
         it('matches by facetId with OR operator', () => testMatchFacetIdsOr(adminClient));

+ 7 - 7
packages/core/src/plugin/default-search-plugin/search-strategy/mysql-search-strategy.ts

@@ -151,21 +151,21 @@ export class MysqlSearchStrategy implements SearchStrategy {
                 .addSelect(`IF (sku LIKE :like_term, 10, 0)`, 'sku_score')
                 .addSelect(
                     `(SELECT sku_score) +
-                     MATCH (productName) AGAINST (:term) * 2 +
-                     MATCH (productVariantName) AGAINST (:term) * 1.5 +
-                     MATCH (description) AGAINST (:term)* 1`,
+                     MATCH (productName) AGAINST (:term IN BOOLEAN MODE) * 2 +
+                     MATCH (productVariantName) AGAINST (:term IN BOOLEAN MODE) * 1.5 +
+                     MATCH (description) AGAINST (:term IN BOOLEAN MODE) * 1`,
                     'score',
                 )
                 .where(
                     new Brackets(qb1 => {
                         qb1.where('sku LIKE :like_term')
-                            .orWhere('MATCH (productName) AGAINST (:term)')
-                            .orWhere('MATCH (productVariantName) AGAINST (:term)')
-                            .orWhere('MATCH (description) AGAINST (:term)');
+                            .orWhere('MATCH (productName) AGAINST (:term IN BOOLEAN MODE)')
+                            .orWhere('MATCH (productVariantName) AGAINST (:term IN BOOLEAN MODE)')
+                            .orWhere('MATCH (description) AGAINST (:term IN BOOLEAN MODE)');
                     }),
                 )
                 .andWhere('channelId = :channelId')
-                .setParameters({ term, like_term: `%${term}%`, channelId: ctx.channelId });
+                .setParameters({ term: `${term}*`, like_term: `%${term}%`, channelId: ctx.channelId });
 
             qb.innerJoin(`(${termScoreQuery.getQuery()})`, 'term_result', 'inner_productId = si.productId')
                 .addSelect(input.groupByProduct ? 'MAX(term_result.score)' : 'term_result.score', 'score')

+ 7 - 1
packages/core/src/plugin/default-search-plugin/search-strategy/postgres-search-strategy.ts

@@ -145,7 +145,13 @@ export class PostgresSearchStrategy implements SearchStrategy {
         const { term, facetValueFilters, facetValueIds, facetValueOperator, collectionId, collectionSlug } =
             input;
         // join multiple words with the logical AND operator
-        const termLogicalAnd = term ? term.trim().replace(/\s+/g, ' & ') : '';
+        const termLogicalAnd = term
+            ? term
+                  .trim()
+                  .split(/\s+/g)
+                  .map(t => `${t}:*`)
+                  .join(' & ')
+            : '';
 
         qb.where('1 = 1');
         if (term && term.length > this.minTermLength) {