server-ts: Port services/search/expressions/note_content_fulltext

This commit is contained in:
Elian Doran 2024-02-18 01:01:17 +02:00
parent 3df6acda32
commit 414964e791
No known key found for this signature in database
4 changed files with 41 additions and 23 deletions

View File

@ -106,6 +106,7 @@ export interface NoteRow {
dateModified: string; dateModified: string;
utcDateCreated: string; utcDateCreated: string;
utcDateModified: string; utcDateModified: string;
content?: string;
} }
export interface AttributeRow { export interface AttributeRow {

View File

@ -1,18 +1,22 @@
"use strict"; "use strict";
const Expression = require('./expression'); import { NoteRow } from "../../../becca/entities/rows";
const NoteSet = require('../note_set'); import SearchContext = require("../search_context");
const log = require('../../log');
const becca = require('../../../becca/becca'); import Expression = require('./expression');
const protectedSessionService = require('../../protected_session'); import NoteSet = require('../note_set');
const striptags = require('striptags'); import log = require('../../log');
const utils = require('../../utils'); import becca = require('../../../becca/becca');
import protectedSessionService = require('../../protected_session');
import striptags = require('striptags');
import utils = require('../../utils');
import sql = require("../../sql");
const ALLOWED_OPERATORS = ['=', '!=', '*=*', '*=', '=*', '%=']; const ALLOWED_OPERATORS = ['=', '!=', '*=*', '*=', '=*', '%='];
const cachedRegexes = {}; const cachedRegexes: Record<string, RegExp> = {};
function getRegex(str) { function getRegex(str: string): RegExp {
if (!(str in cachedRegexes)) { if (!(str in cachedRegexes)) {
cachedRegexes[str] = new RegExp(str, 'ms'); // multiline, dot-all cachedRegexes[str] = new RegExp(str, 'ms'); // multiline, dot-all
} }
@ -20,8 +24,22 @@ function getRegex(str) {
return cachedRegexes[str]; return cachedRegexes[str];
} }
interface ConstructorOpts {
tokens: string[];
raw: boolean;
flatText: boolean;
}
type SearchRow = Pick<NoteRow, "noteId" | "type" | "mime" | "content" | "isProtected">;
class NoteContentFulltextExp extends Expression { class NoteContentFulltextExp extends Expression {
constructor(operator, {tokens, raw, flatText}) {
private operator: string;
private tokens: string[];
private raw: boolean;
private flatText: boolean;
constructor(operator: string, {tokens, raw, flatText}: ConstructorOpts) {
super(); super();
this.operator = operator; this.operator = operator;
@ -30,7 +48,7 @@ class NoteContentFulltextExp extends Expression {
this.flatText = !!flatText; this.flatText = !!flatText;
} }
execute(inputNoteSet, executionContext, searchContext) { execute(inputNoteSet: NoteSet, executionContext: {}, searchContext: SearchContext) {
if (!ALLOWED_OPERATORS.includes(this.operator)) { if (!ALLOWED_OPERATORS.includes(this.operator)) {
searchContext.addError(`Note content can be searched only with operators: ${ALLOWED_OPERATORS.join(", ")}, operator ${this.operator} given.`); searchContext.addError(`Note content can be searched only with operators: ${ALLOWED_OPERATORS.join(", ")}, operator ${this.operator} given.`);
@ -38,9 +56,8 @@ class NoteContentFulltextExp extends Expression {
} }
const resultNoteSet = new NoteSet(); const resultNoteSet = new NoteSet();
const sql = require('../../sql');
for (const row of sql.iterateRows<SearchRow>(`
for (const row of sql.iterateRows(`
SELECT noteId, type, mime, content, isProtected SELECT noteId, type, mime, content, isProtected
FROM notes JOIN blobs USING (blobId) FROM notes JOIN blobs USING (blobId)
WHERE type IN ('text', 'code', 'mermaid') AND isDeleted = 0`)) { WHERE type IN ('text', 'code', 'mermaid') AND isDeleted = 0`)) {
@ -51,18 +68,18 @@ class NoteContentFulltextExp extends Expression {
return resultNoteSet; return resultNoteSet;
} }
findInText({noteId, isProtected, content, type, mime}, inputNoteSet, resultNoteSet) { findInText({noteId, isProtected, content, type, mime}: SearchRow, inputNoteSet: NoteSet, resultNoteSet: NoteSet) {
if (!inputNoteSet.hasNoteId(noteId) || !(noteId in becca.notes)) { if (!inputNoteSet.hasNoteId(noteId) || !(noteId in becca.notes)) {
return; return;
} }
if (isProtected) { if (isProtected) {
if (!protectedSessionService.isProtectedSessionAvailable()) { if (!protectedSessionService.isProtectedSessionAvailable() || !content) {
return; return;
} }
try { try {
content = protectedSessionService.decryptString(content); content = protectedSessionService.decryptString(content) || undefined;
} catch (e) { } catch (e) {
log.info(`Cannot decrypt content of note ${noteId}`); log.info(`Cannot decrypt content of note ${noteId}`);
return; return;
@ -89,7 +106,7 @@ class NoteContentFulltextExp extends Expression {
} }
} else { } else {
const nonMatchingToken = this.tokens.find(token => const nonMatchingToken = this.tokens.find(token =>
!content.includes(token) && !content?.includes(token) &&
( (
// in case of default fulltext search, we should consider both title, attrs and content // in case of default fulltext search, we should consider both title, attrs and content
// so e.g. "hello world" should match when "hello" is in title and "world" in content // so e.g. "hello world" should match when "hello" is in title and "world" in content
@ -106,7 +123,7 @@ class NoteContentFulltextExp extends Expression {
return content; return content;
} }
preprocessContent(content, type, mime) { preprocessContent(content: string, type: string, mime: string) {
content = utils.normalize(content.toString()); content = utils.normalize(content.toString());
if (type === 'text' && mime === 'text/html') { if (type === 'text' && mime === 'text/html') {
@ -125,4 +142,4 @@ class NoteContentFulltextExp extends Expression {
} }
} }
module.exports = NoteContentFulltextExp; export = NoteContentFulltextExp;

View File

@ -12,7 +12,7 @@ const PropertyComparisonExp = require('../expressions/property_comparison.js');
const AttributeExistsExp = require('../expressions/attribute_exists'); const AttributeExistsExp = require('../expressions/attribute_exists');
const LabelComparisonExp = require('../expressions/label_comparison'); const LabelComparisonExp = require('../expressions/label_comparison');
const NoteFlatTextExp = require('../expressions/note_flat_text.js'); const NoteFlatTextExp = require('../expressions/note_flat_text.js');
const NoteContentFulltextExp = require('../expressions/note_content_fulltext.js'); const NoteContentFulltextExp = require('../expressions/note_content_fulltext');
const OrderByAndLimitExp = require('../expressions/order_by_and_limit.js'); const OrderByAndLimitExp = require('../expressions/order_by_and_limit.js');
const AncestorExp = require('../expressions/ancestor'); const AncestorExp = require('../expressions/ancestor');
const buildComparator = require('./build_comparator.js'); const buildComparator = require('./build_comparator.js');

View File

@ -147,12 +147,12 @@ function getRawRows<T extends {} | unknown[]>(query: string, params: Params = []
return (wrap(query, s => s.raw().all(params)) as T[] | null) || []; return (wrap(query, s => s.raw().all(params)) as T[] | null) || [];
} }
function iterateRows(query: string, params: Params = []) { function iterateRows<T>(query: string, params: Params = []): IterableIterator<T> {
if (LOG_ALL_QUERIES) { if (LOG_ALL_QUERIES) {
console.log(query); console.log(query);
} }
return stmt(query).iterate(params); return stmt(query).iterate(params) as IterableIterator<T>;
} }
function getMap<K extends string | number | symbol, V>(query: string, params: Params = []) { function getMap<K extends string | number | symbol, V>(query: string, params: Params = []) {