mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	search now supports searching / ordering by note size
This commit is contained in:
		
							parent
							
								
									480aec1667
								
							
						
					
					
						commit
						872e81fe1f
					
				@ -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 = [];
 | 
			
		||||
 | 
			
		||||
@ -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">
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user