1
0
mirror of https://github.com/nextapps-de/flexsearch.git synced 2025-09-25 12:58:59 +02:00
Files
flexsearch/dist/module-debug/index/search.js
Thomas Wilkerling 2431b51a4f update index.d.ts
2025-05-08 22:08:07 +02:00

452 lines
14 KiB
JavaScript

import { SearchOptions, SearchResults, EnrichedSearchResults, IntermediateSearchResults } from "../type.js";
import { create_object, is_object, sort_by_length_down } from "../common.js";
import Index from "../index.js";
import default_compress from "../compress.js";
import Resolver from "../resolver.js";
import { intersect } from "../intersect.js";
import resolve_default from "../resolve/default.js";
/**
* @param {string|SearchOptions} query
* @param {number|SearchOptions=} limit
* @param {SearchOptions=} options
* @return {
* SearchResults|EnrichedSearchResults|Resolver |
* Promise<SearchResults|EnrichedSearchResults|Resolver>
* }
*/
Index.prototype.search = function (query, limit, options) {
if (!options) {
if (!limit && "object" == typeof query) {
options = /** @type {!SearchOptions} */query;
query = "";
} else if ("object" == typeof limit) {
options = /** @type {!SearchOptions} */limit;
limit = 0;
}
}
if (options && options.cache) {
options.cache = /* suggest */ /* append: */ /* enrich */!1;
const res = this.searchCache(query, limit, options);
options.cache = /* tag? */ /* stringify */ /* stringify */ /* single param */ /* skip update: */ /* append: */ /* skip update: */ /* skip_update: */ /* skip deletion */!0 /*await rows.hasNext()*/ /*await rows.hasNext()*/ /*await rows.hasNext()*/
;
return res;
}
/** @type {!Array<IntermediateSearchResults>} */
let result = [],
length,
context,
suggest,
offset = 0,
resolve,
tag,
boost,
resolution,
// enrich is internally used just
// for the persistent indexes
enrich;
if (options) {
query = options.query || query;
limit = options.limit || limit;
offset = options.offset || 0;
context = options.context;
suggest = options.suggest;
resolve = options.resolve;
enrich = resolve && options.enrich;
boost = options.boost;
resolution = options.resolution;
tag = this.db && options.tag;
}
if ("undefined" == typeof resolve) {
resolve = this.resolve;
}
context = this.depth && !1 !== context;
// do not force a string as input
// https://github.com/nextapps-de/flexsearch/issues/432
/** @type {Array<string>} */
let query_terms = this.encoder.encode(query, !context);
length = query_terms.length;
limit = /** @type {!number} */limit || (resolve ? 100 : 0);
// fast path single term
if (1 === length) {
return single_term_query.call(this, query_terms[0], // term
"", // ctx
limit, offset, resolve, enrich, tag);
}
// fast path single context
if (2 === length && context && !suggest) {
return single_term_query.call(this, query_terms[1], // term
query_terms[0], // ctx
limit, offset, resolve, enrich, tag);
}
// let maxlength = 0;
// let minlength = 0;
//
// if(length > 1){
//
// // term deduplication will break the context chain
// // todo add context to dupe check
// const dupes = create_object();
// const query_new = [];
//
// // if(context){
// // keyword = query_terms[0];
// // dupes[keyword] = 1;
// // query_new.push(keyword);
// // maxlength = minlength = keyword.length;
// // i = 1;
// // }
//
// for(let i = 0, term; i < length; i++){
//
// term = query_terms[i];
//
// if(term && !dupes[term]){
//
// // todo add keyword check
// // this fast path can't apply to persistent indexes
// if(!suggest && !(SUPPORT_PERSISTENT && this.db) && !this.get_array(term/*, keyword*/)){
//
// // fast path "not found"
// return !SUPPORT_RESOLVER || resolve
// ? result
// : new Resolver(result);
// }
// else{
//
// query_new.push(term);
// dupes[term] = 1;
// }
//
// const term_length = term.length;
// maxlength = Math.max(maxlength, term_length);
// minlength = minlength ? Math.min(minlength, term_length) : term_length;
// }
// // else if(term && (!this.depth || context === false)){
// // query_new.push(term);
// // }
// }
//
// query_terms = query_new;
// length = query_terms.length;
// }
//
// // the term length could be changed after deduplication
//
// if(!length){
// return !SUPPORT_RESOLVER || resolve
// ? result
// : new Resolver(result);
// }
//
// // fast path single term
// if(length === 1){
// return single_term_query.call(
// this,
// query_terms[0], // term
// "", // ctx
// limit,
// offset,
// resolve,
// enrich,
// tag
// );
// }
//
// // fast path single context
// if(length === 2 && context && !suggest){
// return single_term_query.call(
// this,
// query_terms[0], // term
// query_terms[1], // ctx
// limit,
// offset,
// resolve,
// enrich,
// tag
// );
// }
let dupes = create_object(),
index = 0,
keyword;
//if(length > 1){
if (context) {
// start with context right away
keyword = query_terms[0];
index = 1;
}
// todo
// else if(maxlength > 9 && (maxlength / minlength) > 3){
// // sorting terms will break the context chain
// // bigger terms has less occurrence
// // this might also reduce the intersection task
// // todo check intersection order
// query_terms.sort(sort_by_length_down);
// }
//}
if (!resolution && 0 !== resolution) {
resolution = keyword ? this.resolution_ctx : this.resolution;
}
// from this point there are just multi-term queries
if (this.db) {
if (this.db.search) {
// when the configuration is not supported it returns false
const result = this.db.search(this, query_terms, limit, offset, suggest, resolve, enrich, tag);
if (!1 !== result) return result;
}
const self = this;
return async function () {
for (let arr, term; index < length; index++) {
term = query_terms[index];
if (term && !dupes[term]) {
dupes[term] = 1;
arr = await self.get_array(term, keyword, 0, 0, !1, !1);
arr = add_result(arr, /** @type {Array} */result, suggest, resolution);
if (arr) {
result = arr;
break;
}
if (keyword) {
// the context is a moving window where the keyword is going forward like a cursor
// 1. when suggestion enabled just forward keyword if term was found
// 2. as long as the result is empty forward the pointer also
if (!suggest || !arr || !result.length) {
keyword = term;
}
}
}
// fallback to non-contextual search when no result was found
if (suggest && keyword && index == length - 1) {
if (!result.length) {
resolution = self.resolution;
keyword = "";
index = -1;
dupes = create_object();
}
}
}
return return_result(result, resolution,
/** @type {!number} */limit, offset, suggest, boost, resolve);
}();
}
for (let arr, term; index < length; index++) {
term = query_terms[index];
// todo should the dupe check applied on [keyword:term]?
if (term && !dupes[term]) {
dupes[term] = 1;
arr = this.get_array(term, keyword, 0, 0, !1, !1);
arr = add_result(arr, /** @type {Array} */result, suggest, resolution);
if (arr) {
result = arr;
break;
}
if (keyword) {
// the context is a moving window where the keyword is going forward like a cursor
// 1. when suggestion enabled just forward keyword if term was found
// 2. as long as the result is empty forward the pointer also
if (!suggest || !arr || !result.length) {
keyword = term;
}
}
}
// fallback to non-contextual search when no result was found
if (suggest && keyword && index == length - 1) {
if (!result.length) {
resolution = this.resolution;
keyword = "";
index = -1;
dupes = create_object();
}
}
}
return return_result(result, resolution,
/** @type {!number} */limit, offset, suggest, boost, resolve);
};
/**
* @param {!Array<IntermediateSearchResults>} result
* @param {number} resolution
* @param {number} limit
* @param {number=} offset
* @param {boolean=} suggest
* @param {number=} boost
* @param {boolean=} resolve
* @return {
* SearchResults|EnrichedSearchResults|Resolver |
* Promise<SearchResults|EnrichedSearchResults|Resolver>
* }
*/
function return_result(result, resolution, limit, offset, suggest, boost, resolve) {
let length = result.length,
final = result;
if (1 < length) {
final = intersect(result, resolution, limit, offset, suggest, boost, resolve);
} else if (1 === length) {
return resolve ? resolve_default.call(null, result[0], limit, offset) : new Resolver(result[0]);
}
return resolve ? final : new Resolver(final);
}
/**
* @param {!string} term
* @param {string|null} keyword
* @param {number} limit
* @param {number=} offset
* @param {boolean=} resolve
* @param {boolean=} enrich
* @param {string=} tag
* @this {Index}
* @return {
* SearchResults|EnrichedSearchResults|Resolver |
* Promise<SearchResults|EnrichedSearchResults|Resolver>
* }
*/
function single_term_query(term, keyword, limit, offset, resolve, enrich, tag) {
const result = this.get_array(term, keyword, limit, offset, resolve, enrich, tag);
if (this.db) {
return result.then(function (result) {
return resolve ? result || [] : new Resolver(result);
});
}
return result && result.length ? resolve ? resolve_default.call(this, /** @type {SearchResults|EnrichedSearchResults} */result, limit, offset) : new Resolver(result) : resolve ? [] : new Resolver();
}
/**
* Returns a 1-dimensional finalized array when the result is done (fast path return),
* returns false when suggestions is enabled and no result was found,
* or returns nothing when a set was pushed successfully to the results
*
* @private
* @param {IntermediateSearchResults} arr
* @param {Array<IntermediateSearchResults>} result
* @param {boolean=} suggest
* @param {number=} resolution
* @return {Array|undefined}
*/
function add_result(arr, result, suggest, resolution) {
let word_arr = [];
if (arr && arr.length) {
// short when resolution does not exceed:
if (arr.length <= resolution) {
result.push(arr);
// return nothing will continue the query
return;
}
// apply reduced resolution for queries
for (let x = 0, tmp; x < resolution; x++) {
if (tmp = arr[x]) {
word_arr[x] = tmp;
}
}
if (word_arr.length) {
result.push(word_arr);
// return nothing will continue the query
return;
}
}
// 1. return an empty array will stop the loop
// 2. return a false value to prevent stop when using suggestions
if (!suggest) return word_arr;
}
/**
* @param {!string} term
* @param {string|null} keyword
* @param {number} limit
* @param {number=} offset
* @param {boolean=} resolve
* @param {boolean=} enrich
* @param {string=} tag
* @return {
* IntermediateSearchResults|EnrichedSearchResults |
* Promise<IntermediateSearchResults|EnrichedSearchResults>
* }
*/
Index.prototype.get_array = function (term, keyword, limit, offset, resolve, enrich, tag) {
let arr, swap;
if (keyword) {
swap = this.bidirectional && term > keyword;
if (swap) {
swap = keyword;
keyword = term;
term = swap;
}
}
if (this.compress) {
term = default_compress(term);
keyword && (keyword = default_compress(keyword));
}
if (this.db) {
return this.db.get(term, keyword, limit, offset, resolve, enrich, tag);
}
if (keyword) {
// the frequency of the starting letter is slightly less
// on the last half of the alphabet (m-z) in almost every latin language,
// so we sort downwards (https://en.wikipedia.org/wiki/Letter_frequency)
arr = this.ctx.get(keyword);
arr = arr && arr.get(term);
} else {
arr = this.map.get(term);
}
return arr;
};