fronted search filter sort

Benchmark created by Rez-1 on


Setup

var QueryUtil = {
  	query: function(data, options) {
  		let result = data || [];
  		if (!options) {
  			return result;
  		}
  
  		// filter first
  		if (options.filter) {
  			result = QueryUtil.filter(result, options.filter);
  		}
  
  		// search second
  		if (options.search) {
  			result = QueryUtil.search(result, options.search);
  		}
  
  		// sort last
  		if (options.sort) {
  			QueryUtil.sort(result, options.sort);
  		}
  		if (options.search) {
  			// always sort on searchscore matches when there's a search query
  			QueryUtil.sort(result, {_searchScore: 'descending'});
  		}
  		return result;
  	},
  
  	filter: function(items, filters) {
  		const filterKeys = Object.keys(filters);
  		const filterFunctions = {};
  		filterKeys.forEach((key) => {
  			switch (typeof filters[key]) {
  			case 'function':
  				filterFunctions[key] = filters[key];
  				break;
  
  			case 'string':
  			default:
  				filterFunctions[key] = (value) => value === (filters[key]);
  				break;
  			}
  		});
  
  		return items.filter((item) => {
  			let match = 0;
  
  			filterKeys.forEach((key) => {
  				if (filterFunctions[key](item[key])) {
  					match++;
  				}
  			});
  
  			return match === filterKeys.length;
  		});
  	},
  
  
  	sort: function(items, sorting) {
  		items.sort(QueryUtil._multipleCompare(Object.keys(sorting), sorting));
  		return items;
  	},
  
  	_multipleCompare: function(sortKeys, sorting) {
  		return (a, b) => {
  			let result;
  			let aValue;
  			let bValue;
  			sortKeys.some((key) => {
  				// NOTE: valueOf() allows for sorting of dates
  				aValue = (a[key] !== null) ? a[key].valueOf() : null;
  				bValue = (b[key] !== null) ? b[key].valueOf() : null;
  				result = (typeof sorting[key] === 'function') ? sorting[key](aValue, bValue) : QueryUtil._getCompareResult(aValue, bValue, sorting[key] === 'descending');
  				// NOTE: keep sorting as long as value is 0 (equal) otherwise stop,
  				// this allows multiple property sort
  				return result !== 0;
  			});
  			return result;
  		};
  	},
  
  	_getCompareResult: function(aValue, bValue, descending) {
  		if (aValue !== bValue) {
  			return !!descending === (aValue === null || aValue > bValue) ? -1 : 1;
  		}
  		return 0;
  	},
  
  	search: function(items, search) {
  		const searchKeys = Object.keys(search);
  		const searchFunctions = {};
  		searchKeys.forEach((key) => {
  			switch (typeof search[key]) {
  			case 'string':
  				searchFunctions[key] = (value) => (value.match(new RegExp(search[key], 'gi')) || []).length;
  				break;
  
  			case 'function':
  			default:
  				searchFunctions[key] = search[key];
  			}
  		});
  
  		return items.filter((item) => {
  			item._searchScore = 0;
  			searchKeys.forEach((key) => {
  				// searchfunction should return occurences
  				item._searchScore += item[key] ? searchFunctions[key](item[key]) : 0;
  			});
  			return !!item._searchScore;
  		});
  	}
  };
  
  var data = [];
  var items = 20000;
  var titles = ['stringA', 'stringB', 'stringC', 'stringD'];
  var titleMod = titles.length;
  var descriptions = ['descriptionAA', 'descriptionBB', 'descriptionCC'];
  var descriptionMod = descriptions.length;
  
  for (var i = 0; i < items; i++) {
  	data.push({
  		id: i,
  		title: titles[i % titleMod],
  		description: descriptions[i % descriptionMod],
  	});
  }

Test runner

Ready to run.

Testing in
TestOps/sec
filter
QueryUtil.query(data, {filter: {title: 'stringB'}});
ready
filter and sort
QueryUtil.query(data, {filter: {title: 'stringB'}, sort: {id: 'descending'}});
ready
search
QueryUtil.query(data, {search: {title: 'A', description: 'A'}});
ready
search filter sort
QueryUtil.query(data, {filter: {title: 'stringB'}, search: {title: 'A', description: 'A'}, sort: {id: 'descending'}});
ready
double filter & multi sort
QueryUtil.query(data, {filter: {title: 'stringB', description: 'descriptionBB'}, sort: {id: 'descending', title: 'ascending'}});
ready
search and sort
QueryUtil.query(data, {search: {title: 'A', description: 'A'}, sort: {id: 'descending'}});
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.

  • Revision 1: published by Rez-1 on