Skip to content

Commit

Permalink
speed up disjunctive facets (not repeating filters) (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
cigolpl authored Apr 10, 2021
1 parent 03d79cc commit 1562606
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 128 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "elasticitems",
"version": "2.0.13",
"version": "2.0.14",
"description": "Faceted search client on top of Elasticsearch 7.x",
"main": "index.js",
"scripts": {
Expand Down
259 changes: 134 additions & 125 deletions src/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,171 +194,180 @@ exports.searchBuilder = function(query, config) {
// disjunctive facets (global aggregations)
qb.aggregation('global', {}, 'global', a => {

// @TODO add global conjunction filter and query here
for (const [key, value] of Object.entries(aggs)) {
a.aggregation('filter', 'global_filterable', 'global_filterable', () => {

if (value.conjunction === false) {
const filter = bodybuilder().andFilter('bool', global_agg_filter => {

if (facets_names_obj === null || facets_names_obj[key]) {
if (query.query) {
global_agg_filter.filter('multi_match', {
query: query.query,
// @TODO rename field to query_fields
fields: query.fields,
// @TODO rename to query_operator
operator: query.operator
});
}

if (query.ids) {
global_agg_filter.filter('ids', { values: query.ids });
}

if (query.query_string) {
global_agg_filter.filter('query_string', { query: query.query_string });
}

a.aggregation('filter', key, key, () => {
if (query.exclude_ids && Array.isArray(query.exclude_ids)) {
global_agg_filter.notFilter('ids', { values: query.exclude_ids });
}

// empty bool is slow
const filter = bodybuilder().andFilter('bool', b => {
return global_agg_filter;
});

// disjunctive filters here
global_agg(filter, aggs, facets_names_obj, filters, not_filters, query);

for (const [key2, values2] of Object.entries(filters)) {
return filter;
});

// disjunction filters for terms
if (aggs[key2] && aggs[key2].conjunction === false && aggs[key2].type !== 'range') {
b.andFilter('bool', c => {
if (key !== key2) {
for (const value2 of values2) {
c.orFilter('term', aggs[key2].field, value2);
}
return a;
});

// no slow post filter
//console.log(JSON.stringify(qb.build(), null, 2));
return qb;
};


const global_agg = function(a, aggs, facets_names_obj, filters, not_filters, query) {

for (const [key, value] of Object.entries(aggs)) {

if (value.conjunction === false) {

if (facets_names_obj === null || facets_names_obj[key]) {

a.aggregation('filter', key, key, () => {

// empty bool is slow
const filter = bodybuilder().andFilter('bool', b => {

// disjunctive filters here

for (const [key2, values2] of Object.entries(filters)) {

// disjunction filters for terms
if (aggs[key2] && aggs[key2].conjunction === false && aggs[key2].type !== 'range') {
b.andFilter('bool', c => {
if (key !== key2) {
for (const value2 of values2) {
c.orFilter('term', aggs[key2].field, value2);
}
}

return c;
});
}
return c;
});
}

// disjunction filters for range
if (aggs[key2] && aggs[key2].conjunction === false && aggs[key2].type === 'range') {
b.andFilter('bool', c => {
if (key !== key2) {
for (const value2 of values2) {
const range = aggs[key2].ranges.find(element => element.key === value2);
if (range) {
c.orFilter('range', aggs[key2].field, {
gte: range.from,
lt: range.to
});
}
// disjunction filters for range
if (aggs[key2] && aggs[key2].conjunction === false && aggs[key2].type === 'range') {
b.andFilter('bool', c => {
if (key !== key2) {
for (const value2 of values2) {
const range = aggs[key2].ranges.find(element => element.key === value2);
if (range) {
c.orFilter('range', aggs[key2].field, {
gte: range.from,
lt: range.to
});
}
}
}

return c;
});
}

// conjunction filters
if (aggs[key2] && aggs[key2].conjunction !== false) {
for (const value2 of values2) {
if (key !== key2) {
return c;
});
}

if (aggs[key2].type === 'range') {
// conjunction filters
if (aggs[key2] && aggs[key2].conjunction !== false) {
for (const value2 of values2) {
if (key !== key2) {

const range = aggs[key2].ranges.find(element => element.key === value2);
if (aggs[key2].type === 'range') {

if (range) {
if (aggs[key2].conjunction !== false) {
b.andFilter('range', aggs[key2].field, {
gte: range.from,
lt: range.to
});
}
}
const range = aggs[key2].ranges.find(element => element.key === value2);

} else {
if (range) {
if (aggs[key2].conjunction !== false) {
b.andFilter('term', aggs[key2].field, value2);
b.andFilter('range', aggs[key2].field, {
gte: range.from,
lt: range.to
});
}
}

} else {
if (aggs[key2].conjunction !== false) {
b.andFilter('term', aggs[key2].field, value2);
}
}
}
}
}
}

return b;
});

const options = {
size: value.size
};

return b;
});

const options = {
size: value.size
};

const order = value.order ? value.order : 'desc';
const order = value.order ? value.order : 'desc';

if (value.sort) {
const sort = value.sort === '_term' ? '_key' : value.sort;
if (value.sort) {
const sort = value.sort === '_term' ? '_key' : value.sort;

options.order = {
[sort]: order
};
}
options.order = {
[sort]: order
};
}

if (facets_names_obj === null || facets_names_obj[key]) {
if (facets_names_obj === null || facets_names_obj[key]) {

if (value.type === 'range') {
filter.aggregation('range', value.field, key, {
ranges: value.ranges
});
} else {
filter.aggregation('terms', value.field, key, options);
}
if (value.type === 'range') {
filter.aggregation('range', value.field, key, {
ranges: value.ranges
});
} else {
filter.aggregation('terms', value.field, key, options);
}
}

/***
/***
* global filters copy
*/
for (const [key, values] of Object.entries(not_filters)) {
for (const value of values) {

if (aggs[key].type === 'range') {
const range = aggs[key].ranges.find(element => element.key === value);
if (range) {
filter.notFilter('range', aggs[key].field, {
gte: range.from,
lt: range.to
});
}
} else {
filter.notFilter('term', aggs[key].field, value);
for (const [key, values] of Object.entries(not_filters)) {
for (const value of values) {

if (aggs[key].type === 'range') {
const range = aggs[key].ranges.find(element => element.key === value);
if (range) {
filter.notFilter('range', aggs[key].field, {
gte: range.from,
lt: range.to
});
}
} else {
filter.notFilter('term', aggs[key].field, value);
}
}
}

/*
* put it to helper function
*/
if (query.query) {
filter.filter('multi_match', {
query: query.query,
// @TODO rename field to query_fields
fields: query.fields,
// @TODO rename to query_operator
operator: query.operator
});
}

if (query.ids) {
filter.filter('ids', { values: query.ids });
}

if (query.query_string) {
filter.filter('query_string', { query: query.query_string });
}

if (query.exclude_ids && Array.isArray(query.exclude_ids)) {
filter.notFilter('ids', { values: query.exclude_ids });
}

/***
/***
* global filters copy end
*/
return filter;
});
}

return filter;
});
}
}

return a;
});

// no slow post filter
//console.log(JSON.stringify(qb.build(), null, 2));
return qb;
};
}
}
4 changes: 2 additions & 2 deletions src/helpers/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ const getAggregationsResponse = function(collection_aggs, result_aggs) {
//console.log(result_aggs);
//console.log(result_aggs.global);

if (result_aggs.global) {
if (result_aggs.global && result_aggs.global.global_filterable) {

for (const [key, value] of Object.entries(result_aggs.global)) {
for (const [key, value] of Object.entries(result_aggs.global.global_filterable)) {
if (value && value[key] && value[key].buckets) {
result_aggs[key] = value[key];
}
Expand Down
12 changes: 12 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ module.exports = function elasticitems(elastic_config, search_config) {
node: elastic_config.host
});


const build = async function(input, local_search_config) {
input = input || {};
input.per_page = input.per_page || 16;


return builder.searchBuilder(input, local_search_config || search_config).build();
}


/**
* per_page
* page
Expand Down Expand Up @@ -111,6 +121,8 @@ module.exports = function elasticitems(elastic_config, search_config) {

search: search,

build: build,

/**
* returns list of elements for specific aggregation i.e. list of tags
* name (aggregation name)
Expand Down

0 comments on commit 1562606

Please sign in to comment.