Skip to content

Commit

Permalink
Merge pull request #114 from pelias/joxit/feat/multi_match
Browse files Browse the repository at this point in the history
Add multi_match leaf query function
  • Loading branch information
orangejulius authored Nov 7, 2019
2 parents 671404e + 19999cf commit 41d6492
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 2 deletions.
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ module.exports.view = {
leaf: {
match_all: require('./view/leaf/match_all'),
match_phrase: require('./view/leaf/match_phrase'),
match: require('./view/leaf/match')
match: require('./view/leaf/match'),
multi_match: require('./view/leaf/multi_match')
},
focus: require('./view/focus'),
focus_only_function: require('./view/focus_only_function'),
Expand Down
36 changes: 36 additions & 0 deletions lib/leaf/multi_match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const MATCH_PARAMS = [
'tie_breaker', 'analyzer', 'boost', 'operator', 'minimum_should_match', 'fuzziness',
'lenient', 'prefix_length', 'max_expansions', 'rewrite', 'zero_terms_query', 'cutoff_frequency'
];
const PHRASE_PARAMS = ['analyzer', 'boost', 'lenient', 'slop', 'zero_terms_query'];
const OPTIONAL_PARAMS = {
'best_fields': MATCH_PARAMS,
'most_fields': MATCH_PARAMS,
'cross_fields': ['analyzer', 'boost', 'operator', 'minimum_should_match', 'lenient', 'zero_terms_query', 'cutoff_frequency'],
'phrase': PHRASE_PARAMS,
'phrase_prefix': PHRASE_PARAMS.concat('max_expansions')
};

module.exports = function( type, fields, value, params ) {
if( !type || !value || !OPTIONAL_PARAMS[type] ) {
return null;
}

const query = {
multi_match: {
type: type,
query: value,
fields: fields
}
};

OPTIONAL_PARAMS[type].forEach(function(param) {
if (params && params[param] && params[param].toString() !== '') {
query.multi_match[param] = params[param];
}
});

return query;
};

module.exports.OPTIONAL_PARAMS = OPTIONAL_PARAMS;
153 changes: 153 additions & 0 deletions test/lib/leaf/multi_match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const multi_match = require('../../../lib/leaf/multi_match');
const Variable = require('../../../lib/Variable');

module.exports.tests = {};

module.exports.tests.multi_match = function(test, common) {
test('null returned if property missing', function(t) {
const query = multi_match();

t.equal(null, query, 'null query returned');
t.end();
});

test('null returned if fields are missing', function(t) {
const query = multi_match('best_fields');

t.equal(null, query, 'null query returned');
t.end();
});

test('null returned if input is missing', function(t) {
const query = multi_match('best_fields', ['fields']);

t.equal(null, query, 'null query returned');
t.end();
});

test('null returned if type is not supported', function(t) {
const query = multi_match('type', ['fields'], 'input');

t.equal(null, query, 'null query returned');
t.end();
});

test('multi_match query returned with type, fields and input', function(t) {
const query = multi_match('best_fields', ['fields'], 'input');

const expected = {
multi_match: {
type: 'best_fields',
fields: ['fields'],
query: 'input'
}
};

t.deepEqual(query, expected, 'valid multi_match query');
t.end();
});

test('multi_match query can handle optional boost parameter', function(t) {
const query = multi_match('phrase', ['fields'], 'input', { boost: 5});


const expected = {
multi_match: {
type: 'phrase',
fields: ['fields'],
query: 'input',
boost: 5
}
};

t.deepEqual(query, expected, 'valid multi_match query with boost');
t.end();
});

test('multi_match phrase query can handle optional slop parameter', function(t) {
const query = multi_match('phrase', ['fields'], 'input', { slop: 1});

const expected = {
multi_match: {
type: 'phrase',
fields: ['fields'],
query: 'input',
slop: 1
}
};

t.deepEqual(query, expected, 'valid multi_match query with slop');
t.end();
});

test('multi_match query can handle optional analyzer parameter', function(t) {
const query = multi_match('phrase', ['fields'], 'input', { analyzer: 'customAnalyzer'});

const expected = {
multi_match: {
type: 'phrase',
fields: ['fields'],
query: 'input',
analyzer: 'customAnalyzer'
}
};

t.deepEqual(query, expected, 'valid multi_match query with analyzer');
t.end();
});

test('multi_match query does not allow empty string as optional param input', function(t) {
const query = multi_match('phrase', ['fields'], 'input', { slop: '' });

const expected = {
multi_match: {
type: 'phrase',
fields: ['fields'],
query: 'input'
}
};

t.deepEqual(query, expected, 'valid multi_match query without empty string input');
t.end();
});

test('multi_match query does not allow empty Variable as optional param input', function(t) {
const query = multi_match('phrase', ['fields'], 'input', { analyzer: new Variable() });

const expected = {
multi_match: {
type: 'phrase',
fields: ['fields'],
query: 'input'
}
};

t.deepEqual(query, expected, 'valid multi_match query with out empty string input');
t.end();
});

test('multi_match query with type phrase_prefix should accept max_expansions', function(t) {
const query = multi_match('phrase_prefix', ['fields'], 'input', { max_expansions: 25 });

const expected = {
multi_match: {
type: 'phrase_prefix',
fields: ['fields'],
query: 'input',
max_expansions: 25
}
};

t.deepEqual(query, expected, 'valid multi_match query with out empty string input');
t.end();
});
};

module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('lib/leaf/multi_match ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};
4 changes: 3 additions & 1 deletion test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var tests = [
require('./layout/VenuesQuery.js'),
require('./lib/leaf/match.js'),
require('./lib/leaf/match_phrase.js'),
require('./lib/leaf/multi_match.js'),
require('./lib/leaf/terms.js'),
require('./lib/Variable.js'),
require('./lib/VariableStore.js'),
Expand All @@ -34,7 +35,8 @@ var tests = [
require('./view/sources.js'),
require('./view/boundary_gid.js'),
require('./view/leaf/match.js'),
require('./view/leaf/match_phrase.js')
require('./view/leaf/match_phrase.js'),
require('./view/leaf/multi_match.js')
];

tests.map(function(t) {
Expand Down
110 changes: 110 additions & 0 deletions test/view/leaf/multi_match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
const VariableStore = require('../../../lib/VariableStore');
const multi_match = require('../../../view/leaf/multi_match');

module.exports.tests = {};

module.exports.tests.base_usage = function(test, common) {
test('input and fields specified', function(t) {

const vs = new VariableStore();
vs.var('multi_match:example:input', 'input value');
vs.var('multi_match:example:fields', ['fields', 'values']);

const view = multi_match('example')(vs);

const actual = JSON.parse(JSON.stringify(view));

const expected = {
multi_match: {
type: 'best_fields',
fields: ['fields', 'values'],
query: 'input value'
}
};

t.deepEqual(actual, expected, 'multi_match view rendered as expected');
t.end();
});

test('optional fields specified', function(t) {
const vs = new VariableStore();
vs.var('multi_match:example2:input', 'input value');
vs.var('multi_match:example2:fields', ['fields', 'values']);
vs.var('multi_match:example2:cutoff_frequency', 0.001);
vs.var('multi_match:example2:analyzer', 'customAnalyzer');

const view = multi_match('example2')(vs);

const actual = JSON.parse(JSON.stringify(view));

const expected = {
multi_match: {
type: 'best_fields',
fields: ['fields', 'values'],
query: 'input value',
analyzer: 'customAnalyzer',
cutoff_frequency: 0.001
}
};

t.deepEqual(actual, expected, 'multi_match view rendered as expected');
t.end();
});

test('specific multi_match type', function(t) {
const vs = new VariableStore();
vs.var('multi_match:example2:input', 'input value');
vs.var('multi_match:example2:type', 'phrase');
vs.var('multi_match:example2:fields', ['fields', 'values']);
vs.var('multi_match:example2:cutoff_frequency', 0.001); // this will be ignored because it's a phrase type
vs.var('multi_match:example2:analyzer', 'customAnalyzer');
vs.var('multi_match:example2:slop', 3);

const view = multi_match('example2')(vs);

const actual = JSON.parse(JSON.stringify(view));

const expected = {
multi_match: {
type: 'phrase',
fields: ['fields', 'values'],
query: 'input value',
analyzer: 'customAnalyzer',
slop: 3
}
};

t.deepEqual(actual, expected, 'multi_match view rendered as expected');
t.end();
});
};

module.exports.tests.incorrect_usage = function(test, common) {
test('no variables specified', function(t) {
const vs = new VariableStore();

const view = multi_match('broken_example')(vs);

t.equal(view, null, 'should return null');
t.end();
});

test('no input specified', function(t) {
const vs = new VariableStore();
vs.var('multi_match:broken_example:fields', 'fields value');

const view = multi_match('broken_example')(vs);

t.equal(view, null, 'should return null');
t.end();
});
};

module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('leaf/multi_match ' + name, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};
28 changes: 28 additions & 0 deletions view/leaf/multi_match.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const multi_match = require('../../lib/leaf/multi_match');
const OPTIONAL_PARAMS = multi_match.OPTIONAL_PARAMS;

module.exports = function( prefix ){
const type_variable = `multi_match:${prefix}:type`;
const fields_variable = `multi_match:${prefix}:fields`;
const input_variable = `multi_match:${prefix}:input`;

return function( vs ){
if( !vs.isset(fields_variable)||
!vs.isset(input_variable) ) {
return null;
}

// best_fields is the default value in ES
const type = vs.isset(type_variable) ? vs.var(type_variable) : 'best_fields';
const options = { };

OPTIONAL_PARAMS[type].forEach(function(param) {
const variable_name = `multi_match:${prefix}:${param}`;
if (vs.isset(variable_name)) {
options[param] = vs.var(variable_name);
}
});

return multi_match(type, vs.var(fields_variable), vs.var(input_variable), options);
};
};

0 comments on commit 41d6492

Please sign in to comment.