Taichi Masuyama 4 лет назад
Родитель
Сommit
a812824185

+ 1 - 79
packages/app/src/server/service/search-delegator/elasticsearch.ts

@@ -642,7 +642,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data> {
     query = this.initializeBoolQuery(query); // eslint-disable-line no-param-reassign
 
     // parse
-    const parsedKeywords = this.parseQueryString(queryString);
+    const parsedKeywords = {} as any; // TODO: pass parsedQuery to this method
 
     if (parsedKeywords.match.length > 0) {
       const q = {
@@ -850,84 +850,6 @@ class ElasticsearchDelegator implements SearchDelegator<Data> {
     return this.searchKeyword(query);
   }
 
-  parseQueryString(queryString: string) {
-    const matchWords: string[] = [];
-    const notMatchWords: string[] = [];
-    const phraseWords: string[] = [];
-    const notPhraseWords: string[] = [];
-    const prefixPaths: string[] = [];
-    const notPrefixPaths: string[] = [];
-    const tags: string[] = [];
-    const notTags: string[] = [];
-
-    queryString.trim();
-    queryString = queryString.replace(/\s+/g, ' '); // eslint-disable-line no-param-reassign
-
-    // First: Parse phrase keywords
-    const phraseRegExp = new RegExp(/(-?"[^"]+")/g);
-    const phrases = queryString.match(phraseRegExp);
-
-    if (phrases !== null) {
-      queryString = queryString.replace(phraseRegExp, ''); // eslint-disable-line no-param-reassign
-
-      phrases.forEach((phrase) => {
-        phrase.trim();
-        if (phrase.match(/^-/)) {
-          notPhraseWords.push(phrase.replace(/^-/, ''));
-        }
-        else {
-          phraseWords.push(phrase);
-        }
-      });
-    }
-
-    // Second: Parse other keywords (include minus keywords)
-    queryString.split(' ').forEach((word) => {
-      if (word === '') {
-        return;
-      }
-
-      // https://regex101.com/r/pN9XfK/1
-      const matchNegative = word.match(/^-(prefix:|tag:)?(.+)$/);
-      // https://regex101.com/r/3qw9FQ/1
-      const matchPositive = word.match(/^(prefix:|tag:)?(.+)$/);
-
-      if (matchNegative != null) {
-        if (matchNegative[1] === 'prefix:') {
-          notPrefixPaths.push(matchNegative[2]);
-        }
-        else if (matchNegative[1] === 'tag:') {
-          notTags.push(matchNegative[2]);
-        }
-        else {
-          notMatchWords.push(matchNegative[2]);
-        }
-      }
-      else if (matchPositive != null) {
-        if (matchPositive[1] === 'prefix:') {
-          prefixPaths.push(matchPositive[2]);
-        }
-        else if (matchPositive[1] === 'tag:') {
-          tags.push(matchPositive[2]);
-        }
-        else {
-          matchWords.push(matchPositive[2]);
-        }
-      }
-    });
-
-    return {
-      match: matchWords,
-      not_match: notMatchWords,
-      phrase: phraseWords,
-      not_phrase: notPhraseWords,
-      prefix: prefixPaths,
-      not_prefix: notPrefixPaths,
-      tag: tags,
-      not_tag: notTags,
-    };
-  }
-
   async syncPageUpdated(page, user) {
     logger.debug('SearchClient.syncPageUpdated', page.path);
 

+ 85 - 2
packages/app/src/server/service/search.ts

@@ -144,8 +144,91 @@ class SearchService implements SearchQueryParser, SearchResolver {
   }
 
   async parseSearchQuery(_queryString: string): Promise<ParsedQuery> {
-    // TODO: impl parser
-    return {} as ParsedQuery;
+    // terms
+    const matchWords: string[] = [];
+    const notMatchWords: string[] = [];
+    const phraseWords: string[] = [];
+    const notPhraseWords: string[] = [];
+    const prefixPaths: string[] = [];
+    const notPrefixPaths: string[] = [];
+    const tags: string[] = [];
+    const notTags: string[] = [];
+    // nqNames
+    const nqNames: string[] = [];
+
+    let queryString = _queryString.trim();
+    queryString = queryString.replace(/\s+/g, ' '); // eslint-disable-line no-param-reassign
+
+    // First: Parse phrase keywords
+    const phraseRegExp = new RegExp(/(-?"[^"]+")/g);
+    const phrases = queryString.match(phraseRegExp);
+
+    if (phrases !== null) {
+      queryString = queryString.replace(phraseRegExp, ''); // eslint-disable-line no-param-reassign
+
+      phrases.forEach((phrase) => {
+        phrase.trim();
+        if (phrase.match(/^-/)) {
+          notPhraseWords.push(phrase.replace(/^-/, ''));
+        }
+        else {
+          phraseWords.push(phrase);
+        }
+      });
+    }
+
+    // Second: Parse other keywords (include minus keywords)
+    queryString.split(' ').forEach((word) => {
+      if (word === '') {
+        return;
+      }
+
+      // https://regex101.com/r/pN9XfK/1
+      const matchNegative = word.match(/^-(prefix:|tag:)?(.+)$/);
+      // https://regex101.com/r/3qw9FQ/1
+      const matchPositive = word.match(/^(prefix:|tag:)?(.+)$/);
+      // https://regex101.com/r/FzDUvT/1
+      const isNamedQuery = (new RE2(/^\[nq:.+\]$/g)).test(word);
+
+      if (isNamedQuery) {
+        nqNames.push(word.replace(/\[nq:|\]/g, '')); // remove '[nq:' and ']'
+      }
+      else if (matchNegative != null) {
+        if (matchNegative[1] === 'prefix:') {
+          notPrefixPaths.push(matchNegative[2]);
+        }
+        else if (matchNegative[1] === 'tag:') {
+          notTags.push(matchNegative[2]);
+        }
+        else {
+          notMatchWords.push(matchNegative[2]);
+        }
+      }
+      else if (matchPositive != null) {
+        if (matchPositive[1] === 'prefix:') {
+          prefixPaths.push(matchPositive[2]);
+        }
+        else if (matchPositive[1] === 'tag:') {
+          tags.push(matchPositive[2]);
+        }
+        else {
+          matchWords.push(matchPositive[2]);
+        }
+      }
+    });
+
+    const terms = {
+      match: matchWords,
+      not_match: notMatchWords,
+      phrase: phraseWords,
+      not_phrase: notPhraseWords,
+      prefix: prefixPaths,
+      not_prefix: notPrefixPaths,
+      tag: tags,
+      not_tag: notTags,
+    };
+
+    return { queryString: _queryString, terms, nqNames };
   }
 
   async resolve(parsedQuery: ParsedQuery): Promise<SearchDelegator> {

+ 39 - 0
packages/app/src/test/integration/service/search/search-service.test.js

@@ -0,0 +1,39 @@
+import SearchService from '~/server/service/search';
+
+const { getInstance } = require('../../setup-crowi');
+
+describe('SearchService test', () => {
+  let crowi;
+  let searchService;
+  const queryString1 = 'match -notmatch "phrase" -"notphrase" [nq:named_query] prefix:/pre1 -prefix:/pre2 tag:Tag1 -tag:Tag2';
+
+  beforeAll(async() => {
+    crowi = await getInstance();
+    searchService = new SearchService(crowi);
+  });
+
+
+  describe('parseSearchQuery()', () => {
+    test('should parse queryString', async() => {
+      const parsedQuery = await searchService.parseSearchQuery(queryString1);
+      const expected = {
+        queryString: queryString1,
+        terms: {
+          match: ['match'],
+          not_match: ['notmatch'],
+          phrase: ['"phrase"'],
+          not_phrase: ['"notphrase"'],
+          prefix: ['/pre1'],
+          not_prefix: ['/pre2'],
+          tag: ['Tag1'],
+          not_tag: ['Tag2'],
+        },
+        nqNames: ['named_query'],
+      };
+
+      expect(parsedQuery).toStrictEqual(expected);
+    });
+  });
+
+
+});