-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add full-text combined fields query (#195)
combined_fields query was added in Elasticsearch 7.13.
- Loading branch information
Showing
4 changed files
with
221 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
'use strict'; | ||
|
||
const isNil = require('lodash.isnil'); | ||
|
||
const { | ||
util: { checkType, invalidParam } | ||
} = require('../../core'); | ||
const FullTextQueryBase = require('./full-text-query-base'); | ||
|
||
const ES_REF_URL = | ||
'https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-combined-fields-query.html'; | ||
|
||
const invalidOperatorParam = invalidParam( | ||
ES_REF_URL, | ||
'operator', | ||
"'and' or 'or'" | ||
); | ||
const invalidZeroTermsQueryParam = invalidParam( | ||
ES_REF_URL, | ||
'zero_terms_query', | ||
"'all' or 'none'" | ||
); | ||
|
||
/** | ||
* [Elasticsearch reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-combined-fields-query.html) | ||
* | ||
* @example | ||
* const qry = esb.combinedFieldsQuery(['subject', 'message'], 'this is a test'); | ||
* | ||
* NOTE: This query was added in elasticsearch v7.13. | ||
* | ||
* @param {Array<string>|string=} fields The fields to be queried | ||
* @param {string=} queryString The query string | ||
* | ||
* @extends FullTextQueryBase | ||
*/ | ||
class CombinedFieldsQuery extends FullTextQueryBase { | ||
// eslint-disable-next-line require-jsdoc | ||
constructor(fields, queryString) { | ||
super('combined_fields', queryString); | ||
|
||
// This field is required | ||
// Avoid checking for key in `this.field` | ||
this._queryOpts.fields = []; | ||
|
||
if (!isNil(fields)) { | ||
if (Array.isArray(fields)) this.fields(fields); | ||
else this.field(fields); | ||
} | ||
} | ||
|
||
/** | ||
* Appends given field to the list of fields to search against. | ||
* Fields can be specified with wildcards. | ||
* Individual fields can be boosted with the caret (^) notation. | ||
* Example - `"subject^3"` | ||
* | ||
* @param {string} field One of the fields to be queried | ||
* @returns {CombinedFieldsQuery} returns `this` so that calls can be chained. | ||
*/ | ||
field(field) { | ||
this._queryOpts.fields.push(field); | ||
return this; | ||
} | ||
|
||
/** | ||
* Appends given fields to the list of fields to search against. | ||
* Fields can be specified with wildcards. | ||
* Individual fields can be boosted with the caret (^) notation. | ||
* | ||
* @example | ||
* // Boost individual fields with caret `^` notation | ||
* const qry = esb.combinedFieldsQuery(['subject^3', 'message'], 'this is a test'); | ||
* | ||
* @example | ||
* // Specify fields with wildcards | ||
* const qry = esb.combinedFieldsQuery(['title', '*_name'], 'Will Smith'); | ||
* | ||
* @param {Array<string>} fields The fields to be queried | ||
* @returns {CombinedFieldsQuery} returns `this` so that calls can be chained. | ||
*/ | ||
fields(fields) { | ||
checkType(fields, Array); | ||
|
||
this._queryOpts.fields = this._queryOpts.fields.concat(fields); | ||
return this; | ||
} | ||
|
||
/** | ||
* If true, match phrase queries are automatically created for multi-term synonyms. | ||
* | ||
* @param {boolean} enable Defaults to `true` | ||
* @returns {CombinedFieldsQuery} returns `this` so that calls can be chained. | ||
*/ | ||
autoGenerateSynonymsPhraseQuery(enable) { | ||
this._queryOpts.auto_generate_synonyms_phrase_query = enable; | ||
return this; | ||
} | ||
|
||
/** | ||
* The operator to be used in the boolean query which is constructed | ||
* by analyzing the text provided. The `operator` flag can be set to `or` or | ||
* `and` to control the boolean clauses (defaults to `or`). | ||
* | ||
* @param {string} operator Can be `and`/`or`. Default is `or`. | ||
* @returns {CombinedFieldsQuery} returns `this` so that calls can be chained. | ||
*/ | ||
operator(operator) { | ||
if (isNil(operator)) invalidOperatorParam(operator); | ||
|
||
const operatorLower = operator.toLowerCase(); | ||
if (operatorLower !== 'and' && operatorLower !== 'or') { | ||
invalidOperatorParam(operator); | ||
} | ||
|
||
this._queryOpts.operator = operatorLower; | ||
return this; | ||
} | ||
|
||
/** | ||
* If the analyzer used removes all tokens in a query like a `stop` filter does, | ||
* the default behavior is to match no documents at all. In order to change that | ||
* the `zero_terms_query` option can be used, which accepts `none` (default) and `all` | ||
* which corresponds to a `match_all` query. | ||
* | ||
* @example | ||
* const qry = esb.combinedFieldsQuery('message', 'to be or not to be') | ||
* .operator('and') | ||
* .zeroTermsQuery('all'); | ||
* | ||
* @param {string} behavior A no match action, `all` or `none`. Default is `none`. | ||
* @returns {CombinedFieldsQuery} returns `this` so that calls can be chained. | ||
*/ | ||
zeroTermsQuery(behavior) { | ||
if (isNil(behavior)) invalidZeroTermsQueryParam(behavior); | ||
|
||
const behaviorLower = behavior.toLowerCase(); | ||
if (behaviorLower !== 'all' && behaviorLower !== 'none') { | ||
invalidZeroTermsQueryParam(behavior); | ||
} | ||
|
||
this._queryOpts.zero_terms_query = behaviorLower; | ||
return this; | ||
} | ||
} | ||
|
||
module.exports = CombinedFieldsQuery; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import test from 'ava'; | ||
import { CombinedFieldsQuery } from '../../src'; | ||
import { | ||
validatedCorrectly, | ||
nameExpectStrategy, | ||
makeSetsOptionMacro | ||
} from '../_macros'; | ||
|
||
const getInstance = (fields, queryStr) => | ||
new CombinedFieldsQuery(fields, queryStr); | ||
|
||
const setsOption = makeSetsOptionMacro( | ||
getInstance, | ||
nameExpectStrategy('combined_fields', { fields: [] }) | ||
); | ||
|
||
test(validatedCorrectly, getInstance, 'operator', ['and', 'or']); | ||
test(validatedCorrectly, getInstance, 'zeroTermsQuery', ['all', 'none']); | ||
test(setsOption, 'field', { | ||
param: 'my_field', | ||
propValue: ['my_field'], | ||
keyName: 'fields' | ||
}); | ||
test(setsOption, 'fields', { | ||
param: ['my_field_a', 'my_field_b'], | ||
spread: false | ||
}); | ||
test(setsOption, 'autoGenerateSynonymsPhraseQuery', { param: true }); | ||
|
||
// constructor, fields can be str or arr | ||
test('constructor sets arguments', t => { | ||
let valueA = getInstance('my_field', 'query str').toJSON(); | ||
let valueB = getInstance() | ||
.field('my_field') | ||
.query('query str') | ||
.toJSON(); | ||
t.deepEqual(valueA, valueB); | ||
|
||
let expected = { | ||
combined_fields: { | ||
fields: ['my_field'], | ||
query: 'query str' | ||
} | ||
}; | ||
t.deepEqual(valueA, expected); | ||
|
||
valueA = getInstance(['my_field_a', 'my_field_b'], 'query str').toJSON(); | ||
valueB = getInstance() | ||
.fields(['my_field_a', 'my_field_b']) | ||
.query('query str') | ||
.toJSON(); | ||
t.deepEqual(valueA, valueB); | ||
|
||
const valueC = getInstance() | ||
.field('my_field_a') | ||
.field('my_field_b') | ||
.query('query str') | ||
.toJSON(); | ||
t.deepEqual(valueA, valueC); | ||
|
||
expected = { | ||
combined_fields: { | ||
fields: ['my_field_a', 'my_field_b'], | ||
query: 'query str' | ||
} | ||
}; | ||
t.deepEqual(valueA, valueB); | ||
}); |