Skip to content

Commit

Permalink
feat: Implement 'Terms Set Query'
Browse files Browse the repository at this point in the history
  • Loading branch information
sudo-suhas committed Jun 3, 2018
1 parent 3792268 commit 229765f
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 0 deletions.
79 changes: 79 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,85 @@ declare namespace esb {
values?: string[] | string | number | boolean
): TermsQuery;

/**
* Returns any documents that match with at least one or more of the provided
* terms. The terms are not analyzed and thus must match exactly. The number of
* terms that must match varies per document and is either controlled by a
* minimum should match field or computed per document in a minimum should match
* script.
*
* NOTE: This query was added in elasticsearch v6.1.
*
* @param {string=} field
* @param {Array<string|number|boolean>|string|number=} terms
*
* @extends Query
*/
export class TermsSetQuery extends Query {
constructor(
field?: string,
terms?: string[] | number[] | boolean[] | string | number
);

/**
* Sets the field to search on.
*
* @param {string} field
*/
field(field: string): this;

/**
* Append given term to set of terms to run Terms Set Query with.
*
* @param {string|number|boolean} term
*/
term(term: string | number | boolean): this;

/**
* Specifies the terms to run query for.
*
* @param {Array<string|number|boolean>} terms Terms set to run query for.
* @throws {TypeError} If `terms` is not an instance of Array
*/
terms(terms: string[] | number[] | boolean[]): this;

/**
* Controls the number of terms that must match per document.
*
* @param {string} fieldName
*/
minimumShouldMatchField(fieldName: string): this;

/**
* Sets the `script` for query. It controls how many terms are required to
* match in a more dynamic way.
*
* The `params.num_terms` parameter is available in the script to indicate
* the number of terms that have been specified.
*
* @param {Script|string|Object} script
* @returns {ScriptQuery} returns `this` so that calls can be chained.
*/
minimumShouldMatchScript(script: Script | string | object): this;
}

/**
* Returns any documents that match with at least one or more of the provided
* terms. The terms are not analyzed and thus must match exactly. The number of
* terms that must match varies per document and is either controlled by a
* minimum should match field or computed per document in a minimum should match
* script.
*
* NOTE: This query was added in elasticsearch v6.1.
*
* @param {string=} field
* @param {Array|string|number=} terms
*/
export function termsSetQuery(
field?: string,
terms?: string[] | number[] | boolean[] | string | number
): TermsSetQuery;

