UNPKG

@nano-sql/plugin-fuzzy-search

Version:

Integrate Elastic Search style indexing in NanoSQL 2!

821 lines 55.2 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; Object.defineProperty(exports, "__esModule", { value: true }); var utilities_1 = require("@nano-sql/core/lib/utilities"); var metaphone = require("metaphone"); var stemmer = require("stemmer"); exports.stopWords = [ "a", "about", "after", "all", "also", "am", "an", "and", "andor", "another", "any", "are", "as", "at", "be", "because", "been", "before", "being", "between", "both", "but", "by", "came", "can", "come", "could", "did", "do", "each", "for", "from", "get", "got", "had", "has", "have", "he", "her", "here", "him", "himself", "his", "how", "i", "if", "in", "into", "is", "it", "like", "make", "many", "me", "might", "more", "most", "much", "must", "my", "never", "now", "of", "on", "only", "or", "other", "our", "out", "over", "said", "same", "see", "should", "since", "some", "still", "such", "take", "than", "that", "the", "their", "them", "then", "there", "these", "they", "this", "those", "through", "to", "too", "under", "up", "very", "was", "way", "we", "well", "were", "what", "where", "which", "while", "who", "with", "would", "you", "your" ]; exports.defaultTokenizer = function (type, stpWrds, decimalPoints) { if (decimalPoints === void 0) { decimalPoints = 4; } return function (tableName, tableId, path, value) { var isStopWord = function (word) { return !word || word === null ? true : // is this word falsey? (ie no length, undefined, etc); String(word).length === 1 ? true : // is this word 1 length long? stpWrds.indexOf(word) !== -1; // does word match something in the stop word list? }; // Step 1, Clean up and normalize the text var words = String(value || "") // everything to lowercase .toLowerCase() // normalize fractions and numbers (1/4 => 0.2500, 1,000,235 => 100235.0000) .replace(/(\d+)\/(\d+)|(?:\d+(?:,\d+)*|\d+)(?:\.\d+)?/gmi, function (all, top, bottom) { return top || bottom ? (parseInt(top) / parseInt(bottom)).toFixed(decimalPoints) : (parseFloat(all.replace(/\,/gmi, ""))).toFixed(decimalPoints); }) // replace dashes, underscores, anything like parantheses, slashes, newlines and tabs with a single whitespace .replace(/\-|\_|\[|\]|\(|\)|\{|\}|\r?\n|\r|\t/gmi, " ") // remove anything but letters, numbers and decimals inside numbers with nothing. .replace(/[^\w\s]|(\d\.)/gmi, "$1") // remove white spaces larger than 1 with 1 white space. .replace(/\s+/g, " ") .split(" "); // Step 2, tokenize! switch (type) { case "english": return words.map(function (w, i) { return ({ i: i, w: isNaN(w) ? (isStopWord(w) ? "" : metaphone(stemmer(w))) : w }); }).filter(function (f) { return f.w; }); case "english-stem": return words.map(function (w, i) { return ({ i: i, w: isNaN(w) ? (isStopWord(w) ? "" : stemmer(w)) : w }); }).filter(function (f) { return f.w; }); case "english-meta": return words.map(function (w, i) { return ({ i: i, w: isNaN(w) ? (isStopWord(w) ? "" : metaphone(w)) : w }); }).filter(function (f) { return f.w; }); } // no tokenization: 2,684 words/ms return words.map(function (w, i) { return ({ w: w, i: i }); }); }; }; var searchIndexes = {}; var indexLocks = {}; var addRowToFuzzy = function (newRow, tableId, pkPath, nSQL, query, complete) { var newRowData = newRow; var newRowPK = utilities_1.deepGet(pkPath, newRowData); var filters = utilities_1.adapterFilters(query.databaseID, nSQL, query); utilities_1.allAsync(searchIndexes[tableId], function (item, i, next, err) { var phrase = utilities_1.deepGet(item.path, newRowData); if (typeof phrase !== "string" || !phrase) { // nothing to index next(); return; } var phraseHash = utilities_1.hash(phrase); var tokens = item.tokenizer(item.tableName, item.tableId, item.path, phrase); var mergedTokens = tokens.reduce(function (prev, cur) { if (!prev[cur.w]) { prev[cur.w] = []; } prev[cur.w].push(cur.i); return prev; }, {}); var tokenArray = tokens.map(function (s) { return s.i + ":" + s.w; }); var indexTableNameWords = "_" + tableId + "_fuzzy_words_" + item.path.join("."); var indexTableNameWordsId = nSQL.getDB(query.databaseID)._tableIds[indexTableNameWords]; // write cache of row index data filters.write(indexTableNameWords, newRowPK, { id: newRowPK, hash: phraseHash, tokens: tokenArray }, function () { // write words to index var indexTable = "_" + tableId + "_fuzzy_" + item.path.join("."); var indexTableId = nSQL.getDB(query.databaseID)._tableIds[indexTable]; utilities_1.allAsync(Object.keys(mergedTokens), function (word, k, nextToken, errToken) { var writeToken = function () { filters.read(indexTable, word, function (tokenRow) { var useRow = utilities_1.maybeAssign(tokenRow) || { wrd: word, ids: [] }; useRow.ids.push({ id: newRowPK, i: mergedTokens[word] }); filters.write(indexTable, word, useRow, function () { delete indexLocks[tableId][word]; nextToken(); }, function (err) { delete indexLocks[tableId][word]; errToken(err); }); }, function (err) { delete indexLocks[tableId][word]; errToken(err); }); }; var checkTokenLock = function () { if (indexLocks[tableId][word]) { setTimeout(function () { checkTokenLock(); }, 2); } else { indexLocks[tableId][word] = true; writeToken(); } }; checkTokenLock(); }).then(next).catch(err); }, err); }).then(function () { complete(); }).catch(complete); }; var rmRowFromFuzzy = function (newRowData, pkPath, databaseID, nSQL, query, tableId, complete) { var newRowPK = utilities_1.deepGet(pkPath, newRowData); var filters = utilities_1.adapterFilters(databaseID, nSQL, query); utilities_1.allAsync(searchIndexes[tableId], function (item, i, next, err) { var indexTableNameWords = "_" + tableId + "_fuzzy_words_" + item.path.join("."); var indexTableNameWordsId = nSQL.getDB(databaseID)._tableIds[indexTableNameWords]; // remove row data cache filters.delete(indexTableNameWords, newRowPK, function () { var phrase = utilities_1.deepGet(item.path, newRowData); if (typeof phrase !== "string" || !phrase) { // nothing to delete next(); return; } var indexTable = "_" + tableId + "_fuzzy_" + item.path.join("."); var indexTableId = nSQL.getDB(databaseID)._tableIds[indexTable]; var tokens = item.tokenizer(item.tableName, item.tableId, item.path, phrase); var mergedTokens = tokens.reduce(function (prev, cur) { if (!prev[cur.w]) { prev[cur.w] = []; } prev[cur.w].push(cur.i); return prev; }, {}); utilities_1.allAsync(Object.keys(mergedTokens), function (word, k, nextToken, errToken) { var writeToken = function () { filters.read(indexTable, word, function (tokenRow) { var useRow = utilities_1.maybeAssign(tokenRow) || { wrd: word, ids: [] }; var idx = -1; var i = useRow.ids.length; while (i-- && idx === -1) { if (useRow.ids[i].id === newRowPK) { idx === i; } } if (idx !== -1) { useRow.ids.splice(idx, 1); if (!useRow.ids.length) { // no rows left for this token filters.delete(indexTable, word, function () { delete indexLocks[tableId][word]; nextToken(); }, function (err) { delete indexLocks[tableId][word]; errToken(err); }); } else { // write other row data back filters.write(indexTable, word, useRow, function () { delete indexLocks[tableId][word]; nextToken(); }, function (err) { delete indexLocks[tableId][word]; errToken(err); }); } } else { delete indexLocks[tableId][word]; nextToken(); } }, function (err) { delete indexLocks[tableId][word]; errToken(err); }); }; var checkTokenLock = function () { if (indexLocks[tableId][word]) { setTimeout(function () { checkTokenLock(); }, 2); } else { indexLocks[tableId][word] = true; writeToken(); } }; checkTokenLock(); }).then(next).catch(err); }, err); }).then(function () { complete(); }).catch(complete); }; exports.FuzzyUserSanitize = function (str) { return String(str).replace(/\'|\,/gmi, ""); }; exports.FuzzySearch = function () { var nSQL; return { name: "Fuzzy Search", version: 2.02, filters: [ { name: "willConnect", priority: 1000, call: function (inputArgs, complete, cancel) { nSQL = inputArgs.res; complete(inputArgs); nSQL.functions["SEARCH"] = { type: "S", call: function (query, row, prev, gpsCol, lat, lon) { // no standard usage return { result: 0 }; }, checkIndex: function (query, fnArgs, where) { var tableId = nSQL.getDB(query.databaseID)._tableIds[query.table]; // no indexes on this table if (!searchIndexes[tableId] || !searchIndexes[tableId].length) { return false; } // search all fuzzy indexes if (fnArgs[0] === "*") { return { index: "*", parsedFn: { name: "SEARCH", args: fnArgs }, comp: where[1], value: where[2], col: String(-1) }; } var indexPath = utilities_1.resolvePath(fnArgs[0]); var indexJoined = indexPath.join("."); var idx = -1; var i = searchIndexes[tableId].length; while (i-- && idx === -1) { if (searchIndexes[tableId][i].path.join(".") == indexJoined) { idx = i; } } // no matching index found if (idx === -1) { return false; } return { index: searchIndexes[tableId][idx].indexId, parsedFn: { name: "SEARCH", args: fnArgs }, comp: where[1], value: where[2], col: String(idx) }; }, queryIndex: function (query, where, onlyPKs, onRow, complete, error) { var filters = utilities_1.adapterFilters(query.databaseID, nSQL, query); var getRows = function (PKs) { PKs = PKs.filter(function (v, i, s) { return s.indexOf(v) === i; }); if (!PKs.length) { complete(); return; } if (onlyPKs) { var kk = 0; while (kk < PKs.length) { onRow(PKs[kk], kk); kk++; } complete(); } utilities_1.chainAsync(PKs, function (rowKey, i, next, err) { var counter = 0; filters.read(query.table, rowKey, function (row) { if (!row) { next(); return; } onRow(row, counter); counter++; next(); }, err); }).then(function () { complete(); }).catch(error); }; var tableId = nSQL.getDB(query.databaseID)._tableIds[query.table]; var searchTheseIndexes = (parseInt(where.col) !== -1 ? [where.index] : searchIndexes[tableId].map(function (s) { return s.indexId; })); var secondaryIndexPKS = []; var getNextSearchTerm = function (searchIdx, indexNum) { // search done if (!searchTheseIndexes[indexNum]) { getRows(secondaryIndexPKS); return; } // out of search terms for this index // move to next index if (!where.parsedFn.args[searchIdx]) { getNextSearchTerm(1, indexNum + 1); return; } // strip trailing and leading quotes from search term var searchValue = where.parsedFn.args[searchIdx]; var quoteRegex = /^[\"|\'](.*)[\"|\']$/gmi; var hasQuotes = quoteRegex.exec(searchValue); var useSeachValue = hasQuotes ? hasQuotes[1] : searchValue; var useIndex = searchIndexes[tableId][indexNum]; if (!useIndex) { error("Erro getting fuzzy index!"); return; } var phraseTokens = useIndex.tokenizer(useIndex.tableName, useIndex.tableId, useIndex.path, useSeachValue); // blank search term, cya! if (!phraseTokens.length) { complete(); return; } var mergedTokens = phraseTokens.reduce(function (prev, cur) { if (!prev[cur.w]) { prev[cur.w] = []; } prev[cur.w].push(cur.i); return prev; }, {}); var tokenLocs = {}; var wordLocs = {}; // get exact matches from secondary index filters.readIndexKey(query.table, searchTheseIndexes[indexNum], useSeachValue, function (pk) { secondaryIndexPKS.push(pk); }, function () { // now do tokenization and search utilities_1.allAsync(Object.keys(mergedTokens), function (word, i, nextWord, errWord) { var indexTable = "_" + tableId + "_fuzzy_" + useIndex.path.join("."); var indexTableId = nSQL.getDB(query.databaseID)._tableIds[indexTable]; filters.read(indexTable, word, function (tokenRow) { var useRow = tokenRow || { wrd: word, ids: [] }; useRow.ids.forEach(function (rowData) { if (!wordLocs[String(rowData.id)]) { wordLocs[String(rowData.id)] = { rowKey: rowData.id, words: {} }; } wordLocs[String(rowData.id)].words[useRow.wrd] = rowData.i; }); tokenLocs[word] = useRow; nextWord(); }, errWord); }).then(function () { if (phraseTokens.length <= 1) { // single word search (easy and quick) if ((where.comp === "=" || where.comp.indexOf("=") !== -1) && where.comp !== "!=" && where.value === 0) { // doing exact match Object.keys(tokenLocs).forEach(function (word) { secondaryIndexPKS = secondaryIndexPKS.concat(tokenLocs[word].ids.map(function (r) { return r.id; })); }); getNextSearchTerm(searchIdx + 1, indexNum); } else { if (secondaryIndexPKS.length) { getNextSearchTerm(searchIdx + 1, indexNum); } else { // no matches getNextSearchTerm(searchIdx + 1, indexNum); } } } else { // phrase search (oh boy) var phraseWords_1 = phraseTokens.map(function (s) { return s.w; }); var phraseLocs_1 = phraseTokens.map(function (s) { return s.i; }); var rowSlots_1 = []; Object.keys(wordLocs).forEach(function (rowID) { var rowData = wordLocs[rowID]; var newSlot = { rowKey: rowData.rowKey, wordSlots: [] }; phraseWords_1.forEach(function (phraseWord, jj) { newSlot.wordSlots[jj] = rowData.words[phraseWord] || []; }); rowSlots_1.push(newSlot); }); rowSlots_1.forEach(function (slot) { // best score === 0; var score = 100000; /* slot.wordSlots contains an array of phrase matches, in order of the search phrase, found in this row. each wordSlot contains all the locations for the word match in the target row we're going to scroll through every possible combination of matched phrases to find the best matches */ var recursiveMatch = function () { var positions = []; for (var _i = 0; _i < arguments.length; _i++) { positions[_i] = arguments[_i]; } var baseScore = 0; // checkLocs contains the phrase locations we are testing var checkLocs = slot.wordSlots.map(function (s, i) { if (!slot.wordSlots[i].length) { baseScore += 1; } return slot.wordSlots[i][positions[i]]; }); var firstPoint; var phrasePoint; for (var i = 0; i < checkLocs.length; i++) { if (checkLocs[i] === undefined) { baseScore++; } else { if (firstPoint === undefined) { firstPoint = checkLocs[i]; phrasePoint = phraseLocs_1[i]; } else { var diff = Math.abs((checkLocs[i] - firstPoint) - 1); var phraseDiff = Math.abs((phraseLocs_1[i] - phrasePoint) - 1); baseScore += Math.abs(diff - phraseDiff); firstPoint = checkLocs[i]; phrasePoint = phraseLocs_1[i]; } } } // set up score score = Math.min(score, baseScore); // setup next loop // start jumping to the next item on the last phrase, then the next phrase... var nextAdjustPos = slot.wordSlots.length - 1; var nextIndex = positions[nextAdjustPos] + 1; while (nextAdjustPos >= 0 && !slot.wordSlots[nextAdjustPos][nextIndex]) { nextAdjustPos--; nextIndex = positions[nextAdjustPos] + 1; } var newPos = positions.slice(); newPos[nextAdjustPos] = nextIndex; // if this is undefined we've ran out of comparisons if (newPos[nextAdjustPos]) { recursiveMatch.apply(void 0, newPos); } }; // start at position 0, 0, 0, 0, 0... recursiveMatch.apply(void 0, slot.wordSlots.map(function (s) { return 0; })); switch (where.comp) { case "=": if (score === where.value) { secondaryIndexPKS.push(slot.rowKey); } break; case ">=": if (score >= where.value) { secondaryIndexPKS.push(slot.rowKey); } break; case "<=": if (score <= where.value) { secondaryIndexPKS.push(slot.rowKey); } break; case "<": if (score < where.value) { secondaryIndexPKS.push(slot.rowKey); } break; case ">": if (score > where.value) { secondaryIndexPKS.push(slot.rowKey); } break; case "!=": if (score !== where.value) { secondaryIndexPKS.push(slot.rowKey); } break; } }); getNextSearchTerm(searchIdx + 1, indexNum); } }); }, error); }; getNextSearchTerm(1, 0); } }; } }, { name: "configTableSystem", priority: 1000, call: function (inputArgs, complete, cancel) { var tableId = inputArgs.res.id; var tableName = inputArgs.res.name; var pkKey = "id:" + inputArgs.res.pkType; if (inputArgs.res.name.indexOf("_") === 0) { complete(inputArgs); return; } var indexes = inputArgs.res.indexes || {}; searchIndexes[tableId] = []; indexLocks[tableId] = {}; Object.keys(indexes).forEach(function (indexId) { if (indexes[indexId].props.search) { if (typeof indexes[indexId].props.search === "boolean") { searchIndexes[tableId].push({ indexId: indexId, tableName: tableName, tableId: tableId, path: indexes[indexId].path, tokenizer: exports.defaultTokenizer("english", exports.stopWords) }); } else { searchIndexes[tableId].push({ indexId: indexId, tableName: tableName, tableId: tableId, path: indexes[indexId].path, tokenizer: indexes[indexId].props.search.tokenizer || exports.defaultTokenizer("english", exports.stopWords) }); } } }); utilities_1.allAsync(searchIndexes[tableId], function (item, i, next, err) { var _a; var indexTableName = "_" + tableId + "_fuzzy_" + item.path.join("."); // store the data for each token nSQL.triggerQuery(inputArgs.query.databaseID, __assign({}, utilities_1.buildQuery(inputArgs.query.databaseID, nSQL, "", "create table"), { actionArgs: { name: indexTableName, _internal: true, model: { "wrd:string": { pk: true }, "ids:any[]": { notNull: true, model: (_a = {}, _a[pkKey] = {}, _a["i:int[]"] = {}, _a) } } } }), utilities_1.noop, function () { var _a; // store the locations of each token for every row // allows us to diff updates var indexTableNameWords = "_" + tableId + "_fuzzy_words_" + item.path.join("."); nSQL.triggerQuery(inputArgs.query.databaseID, __assign({}, utilities_1.buildQuery(inputArgs.query.databaseID, nSQL, "", "create table"), { actionArgs: { name: indexTableNameWords, _internal: true, model: (_a = {}, _a[pkKey] = { pk: true }, _a["hash:string"] = {}, _a["tokens:any"] = {}, _a) } }), utilities_1.noop, next, err); }, err); }).then(function () { complete(inputArgs); }).catch(function (err) { cancel(err); }); } }, { name: "addRowEvent", priority: 1000, call: function (inputArgs, complete, cancel) { var tableId = nSQL.getDB(inputArgs.query.databaseID)._tableIds[inputArgs.query.table]; var pkPath = nSQL.getDB(inputArgs.query.databaseID)._tables[inputArgs.query.table].pkCol; if (!searchIndexes[tableId] || !searchIndexes[tableId].length) { // no indexes for this table complete(inputArgs); } else { // add new row addRowToFuzzy(inputArgs.res.result, tableId, pkPath, nSQL, inputArgs.query, function (err) { if (err) { cancel(err); } else { complete(inputArgs); } }); } } }, { name: "updateRow", priority: 1000, call: function (inputArgs, complete, cancel) { var tableId = nSQL.getDB(inputArgs.query.databaseID)._tableIds[inputArgs.query.table]; var pkPath = nSQL.getDB(inputArgs.query.databaseID)._tables[inputArgs.query.table].pkCol; if (!searchIndexes[tableId] || !searchIndexes[tableId].length) { // no indexes for this table complete(inputArgs); } else { // update row data var newRowData_1 = inputArgs.res; var newRowPK_1 = utilities_1.deepGet(pkPath, newRowData_1); var filters_1 = utilities_1.adapterFilters(inputArgs.query.databaseID, nSQL, inputArgs.query); utilities_1.allAsync(searchIndexes[tableId], function (item, i, next, err) { var indexTableNameWords = "_" + tableId + "_fuzzy_words_" + item.path.join("."); var indexTableNameWordsId = nSQL.getDB(inputArgs.query.databaseID)._tableIds[indexTableNameWords]; // read row cache filters_1.read(indexTableNameWords, newRowPK_1, function (row) { var useRow = utilities_1.maybeAssign(row) || { id: newRowPK_1, hash: "", tokens: [] }; var phrase = utilities_1.deepGet(item.path, newRowData_1); // new and old are both empty if (!useRow.hash && (!phrase || typeof phrase !== "string")) { next(); return; } var phraseHash = utilities_1.hash(String(phrase)); if (phraseHash === useRow.hash) { // no changes in index data next(); return; } var indexTable = "_" + tableId + "_fuzzy_" + item.path.join("."); var indexTableId = nSQL.getDB(inputArgs.query.databaseID)._tableIds[indexTable]; var tokens = item.tokenizer(item.tableName, item.tableId, item.path, phrase); var tokenArray = tokens.map(function (s) { return s.i + ":" + s.w; }); var deleteTokens = useRow.tokens.filter(function (t) { return tokenArray.indexOf(t) === -1; }); var addTokens = tokenArray.filter(function (t) { return useRow.tokens.indexOf(t) === -1; }); // adjust tokens utilities_1.allAsync([deleteTokens, addTokens], function (arr, kk, nextArr, errArr) { var cleanedTokens = arr.map(function (s) { var sp = s.split(":"); var thisPos = sp.shift(); // shift off index return { w: sp.join(":"), i: parseInt(thisPos) }; }); var mergedTokens = cleanedTokens.reduce(function (prev, cur) { if (!prev[cur.w]) { prev[cur.w] = []; } prev[cur.w].push(cur.i); return prev; }, {}); utilities_1.allAsync(Object.keys(mergedTokens), function (word, k, nextToken, errToken) { var writeToken = function () { filters_1.read(indexTable, word, function (tokenRow) { var useRow = utilities_1.maybeAssign(tokenRow) || { wrd: word, ids: [] }; var idx = -1; var i = 0; while (i < useRow.ids.length && idx === -1) { if (useRow.ids[i].id === newRowPK_1) { idx = i; } i++; } if (idx !== -1) { if (kk === 0) { // deleting useRow.ids[idx].i = useRow.ids[idx].i.filter(function (pos) { return mergedTokens[word].indexOf(pos) === -1; }); } else { // adding useRow.ids[idx].i = useRow.ids[idx].i.concat(mergedTokens[word]); } // remove row from index data if it has no index values left if (!useRow.ids[idx].i.length) { useRow.ids.splice(idx, 1); } // no row values left for this token if (!useRow.ids.length) { filters_1.delete(indexTable, word, function () { delete indexLocks[tableId][word]; nextToken(); }, function (err) { delete indexLocks[tableId][word]; errToken(err); }); } else { filters_1.write(indexTable, word, useRow, function () { delete indexLocks[tableId][word]; nextToken(); }, function (err) { delete indexLocks[tableId][word]; errToken(err); }); } } else { if (kk === 1) { // adding useRow.ids.push({ id: newRowPK_1, i: mergedTokens[word] }); filters_1.write(indexTable, word, useRow, function () { delete indexLocks[tableId][word]; nextToken(); }, function (err) { delete indexLocks[tableId][word]; errToken(err); }); } else { // deleting delete indexLocks[tableId][word]; nextToken(); } } }, function (err) { delete indexLocks[tableId][word]; errToken(err); }); }; var checkTokenLock = function () { if (indexLocks[tableId][word]) { setTimeout(function () { checkTokenLock(); }, 2); } else { indexLocks[tableId][word] = true; writeToken(); } }; checkTokenLock(); }).then(next).catch(err); }).then(function () { // write new cache data filters_1.write(indexTableNameWords, newRowPK_1, { id: newRowPK_1, hash: phraseHash, tokens: tokenArray }, next, err); }); }, err); }).then(function () { complete(inputArgs); }).catch(cancel); } } }, { name: "deleteRow", priority: 1000, call: function (inputArgs, complete, cancel) { var tableId = nSQL.getDB(inputArgs.query.databaseID)._tableIds[inputArgs.query.table]; var pkPath = nSQL.getDB(inputArgs.query.databaseID)._tables[inputArgs.query.table].pkCol; if (!searchIndexes[tableId] || !searchIndexes[tableId].length) { // no indexes for this table complete(inputArgs); } else { // delete row data rmRowFromFuzzy(inputArgs.res, pkPath, inputArgs.query.databaseID, nSQL, inputArgs.query, tableId, function (err) { if (err) { cancel(err); } else { complete(inputArgs); } }); } } }, { name: "customQuery", priority: 1000, call: function (inputArgs, complete, cancel) { if (String(inputArgs.query.action).trim().toLowerCase() === "rebuild search") { // capture rebuild search var table = inputArgs.query.table; if (typeof table !== "string") { inputArgs.error("Can't rebuild search on this table type!"); } else { var tableId_1 = nSQL.getDB(inputArgs.query.databaseID)._tableIds[inputArgs.query.table]; var pkPath_1 = nSQL.getDB(inputArgs.query.databaseID)._tables[inputArgs.query.table].pkCol; if (!searchIndexes[tableId_1] || !searchIndexes[tableId_1].length) { // no indexes for this table inputArgs.complete(); } else { // rebuild indexes if (inputArgs.query.where) { // rebuild specific indexes var rebuildQ_1 = new utilities_1._nanoSQLQueue(function (item, i, done, error) { rmRowFromFuzzy(item, pkPath_1, inputArgs.query.databaseID, nSQL, inputArgs.query, tableId_1, function (err) { if (err) { error(err); } else { addRowToFuzzy(item, tableId_1, pkPath_1, nSQL, inputArgs.query, function (err2) { if (err2) { error(err2); } else { done(); } }); } }); }, inputArgs.error, function () { inputArgs.complete(); }); nSQL.triggerQuery(inputArgs