search now supports searching / ordering by note size

This commit is contained in:
zadam 2021-01-22 22:20:17 +01:00
parent 480aec1667
commit 872e81fe1f
9 changed files with 93 additions and 20 deletions

View File

@ -172,9 +172,6 @@ class TreeCache {
throw new Error(`Search note ${note.noteId} failed: ${searchResultNoteIds}`);
}
// force to load all the notes at once instead of one by one
await this.getNotes(searchResultNoteIds);
// reset all the virtual branches from old search results
if (note.noteId in treeCache.notes) {
treeCache.notes[note.noteId].children = [];

View File

@ -164,6 +164,9 @@ const TPL = `
<option value="title">Title</option>
<option value="dateCreated">Date created</option>
<option value="dateModified">Date of last modification</option>
<option value="contentSize">Note content size</option>
<option value="noteSize">Note content size including revisions</option>
<option value="revisionCount">Number of revisions</option>
</select>
<select name="orderDirection" class="form-control w-auto d-inline">

View File

@ -15,7 +15,7 @@ async function search(note) {
if (searchScript) {
searchResultNoteIds = await searchFromRelation(note, 'searchScript');
} else if (searchString) {
} else {
const searchContext = new SearchContext({
fastSearch: note.hasLabel('fastSearch'),
ancestorNoteId: note.getRelationValue('ancestor'),
@ -27,8 +27,6 @@ async function search(note) {
searchResultNoteIds = searchService.findNotesWithQuery(searchString, searchContext)
.map(sr => sr.noteId);
} else {
searchResultNoteIds = [];
}
// we won't return search note's own noteId

View File

@ -30,6 +30,15 @@ class Note {
/** @param {Note[]|null} */
this.ancestorCache = null;
// following attributes are filled during searching from database
/** @param {int} size of the content in bytes */
this.contentSize = null;
/** @param {int} size of the content and note revision contents in bytes */
this.noteSize = null;
/** @param {int} number of note revisions for this note */
this.revisionCount = null;
}
update(row) {

View File

@ -22,7 +22,10 @@ const PROP_MAPPING = {
"childrencount": "childrenCount",
"attributecount": "attributeCount",
"labelcount": "labelCount",
"relationcount": "relationCount"
"relationcount": "relationCount",
"contentsize": "contentSize",
"notesize": "noteSize",
"revisioncount": "revisionCount"
};
class PropertyComparisonExp extends Expression {
@ -30,11 +33,15 @@ class PropertyComparisonExp extends Expression {
return name in PROP_MAPPING;
}
constructor(propertyName, comparator) {
constructor(searchContext, propertyName, comparator) {
super();
this.propertyName = PROP_MAPPING[propertyName];
this.comparator = comparator;
if (['contentsize', 'notesize', 'revisioncount'].includes(this.propertyName)) {
searchContext.dbLoadNeeded = true;
}
}
execute(inputNoteSet, executionContext) {

View File

@ -10,6 +10,9 @@ class SearchContext {
this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch;
this.highlightedTokens = [];
this.originalQuery = "";
// if true, note cache does not have (up-to-date) information needed to process the query
// and some extra data needs to be loaded before executing
this.dbLoadNeeded = false;
this.error = null;
}

View File

@ -192,7 +192,7 @@ function getExpression(tokens, searchContext, level = 0) {
i += 2;
return new OrExp([
new PropertyComparisonExp('title', buildComparator('*=*', tokens[i].token)),
new PropertyComparisonExp(searchContext, 'title', buildComparator('*=*', tokens[i].token)),
new NoteContentProtectedFulltextExp('*=*', [tokens[i].token]),
new NoteContentUnprotectedFulltextExp('*=*', [tokens[i].token])
]);
@ -213,7 +213,7 @@ function getExpression(tokens, searchContext, level = 0) {
return;
}
return new PropertyComparisonExp(propertyName, comparator);
return new PropertyComparisonExp(searchContext, propertyName, comparator);
}
searchContext.addError(`Unrecognized note property "${tokens[i].token}" in ${context(i)}`);
@ -307,7 +307,7 @@ function getExpression(tokens, searchContext, level = 0) {
i++;
}
const valueExtractor = new ValueExtractor(propertyPath);
const valueExtractor = new ValueExtractor(searchContext, propertyPath);
if (valueExtractor.validate()) {
searchContext.addError(valueExtractor.validate());
@ -409,7 +409,7 @@ function getExpression(tokens, searchContext, level = 0) {
function parse({fulltextTokens, expressionTokens, searchContext}) {
let exp = AndExp.of([
searchContext.includeArchivedNotes ? null : new PropertyComparisonExp("isarchived", buildComparator("=", "false")),
searchContext.includeArchivedNotes ? null : new PropertyComparisonExp(searchContext, "isarchived", buildComparator("=", "false")),
searchContext.ancestorNoteId ? new AncestorExp(searchContext.ancestorNoteId) : null,
getFulltext(fulltextTokens, searchContext),
getExpression(expressionTokens, searchContext)
@ -419,7 +419,7 @@ function parse({fulltextTokens, expressionTokens, searchContext}) {
const filterExp = exp;
exp = new OrderByAndLimitExp([{
valueExtractor: new ValueExtractor(['note', searchContext.orderBy]),
valueExtractor: new ValueExtractor(searchContext, ['note', searchContext.orderBy]),
direction: searchContext.orderDirection
}], 0);

View File

@ -10,6 +10,54 @@ const noteCache = require('../../note_cache/note_cache.js');
const noteCacheService = require('../../note_cache/note_cache_service.js');
const utils = require('../../utils.js');
const cls = require('../../cls.js');
const log = require('../../log.js');
function loadNeededInfoFromDatabase() {
const sql = require('../../sql.js');
for (const noteId in noteCache.notes) {
noteCache.notes[noteId].contentSize = 0;
noteCache.notes[noteId].noteSize = 0;
noteCache.notes[noteId].revisionCount = 0;
}
const noteContentLengths = sql.getRows(`
SELECT
noteId,
LENGTH(content) AS length
FROM notes
JOIN note_contents USING(noteId)
WHERE notes.isDeleted = 0`);
for (const {noteId, length} of noteContentLengths) {
if (!(noteId in noteCache.notes)) {
log.error(`Note ${noteId} not found in note cache.`);
continue;
}
noteCache.notes[noteId].contentSize = length;
noteCache.notes[noteId].noteSize = length;
}
const noteRevisionContentLengths = sql.getRows(`
SELECT
noteId,
LENGTH(content) AS length
FROM notes
JOIN note_revisions USING(noteId)
JOIN note_revision_contents USING(noteRevisionId)
WHERE notes.isDeleted = 0`);
for (const {noteId, length} of noteRevisionContentLengths) {
if (!(noteId in noteCache.notes)) {
log.error(`Note ${noteId} not found in note cache.`);
continue;
}
noteCache.notes[noteId].noteSize += length;
noteCache.notes[noteId].revisionCount++;
}
}
/**
* @param {Expression} expression
@ -22,6 +70,10 @@ function findNotesWithExpression(expression, searchContext) {
? hoistedNote.subtreeNotes
: Object.values(noteCache.notes);
if (searchContext.dbLoadNeeded) {
loadNeededInfoFromDatabase();
}
// in the process of loading data sometimes we create "skeleton" note instances which are expected to be filled later
// in case of inconsistent data this might not work and search will then crash on these
allNotes = allNotes.filter(note => note.type !== undefined);
@ -84,10 +136,7 @@ function parseQueryToExpression(query, searchContext) {
* @return {SearchResult[]}
*/
function findNotesWithQuery(query, searchContext) {
if (!query.trim().length) {
return [];
}
query = query || "";
searchContext.originalQuery = query;
const expression = parseQueryToExpression(query, searchContext);

View File

@ -19,18 +19,25 @@ const PROP_MAPPING = {
"childrencount": "childrenCount",
"attributecount": "attributeCount",
"labelcount": "labelCount",
"relationcount": "relationCount"
"relationcount": "relationCount",
"contentsize": "contentSize",
"notesize": "noteSize",
"revisioncount": "revisionCount"
};
class ValueExtractor {
constructor(propertyPath) {
constructor(searchContext, propertyPath) {
this.propertyPath = propertyPath.map(pathEl => pathEl.toLowerCase());
if (this.propertyPath[0].startsWith('#')) {
this.propertyPath = ['note', 'labels', this.propertyPath[0].substr(1), ...this.propertyPath.slice( 1, this.propertyPath.length)];
}
else if (this.propertyPath[0].startsWith('~')) {
this.propertyPath = ['note', 'relations', this.propertyPath[0].substr(1), ...this.propertyPath.slice( 1, this.propertyPath.length)];
this.propertyPath = ['note', 'relations', this.propertyPath[0].substr(1), ...this.propertyPath.slice(1, this.propertyPath.length)];
}
if (['contentsize', 'notesize', 'revisioncount'].includes(this.propertyPath[this.propertyPath.length - 1])) {
searchContext.dbLoadNeeded = true;
}
}