From 151641b659b7c026d19ea5814076ff03dc6e1fc2 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 21 Apr 2019 11:54:13 +0200 Subject: [PATCH] fulltext search now doesn't use FTS5, closes #489 --- src/services/build_search_query.js | 28 ++++++++--------- src/services/parse_filters.js | 50 +++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/services/build_search_query.js b/src/services/build_search_query.js index f81a6ff33..9ec202423 100644 --- a/src/services/build_search_query.js +++ b/src/services/build_search_query.js @@ -82,18 +82,8 @@ module.exports = function(filters, selectedColumns = 'notes.*') { where += `${accessor} IS NULL`; } else if (filter.operator === '=' || filter.operator === '!=') { - if (filter.name === 'text') { - const safeSearchText = utils.sanitizeSql(filter.value); - const not = filter.operator.includes("!") ? "NOT" : ""; - - // fulltext needs to use subselect because fulltext doesn't support OR operations at all - // which makes it impossible to combine more operations together - where += `notes.noteId ${not} IN (SELECT noteId FROM note_fulltext WHERE note_fulltext MATCH '${safeSearchText}')`; - } - else { - where += `${accessor} ${filter.operator} ?`; - params.push(filter.value); - } + where += `${accessor} ${filter.operator} ?`; + params.push(filter.value); } else if (filter.operator === '*=' || filter.operator === '!*=') { where += `${accessor}` @@ -106,9 +96,17 @@ module.exports = function(filters, selectedColumns = 'notes.*') { + ` LIKE ` + utils.prepareSqlForLike('', filter.value, '%'); } else if (filter.operator === '*=*' || filter.operator === '!*=*') { - where += `${accessor}` - + (filter.operator.includes('!') ? ' NOT' : '') - + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, '%'); + const columns = filter.name === 'text' ? [getAccessor("title"), getAccessor("content")] : [accessor]; + + let condition = "(" + columns.map(column => + `${column}` + ` LIKE ` + utils.prepareSqlForLike('%', filter.value, '%')) + .join(" OR ") + ")"; + + if (filter.operator.includes('!')) { + condition = "NOT(" + condition + ")"; + } + + where += condition; } else if ([">", ">=", "<", "<="].includes(filter.operator)) { let floatParam; diff --git a/src/services/parse_filters.js b/src/services/parse_filters.js index e6c04beca..c46cd207b 100644 --- a/src/services/parse_filters.js +++ b/src/services/parse_filters.js @@ -43,25 +43,45 @@ function calculateSmartValue(v) { } module.exports = function (searchText) { - // if the string doesn't start with attribute then we consider it as just standard full text search - if (!searchText.trim().startsWith("@")) { - // replace with space instead of empty string since these characters are probably separators - const sanitizedSearchText = searchText.replace(/[^\w ]+/g, " "); + searchText = searchText.trim(); - return [ - { + // if the string doesn't start with attribute then we consider it as just standard full text search + if (!searchText.startsWith("@")) { + // replace with space instead of empty string since these characters are probably separators + const filters = []; + + if (searchText.startsWith('"') && searchText.endsWith('"')) { + // "bla bla" will search for exact match + searchText = searchText.substr(1, searchText.length - 2); + + filters.push({ relation: 'and', name: 'text', - operator: '=', - value: sanitizedSearchText - }, - { - relation: 'or', - name: 'noteId', - operator: '=', - value: sanitizedSearchText + operator: '*=*', + value: searchText + }); + } + else { + const tokens = searchText.split(/\s+/); + + for (const token of tokens) { + filters.push({ + relation: 'and', + name: 'text', + operator: '*=*', + value: token + }); } - ] + } + + filters.push({ + relation: 'or', + name: 'noteId', + operator: '=', + value: searchText + }); + + return filters; } const filters = [];