add possibility to debug search queries by logging expression tree, #1655

This commit is contained in:
zadam 2021-02-18 22:10:49 +01:00
parent 0c9a11db6f
commit 859465841d
9 changed files with 65 additions and 14 deletions

View File

@ -0,0 +1,35 @@
import AbstractSearchOption from "./abstract_search_option.js";
const TPL = `
<tr data-search-option-conf="debug">
<td colSpan="2">
<span class="bx bx-bug"></span>
Debug
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>Debug will print extra debugging information into the console to aid in debugging complex queries.</p>
<p>To access the debug information, execute query and click on "Show backend log" in top left corner.</p>
</div>
</div>
<span class="bx bx-x icon-action search-option-del"></span>
</td>
</tr>`;
export default class Debug extends AbstractSearchOption {
static get optionName() { return "debug" };
static get attributeType() { return "label" };
static async create(noteId) {
await AbstractSearchOption.setAttribute(noteId,'label', 'debug');
}
doRender() {
return $(TPL);
}
}

View File

@ -20,6 +20,7 @@ import OrderBy from "../search_options/order_by.js";
import SearchScript from "../search_options/search_script.js"; import SearchScript from "../search_options/search_script.js";
import Limit from "../search_options/limit.js"; import Limit from "../search_options/limit.js";
import DeleteNoteRevisionsSearchAction from "../search_actions/delete_note_revisions.js"; import DeleteNoteRevisionsSearchAction from "../search_actions/delete_note_revisions.js";
import Debug from "../search_options/debug.js";
const TPL = ` const TPL = `
<div class="search-definition-widget"> <div class="search-definition-widget">
@ -113,6 +114,11 @@ const TPL = `
limit limit
</button> </button>
<button type="button" class="btn btn-sm" data-search-option-add="debug" title="Debug will print extra debugging information into the console to aid in debugging complex queries">
<span class="bx bx-bug"></span>
debug
</button>
<div class="dropdown" style="display: inline-block;"> <div class="dropdown" style="display: inline-block;">
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="bx bxs-zap"></span> <span class="bx bxs-zap"></span>
@ -173,7 +179,8 @@ const OPTION_CLASSES = [
FastSearch, FastSearch,
IncludeArchivedNotes, IncludeArchivedNotes,
OrderBy, OrderBy,
Limit Limit,
Debug
]; ];
const ACTION_CLASSES = {}; const ACTION_CLASSES = {};

View File

@ -24,6 +24,7 @@ async function search(note) {
orderBy: note.getLabelValue('orderBy'), orderBy: note.getLabelValue('orderBy'),
orderDirection: note.getLabelValue('orderDirection'), orderDirection: note.getLabelValue('orderDirection'),
limit: note.getLabelValue('limit'), limit: note.getLabelValue('limit'),
debug: note.hasLabel('debug'),
fuzzyAttributeSearch: false fuzzyAttributeSearch: false
}); });

View File

@ -10,6 +10,7 @@ class AncestorExp extends Expression {
super(); super();
this.ancestorNoteId = ancestorNoteId; this.ancestorNoteId = ancestorNoteId;
this.ancestorDepth = ancestorDepth; // for DEBUG mode
this.ancestorDepthComparator = this.getComparator(ancestorDepth); this.ancestorDepthComparator = this.getComparator(ancestorDepth);
} }

View File

@ -1,6 +1,10 @@
"use strict"; "use strict";
class Expression { class Expression {
constructor() {
this.name = this.constructor.name; // for DEBUG mode to have expression name as part of dumped JSON
}
/** /**
* @param {NoteSet} inputNoteSet * @param {NoteSet} inputNoteSet
* @param {object} executionContext * @param {object} executionContext

View File

@ -2,6 +2,7 @@
const Expression = require('./expression'); const Expression = require('./expression');
const NoteSet = require('../note_set'); const NoteSet = require('../note_set');
const buildComparator = require("../services/build_comparator.js");
/** /**
* Search string is lower cased for case insensitive comparison. But when retrieving properties * Search string is lower cased for case insensitive comparison. But when retrieving properties
@ -33,11 +34,13 @@ class PropertyComparisonExp extends Expression {
return name in PROP_MAPPING; return name in PROP_MAPPING;
} }
constructor(searchContext, propertyName, comparator) { constructor(searchContext, propertyName, operator, comparedValue) {
super(); super();
this.propertyName = PROP_MAPPING[propertyName]; this.propertyName = PROP_MAPPING[propertyName];
this.comparator = comparator; this.operator = operator; // for DEBUG mode
this.comparedValue = comparedValue; // for DEBUG mode
this.comparator = buildComparator(operator, comparedValue);
if (['contentsize', 'notesize', 'revisioncount'].includes(this.propertyName)) { if (['contentsize', 'notesize', 'revisioncount'].includes(this.propertyName)) {
searchContext.dbLoadNeeded = true; searchContext.dbLoadNeeded = true;

View File

@ -11,6 +11,7 @@ class SearchContext {
this.orderBy = params.orderBy; this.orderBy = params.orderBy;
this.orderDirection = params.orderDirection; this.orderDirection = params.orderDirection;
this.limit = params.limit; this.limit = params.limit;
this.debug = params.debug;
this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch; this.fuzzyAttributeSearch = !!params.fuzzyAttributeSearch;
this.highlightedTokens = []; this.highlightedTokens = [];
this.originalQuery = ""; this.originalQuery = "";

View File

@ -194,7 +194,7 @@ function getExpression(tokens, searchContext, level = 0) {
i += 2; i += 2;
return new OrExp([ return new OrExp([
new PropertyComparisonExp(searchContext, 'title', buildComparator('*=*', tokens[i].token)), new PropertyComparisonExp(searchContext, 'title', '*=*', tokens[i].token),
new NoteContentProtectedFulltextExp('*=*', [tokens[i].token]), new NoteContentProtectedFulltextExp('*=*', [tokens[i].token]),
new NoteContentUnprotectedFulltextExp('*=*', [tokens[i].token]) new NoteContentUnprotectedFulltextExp('*=*', [tokens[i].token])
]); ]);
@ -208,14 +208,7 @@ function getExpression(tokens, searchContext, level = 0) {
const comparedValue = resolveConstantOperand(); const comparedValue = resolveConstantOperand();
const comparator = buildComparator(operator, comparedValue); return new PropertyComparisonExp(searchContext, propertyName, operator, comparedValue);
if (!comparator) {
searchContext.addError(`Can't find operator '${operator}' in ${context(i - 2)}`);
return;
}
return new PropertyComparisonExp(searchContext, propertyName, comparator);
} }
searchContext.addError(`Unrecognized note property "${tokens[i].token}" in ${context(i)}`); searchContext.addError(`Unrecognized note property "${tokens[i].token}" in ${context(i)}`);
@ -411,8 +404,8 @@ function getExpression(tokens, searchContext, level = 0) {
function parse({fulltextTokens, expressionTokens, searchContext}) { function parse({fulltextTokens, expressionTokens, searchContext}) {
let exp = AndExp.of([ let exp = AndExp.of([
searchContext.includeArchivedNotes ? null : new PropertyComparisonExp(searchContext, "isarchived", buildComparator("=", "false")), searchContext.includeArchivedNotes ? null : new PropertyComparisonExp(searchContext, "isarchived", "=", "false"),
searchContext.ancestorNoteId ? new AncestorExp(searchContext.ancestorNoteId, searchContext.ancestorDepth) : null, (searchContext.ancestorNoteId && searchContext.ancestorNoteId !== 'root') ? new AncestorExp(searchContext.ancestorNoteId, searchContext.ancestorDepth) : null,
getFulltext(fulltextTokens, searchContext), getFulltext(fulltextTokens, searchContext),
getExpression(expressionTokens, searchContext) getExpression(expressionTokens, searchContext)
]); ]);

View File

@ -127,6 +127,12 @@ function parseQueryToExpression(query, searchContext) {
originalQuery: query originalQuery: query
}); });
if (searchContext.debug) {
log.info(`Fulltext tokens: ` + JSON.stringify(fulltextTokens));
log.info(`Expression tokens: ` + JSON.stringify(structuredExpressionTokens, null, 4));
log.info("Expression tree: " + JSON.stringify(expression, null, 4));
}
return expression; return expression;
} }