|
@@ -24,6 +24,7 @@ import type { UpdateOrInsertPagesOpts } from '../interfaces/search';
|
|
|
|
|
|
|
|
import { aggregatePipelineToIndex } from './aggregate-to-index';
|
|
import { aggregatePipelineToIndex } from './aggregate-to-index';
|
|
|
import type { AggregatedPage, BulkWriteBody, BulkWriteCommand } from './bulk-write';
|
|
import type { AggregatedPage, BulkWriteBody, BulkWriteCommand } from './bulk-write';
|
|
|
|
|
+import type { SearchQuery } from './elasticsearch-client-delegator';
|
|
|
import {
|
|
import {
|
|
|
getClient,
|
|
getClient,
|
|
|
isES7ClientDelegator,
|
|
isES7ClientDelegator,
|
|
@@ -603,7 +604,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
* create search query for Elasticsearch
|
|
* create search query for Elasticsearch
|
|
|
* @returns {object} query object
|
|
* @returns {object} query object
|
|
|
*/
|
|
*/
|
|
|
- createSearchQuery() {
|
|
|
|
|
|
|
+ createSearchQuery(): SearchQuery {
|
|
|
const fields = ['path', 'bookmark_count', 'comment_count', 'seenUsers_count', 'updated_at', 'tag_names', 'comments'];
|
|
const fields = ['path', 'bookmark_count', 'comment_count', 'seenUsers_count', 'updated_at', 'tag_names', 'comments'];
|
|
|
|
|
|
|
|
// sort by score
|
|
// sort by score
|
|
@@ -611,7 +612,9 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
index: this.aliasName,
|
|
index: this.aliasName,
|
|
|
_source: fields,
|
|
_source: fields,
|
|
|
body: {
|
|
body: {
|
|
|
- query: {}, // query
|
|
|
|
|
|
|
+ query: {
|
|
|
|
|
+ bool: {},
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -630,10 +633,9 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
query.body.sort = { [sort]: { order } };
|
|
query.body.sort = { [sort]: { order } };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- initializeBoolQuery(query) {
|
|
|
|
|
- // query is created by createSearchQuery()
|
|
|
|
|
- if (!query.body.query.bool) {
|
|
|
|
|
- query.body.query.bool = {};
|
|
|
|
|
|
|
+ initializeBoolQuery(query: SearchQuery): SearchQuery {
|
|
|
|
|
+ if (query?.body?.query?.bool == null) {
|
|
|
|
|
+ throw new Error('query.body.query.bool is not initialized');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const isInitialized = (query) => { return !!query && Array.isArray(query) };
|
|
const isInitialized = (query) => { return !!query && Array.isArray(query) };
|
|
@@ -650,14 +652,30 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
return query;
|
|
return query;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- appendCriteriaForQueryString(query, parsedKeywords: ESQueryTerms): void {
|
|
|
|
|
|
|
+ appendCriteriaForQueryString(query: SearchQuery, parsedKeywords: ESQueryTerms): void {
|
|
|
query = this.initializeBoolQuery(query); // eslint-disable-line no-param-reassign
|
|
query = this.initializeBoolQuery(query); // eslint-disable-line no-param-reassign
|
|
|
|
|
|
|
|
|
|
+ if (query.body?.query?.bool == null) {
|
|
|
|
|
+ throw new Error('query.body.query.bool is not initialized');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (query.body?.query?.bool.must == null || !Array.isArray(query.body?.query?.bool.must)) {
|
|
|
|
|
+ throw new Error('query.body.query.bool.must is not initialized');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (query.body?.query?.bool.must_not == null || !Array.isArray(query.body?.query?.bool.must_not)) {
|
|
|
|
|
+ throw new Error('query.body.query.bool.must_not is not initialized');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (query.body?.query?.bool.filter == null || !Array.isArray(query.body?.query?.bool.filter)) {
|
|
|
|
|
+ throw new Error('query.body.query.bool.filter is not initialized');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (parsedKeywords.match.length > 0) {
|
|
if (parsedKeywords.match.length > 0) {
|
|
|
const q = {
|
|
const q = {
|
|
|
multi_match: {
|
|
multi_match: {
|
|
|
query: parsedKeywords.match.join(' '),
|
|
query: parsedKeywords.match.join(' '),
|
|
|
- type: 'most_fields',
|
|
|
|
|
|
|
+ type: 'most_fields' as const,
|
|
|
fields: ['path.ja^2', 'path.en^2', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
|
|
fields: ['path.ja^2', 'path.en^2', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
@@ -669,36 +687,35 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
multi_match: {
|
|
multi_match: {
|
|
|
query: parsedKeywords.not_match.join(' '),
|
|
query: parsedKeywords.not_match.join(' '),
|
|
|
fields: ['path.ja', 'path.en', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
|
|
fields: ['path.ja', 'path.en', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
|
|
|
- operator: 'or',
|
|
|
|
|
|
|
+ operator: 'or' as const,
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
query.body.query.bool.must_not.push(q);
|
|
query.body.query.bool.must_not.push(q);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (parsedKeywords.phrase.length > 0) {
|
|
if (parsedKeywords.phrase.length > 0) {
|
|
|
- parsedKeywords.phrase.forEach((phrase) => {
|
|
|
|
|
|
|
+ for (const phrase of parsedKeywords.phrase) {
|
|
|
const phraseQuery = {
|
|
const phraseQuery = {
|
|
|
multi_match: {
|
|
multi_match: {
|
|
|
- query: phrase, // each phrase is quoteted words like "This is GROWI"
|
|
|
|
|
- type: 'phrase',
|
|
|
|
|
|
|
+ query: phrase,
|
|
|
|
|
+ type: 'phrase' as const,
|
|
|
fields: [
|
|
fields: [
|
|
|
- // Not use "*.ja" fields here, because we want to analyze (parse) search words
|
|
|
|
|
'path.raw^2',
|
|
'path.raw^2',
|
|
|
'body',
|
|
'body',
|
|
|
'comments',
|
|
'comments',
|
|
|
],
|
|
],
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
- query.body.query.bool.must.push(phraseQuery);
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ query.body.query.bool.must.push(phraseQuery); // TypeScript can track this better
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (parsedKeywords.not_phrase.length > 0) {
|
|
if (parsedKeywords.not_phrase.length > 0) {
|
|
|
- parsedKeywords.not_phrase.forEach((phrase) => {
|
|
|
|
|
|
|
+ for (const phrase of parsedKeywords.not_phrase) {
|
|
|
const notPhraseQuery = {
|
|
const notPhraseQuery = {
|
|
|
multi_match: {
|
|
multi_match: {
|
|
|
query: phrase, // each phrase is quoteted words
|
|
query: phrase, // each phrase is quoteted words
|
|
|
- type: 'phrase',
|
|
|
|
|
|
|
+ type: 'phrase' as const, // Add 'as const' to fix type error
|
|
|
fields: [
|
|
fields: [
|
|
|
// Not use "*.ja" fields here, because we want to analyze (parse) search words
|
|
// Not use "*.ja" fields here, because we want to analyze (parse) search words
|
|
|
'path.raw^2',
|
|
'path.raw^2',
|
|
@@ -707,7 +724,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
query.body.query.bool.must_not.push(notPhraseQuery);
|
|
query.body.query.bool.must_not.push(notPhraseQuery);
|
|
|
- });
|
|
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (parsedKeywords.prefix.length > 0) {
|
|
if (parsedKeywords.prefix.length > 0) {
|