lucene-filter.js

Data filter for lucene queries
git clone git://git.finwo.net/lib/lucene-filter.js
Log | Files | Refs | README | LICENSE

commit 572c7d36f90e2446ce6608dae9a36c5d157f9f36
parent e04fb7b7508934510f1613dd5168dcb051f93037
Author: Paul Canning <pcanning@gmail.com>
Date:   Tue, 31 May 2022 15:40:20 +0100

Merge branch 'finwo:master' into master

Diffstat:
Mpackage-lock.json | 4++--
Mpackage.json | 2+-
Asrc/filters/number/comparison.js | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/filters/number/comparison.test.js | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/filters/number/range.js | 17+++++++++++++++--
Asrc/filters/number/range.test.js | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 181 insertions(+), 5 deletions(-)

diff --git a/package-lock.json b/package-lock.json @@ -1,12 +1,12 @@ { "name": "lucene-filter", - "version": "1.1.0", + "version": "1.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "lucene-filter", - "version": "1.1.0", + "version": "1.3.0", "license": "MIT", "devDependencies": { "aaargh": "^1.0.3", diff --git a/package.json b/package.json @@ -1,6 +1,6 @@ { "name": "lucene-filter", - "version": "1.1.0", + "version": "1.3.0", "description": "Data filter for lucene queries", "main": "src/index.js", "browser": "dist/index.js", diff --git a/src/filters/number/comparison.js b/src/filters/number/comparison.js @@ -0,0 +1,48 @@ +const field = require('../../field'); + +module.exports = { + detect : function (query) { + if (!query) { + return false; + } + if ('object' !== typeof query) { + return false; + } + if (!query.term) { + return false; + } + return Array.isArray(query.term.match(/^[<=>]+/)); + }, + compile: function (query) { + query.similarity = query.similarity || 0; + return function (data) { + return field(query.field, data, function (value) { + let sign, num; + value = parseFloat(value); + if (sign = query.term.match(/^[<=>]+/)) { + [sign] = sign; + } + num = query.term.match(/[0-9.]+/); + if (!num) { + return false; + } + num = Number(num[0]); + if (sign) { + if (sign === '>') { + return value > num; + } else if (sign === '>=') { + return value >= num; + } else if (sign === '<') { + return value < num; + } else if (sign === '<=') { + return value <= num; + } else { + throw new Error(`Invalid sign: ${sign}`); + } + } else { + return value === num; + } + }); + }; + }, +}; diff --git a/src/filters/number/comparison.test.js b/src/filters/number/comparison.test.js @@ -0,0 +1,60 @@ +const tape = require('tape'); +const comparison = require('./comparison'); + +tape('filter.number.comparison -- structure', async t => { + t.plan(2); + t.ok('detect' in comparison, 'comparison filter has detect method'); + t.ok('compile' in comparison, 'comparison filter has compile method'); +}); + +tape('filter.number.comparison -- detect', async t => { + t.plan(9); + t.notOk(comparison.detect(false), 'contains.detect doesn\'t trigger on false query'); + t.notOk(comparison.detect(null), 'contains.detect doesn\'t trigger on null query'); + t.notOk(comparison.detect(true), 'contains.detect doesn\'t trigger on true query'); + t.notOk(comparison.detect([]), 'contains.detect doesn\'t trigger on empty array query'); + t.notOk(comparison.detect({}), 'contains.detect doesn\'t trigger on empty object query'); + t.notOk(comparison.detect({ field: 'something' }), 'contains.detect doesn\'t trigger on termless query'); + t.notOk(comparison.detect({ term: 'something' }), 'contains.detect doesn\'t trigger on fieldless query'); + t.ok(comparison.detect({ field: 'hello', term: '>1' }), 'contains.detect triggers on properly structured query'); + t.notOk(comparison.detect({ field: 'hello', term: '1' }), 'contains.detect doesn\'t trigger on query with no comparator'); +}); + +tape('filter.number.comparison, gt -- compile', async t => { + t.plan(3); + + const extractor = comparison.compile({ field: 'age', term: '>21' }); + t.equal(typeof extractor, 'function', 'comparison.compile returns a function'); + + t.ok(extractor({ age: 22 }), 'finds term in comparison'); + t.notOk(extractor({ age: 21 }), 'doesn\'t find term in comparison'); +}); +tape('filter.number.comparison, gte -- compile', async t => { + t.plan(4); + + const extractor = comparison.compile({ field: 'age', term: '>=21' }); + t.equal(typeof extractor, 'function', 'comparison.compile returns a function'); + + t.ok(extractor({ age: 22 }), 'finds term in comparison'); + t.ok(extractor({ age: 21 }), 'finds term in comparison'); + t.notOk(extractor({ age: 20 }), 'doesn\'t find term in comparison'); +}); +tape('filter.number.comparison, lt -- compile', async t => { + t.plan(3); + + const extractor = comparison.compile({ field: 'age', term: '<21' }); + t.equal(typeof extractor, 'function', 'comparison.compile returns a function'); + + t.ok(extractor({ age: 20 }), 'finds term in comparison'); + t.notOk(extractor({ age: 21 }), 'doesn\'t find term in comparison'); +}); +tape('filter.number.comparison, lte -- compile', async t => { + t.plan(4); + + const extractor = comparison.compile({ field: 'age', term: '<=21' }); + t.equal(typeof extractor, 'function', 'comparison.compile returns a function'); + + t.ok(extractor({ age: 20 }), 'finds term in comparison'); + t.ok(extractor({ age: 21 }), 'finds term in comparison'); + t.notOk(extractor({ age: 22 }), 'doesn\'t find term in comparison'); +}); diff --git a/src/filters/number/range.js b/src/filters/number/range.js @@ -11,8 +11,21 @@ module.exports = { compile: function (query) { return function (data) { return field(query.field, data, function (value) { - console.log('range value', query, value); - }) ? query.boost : 0; + // console.log('range value', query, value); + let min = parseFloat(query.term_min); + let max = parseFloat(query.term_max); + let inclusive = query.inclusive; + if (inclusive === 'both') { + return (value >= min) && (value <= max); + } + if (inclusive === 'left') { + return (value >= min) && (value < max); + } + if (inclusive === 'right') { + return (value > min) && (value <= max); + } + return false; + }); }; }, }; diff --git a/src/filters/number/range.test.js b/src/filters/number/range.test.js @@ -0,0 +1,55 @@ +const tape = require('tape'); +const range = require('./range'); + +tape('filter.number.range -- structure', async t => { + t.plan(2); + t.ok('detect' in range, 'range filter has detect method'); + t.ok('compile' in range, 'range filter has compile method'); +}); + +tape('filter.number.range -- detect', async t => { + t.plan(8); + t.notOk(range.detect(false), 'contains.detect doesn\'t trigger on false query'); + t.notOk(range.detect(null), 'contains.detect doesn\'t trigger on null query'); + t.notOk(range.detect(true), 'contains.detect doesn\'t trigger on true query'); + t.notOk(range.detect([]), 'contains.detect doesn\'t trigger on empty array query'); + t.notOk(range.detect({}), 'contains.detect doesn\'t trigger on empty object query'); + t.notOk(range.detect({ field: 'something' }), 'contains.detect doesn\'t trigger on termless query'); + t.notOk(range.detect({ term: 'something' }), 'contains.detect doesn\'t trigger on fieldless query'); + t.ok(range.detect({ field: 'hello', term_min: 1, term_max: 10, inclusive: 'both' }), 'contains.detect triggers on properly structured query'); +}); + +tape('filter.number.range, both inclusive -- compile', async t => { + t.plan(6); + + const extractor = range.compile({ field: 'age', term_min: 18, term_max: 21, inclusive: 'both' }); + t.equal(typeof extractor, 'function', 'range.compile returns a function'); + + t.ok(extractor({ age: 20 }), 'finds term in range'); + t.ok(extractor({ age: 21 }), 'finds term in range'); + t.ok(extractor({ age: 18 }), 'finds term in range'); + t.notOk(extractor({ age: 17 }), 'doesn\'t find term in range'); + t.notOk(extractor({ age: 22 }), 'doesn\'t find term in range'); +}); + +tape('filter.number.range, left inclusive -- compile', async t => { + t.plan(4); + + const extractor = range.compile({ field: 'age', term_min: 18, term_max: 21, inclusive: 'left' }); + t.equal(typeof extractor, 'function', 'range.compile returns a function'); + + t.ok(extractor({ age: 18 }), 'finds term in range'); + t.ok(extractor({ age: 20 }), 'finds term in range'); + t.notOk(extractor({ age: 21 }), 'doesn\'t find term in range'); +}); + +tape('filter.number.range, right inclusive -- compile', async t => { + t.plan(4); + + const extractor = range.compile({ field: 'age', term_min: 18, term_max: 21, inclusive: 'right' }); + t.equal(typeof extractor, 'function', 'range.compile returns a function'); + + t.ok(extractor({ age: 20 }), 'finds term in range'); + t.ok(extractor({ age: 21 }), 'finds term in range'); + t.notOk(extractor({ age: 18 }), 'doesn\'t find term in range'); +});