/**
* Interface-like class used to group and identify various implementations of
* multi term queries:
Expand Down
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const {
termLevelQueries: {
TermQuery,
TermsQuery,
TermsSetQuery,
RangeQuery,
ExistsQuery,
PrefixQuery,
Expand Down Expand Up @@ -190,6 +191,9 @@ exports.termQuery = constructorWrapper(TermQuery);
exports.TermsQuery = TermsQuery;
exports.termsQuery = constructorWrapper(TermsQuery);

exports.TermsSetQuery = TermsSetQuery;
exports.termsSetQuery = constructorWrapper(TermsSetQuery);

exports.RangeQuery = RangeQuery;
exports.rangeQuery = constructorWrapper(RangeQuery);

Expand Down
1 change: 1 addition & 0 deletions src/queries/term-level-queries/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ exports.MultiTermQueryBase = require('./multi-term-query-base');

exports.TermQuery = require('./term-query');
exports.TermsQuery = require('./terms-query');
exports.TermsSetQuery = require('./terms-set-query');
exports.RangeQuery = require('./range-query');
exports.ExistsQuery = require('./exists-query');
exports.PrefixQuery = require('./prefix-query');
Expand Down
123 changes: 123 additions & 0 deletions src/queries/term-level-queries/terms-set-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'use strict';

const isNil = require('lodash.isnil');

const { Query, util: { checkType } } = require('../../core');

/**
* Returns any documents that match with at least one or more of the provided
* terms. The terms are not analyzed and thus must match exactly. The number of
* terms that must match varies per document and is either controlled by a
* minimum should match field or computed per document in a minimum should match
* script.
*
* [Elasticsearch reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-set-query.html)
*
* NOTE: This query was added in elasticsearch v6.1.
*
* @example
* const qry = esb.termsSetQuery('codes', ['abc', 'def', 'ghi'])
* .minimumShouldMatchField('required_matches')
*
* @param {string=} field
* @param {Array<string|number|boolean>|string|number=} terms
*
* @extends Query
*/
class TermsSetQuery extends Query {
// eslint-disable-next-line require-jsdoc
constructor(field, terms) {
super('terms_set');

this._queryOpts.terms = [];

if (!isNil(field)) this._field = field;
if (!isNil(terms)) {
if (Array.isArray(terms)) this.terms(terms);
else this.term(terms);
}
}

/**
* Sets the field to search on.
*
* @param {string} field
* @returns {TermsSetQuery} returns `this` so that calls can be chained.
*/
field(field) {
this._field = field;
return this;
}

/**
* Append given term to set of terms to run Terms Set Query with.
*
* @param {string|number|boolean} term
* @returns {TermsSetQuery} returns `this` so that calls can be chained
*/
term(term) {
this._queryOpts.terms.push(term);
return this;
}

/**
* Specifies the terms to run query for.
*
* @param {Array<string|number|boolean>} terms Terms set to run query for.
* @returns {TermsSetQuery} returns `this` so that calls can be chained
* @throws {TypeError} If `terms` is not an instance of Array
*/
terms(terms) {
checkType(terms, Array);

this._queryOpts.terms = this._queryOpts.terms.concat(terms);
return this;
}

/**
* Controls the number of terms that must match per document.
*
* @param {string} fieldName
* @returns {TermsSetQuery} returns `this` so that calls can be chained
*/
minimumShouldMatchField(fieldName) {
this._queryOpts.minimum_should_match_field = fieldName;
return this;
}

/**
* Sets the `script` for query. It controls how many terms are required to
* match in a more dynamic way.
*
* The `params.num_terms` parameter is available in the script to indicate
* the number of terms that have been specified.
*
* @example
* const qry = esb.termsSetQuery('codes', ['abc', 'def', 'ghi'])
* .minimumShouldMatchScript({
* source: "Math.min(params.num_terms, doc['required_matches'].value)"
* })
*
* @param {Script|string|Object} script
* @returns {ScriptQuery} returns `this` so that calls can be chained.
*/
minimumShouldMatchScript(script) {
this._queryOpts.minimum_should_match_script = script;
return this;
}

/**
* Override default `toJSON` to return DSL representation of the term level query
* class instance.
*
* @override
* @returns {Object} returns an Object which maps to the elasticsearch query DSL
*/
toJSON() {
return {
[this.queryType]: { [this._field]: this._queryOpts }
};
}
}

module.exports = TermsSetQuery;
3 changes: 3 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ test('queries are exported', t => {
t.truthy(esb.TermsQuery);
t.truthy(esb.termsQuery);

t.truthy(esb.TermsSetQuery);
t.truthy(esb.termsSetQuery);

t.truthy(esb.RangeQuery);
t.truthy(esb.rangeQuery);

Expand Down
66 changes: 66 additions & 0 deletions test/queries-test/terms-set-query.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import test from 'ava';
import { TermsSetQuery } from '../../src';
import {
illegalParamType,
nameFieldExpectStrategy,
makeSetsOptionMacro
} from '../_macros';

const getInstance = () => new TermsSetQuery('my_field');

const setsOption = makeSetsOptionMacro(
getInstance,
nameFieldExpectStrategy('terms_set', { terms: [] })
);

test(illegalParamType, getInstance(), 'terms', 'Array');
test(setsOption, 'term', {
param: 'my-value',
propValue: ['my-value'],
keyName: 'terms'
});
test(setsOption, 'terms', {
param: ['my-value-1', 'my-value-2'],
spread: false
});
test(setsOption, 'minimumShouldMatchField', { param: 'required_matches' });
test(setsOption, 'minimumShouldMatchScript', {
param: {
source: "Math.min(params.num_terms, doc['required_matches'].value)"
}
});

test('constructor sets arguments', t => {
let valueA = new TermsSetQuery('my_field', 'my-value').toJSON();
let valueB = new TermsSetQuery()
.field('my_field')
.term('my-value')
.toJSON();
t.deepEqual(valueA, valueB);

let expected = {
terms_set: {
my_field: { terms: ['my-value'] }
}
};
t.deepEqual(valueA, expected);

valueA = new TermsSetQuery('my_field', [
'my-value-1',
'my-value-2'
]).toJSON();
valueB = new TermsSetQuery()
.field('my_field')
.terms(['my-value-1', 'my-value-2'])
.toJSON();
t.deepEqual(valueA, valueB);

expected = {
terms_set: {
my_field: {
terms: ['my-value-1', 'my-value-2']
}
}
};
t.deepEqual(valueA, expected);
});

0 comments on commit 229765f

Please sign in to comment.