WIP blobs

This commit is contained in:
zadam 2023-03-16 11:02:07 +01:00
parent 5a8e216dec
commit e16bedfab4
30 changed files with 65 additions and 102 deletions

View File

@ -1,10 +1,8 @@
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
UPDATE notes SET title = 'title' WHERE noteId != 'root' AND noteId NOT LIKE '\_%' ESCAPE '\';
UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_revisions SET title = 'title';
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_ancillary_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE attributes SET name = 'name', value = 'value'
WHERE type = 'label'

View File

@ -5,7 +5,6 @@ CREATE TABLE IF NOT EXISTS "note_ancillaries"
name TEXT not null,
mime TEXT not null,
isProtected INT not null DEFAULT 0,
contentCheckSum TEXT not null,
blobId TEXT not null,
utcDateModified TEXT not null,
isDeleted INT not null,

View File

@ -119,13 +119,10 @@ CREATE TABLE IF NOT EXISTS "note_ancillaries"
name TEXT not null,
mime TEXT not null,
isProtected INT not null DEFAULT 0,
contentCheckSum TEXT not null,
utcDateModified TEXT not null,
isDeleted INT not null,
`deleteId` TEXT DEFAULT NULL);
CREATE TABLE IF NOT EXISTS "note_ancillary_contents" (`noteAncillaryId` TEXT NOT NULL PRIMARY KEY,
`content` TEXT DEFAULT NULL,
`utcDateModified` TEXT NOT NULL);
CREATE INDEX IDX_note_ancillaries_name
on note_ancillaries (name);
CREATE UNIQUE INDEX IDX_note_ancillaries_noteId_name

View File

@ -74,7 +74,7 @@ function dumpDocument(documentPath, targetPath, options) {
return;
}
let {content} = sql.getRow("SELECT content FROM note_contents WHERE noteId = ?", [noteId]);
let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [note.blobId]);
if (content !== null && note.isProtected && dataKey) {
content = decryptService.decrypt(dataKey, content);

View File

@ -125,7 +125,7 @@ class Becca {
getNoteAncillary(noteAncillaryId) {
const row = sql.getRow("SELECT * FROM note_ancillaries WHERE noteAncillaryId = ?", [noteAncillaryId]);
const BNoteAncillary = require("./entities/bnote_ancillary"); // avoiding circular dependency problems
const BNoteAncillary = require("./entities/bnote_attachment.js"); // avoiding circular dependency problems
return row ? new BNoteAncillary(row) : null;
}

View File

@ -8,7 +8,7 @@ const dateUtils = require('../../services/date_utils');
const entityChangesService = require('../../services/entity_changes');
const AbstractBeccaEntity = require("./abstract_becca_entity");
const BNoteRevision = require("./bnote_revision");
const BNoteAncillary = require("./bnote_ancillary");
const BNoteAncillary = require("./bnote_attachment.js");
const TaskContext = require("../../services/task_context");
const dayjs = require("dayjs");
const utc = require('dayjs/plugin/utc');
@ -1507,13 +1507,6 @@ class BNote extends AbstractBeccaEntity {
saveNoteAncillary(name, mime, content) {
let noteAncillary = this.getNoteAncillaryByName(name);
if (noteAncillary
&& noteAncillary.mime === mime
&& noteAncillary.contentCheckSum === noteAncillary.calculateCheckSum(content)) {
return noteAncillary; // no change
}
noteAncillary = new BNoteAncillary({
noteId: this.noteId,
name,

View File

@ -41,8 +41,6 @@ class BNoteAncillary extends AbstractBeccaEntity {
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @type {string} */
this.contentCheckSum = row.contentCheckSum;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
}
@ -91,7 +89,6 @@ class BNoteAncillary extends AbstractBeccaEntity {
setContent(content) {
sql.transactional(() => {
this.contentCheckSum = this.calculateCheckSum(content);
this.save(); // also explicitly save note_ancillary to update contentCheckSum
const pojo = {
@ -113,7 +110,7 @@ class BNoteAncillary extends AbstractBeccaEntity {
entityChangesService.addEntityChange({
entityName: 'note_ancillary_contents',
entityId: this.noteAncillaryId,
hash: this.contentCheckSum,
hash: this.contentCheckSum, // FIXME
isErased: false,
utcDateChanged: pojo.utcDateModified,
isSynced: true
@ -144,7 +141,7 @@ class BNoteAncillary extends AbstractBeccaEntity {
name: this.name,
mime: this.mime,
isProtected: !!this.isProtected,
contentCheckSum: this.contentCheckSum,
contentCheckSum: this.contentCheckSum, // FIXME
isDeleted: false,
utcDateModified: this.utcDateModified
};

View File

@ -1,6 +1,6 @@
const BNote = require('./entities/bnote');
const BNoteRevision = require('./entities/bnote_revision');
const BNoteAncillary = require("./entities/bnote_ancillary");
const BNoteAncillary = require("./entities/bnote_attachment.js");
const BBranch = require('./entities/bbranch');
const BAttribute = require('./entities/battribute');
const BRecentNote = require('./entities/brecent_note');
@ -11,11 +11,8 @@ const ENTITY_NAME_TO_ENTITY = {
"attributes": BAttribute,
"branches": BBranch,
"notes": BNote,
"note_contents": BNote,
"note_revisions": BNoteRevision,
"note_revision_contents": BNoteRevision,
"note_ancillaries": BNoteAncillary,
"note_ancillary_contents": BNoteAncillary,
"recent_notes": BRecentNote,
"etapi_tokens": BEtapiToken,
"options": BOption

View File

@ -25,8 +25,6 @@ async function processEntityChanges(entityChanges) {
loadResults.addNoteContent(ec.noteIds, ec.componentId);
} else if (ec.entityName === 'note_revisions') {
loadResults.addNoteRevision(ec.entityId, ec.noteId, ec.componentId);
} else if (ec.entityName === 'note_revision_contents') {
// this should change only when toggling isProtected, ignore
} else if (ec.entityName === 'options') {
if (ec.entity.name === 'openTabs') {
continue; // only noise
@ -36,7 +34,7 @@ async function processEntityChanges(entityChanges) {
loadResults.addOption(ec.entity.name);
}
else if (['etapi_tokens', 'note_ancillaries', 'note_ancillary_contents'].includes(ec.entityName)) {
else if (['etapi_tokens', 'note_ancillaries'].includes(ec.entityName)) {
// NOOP
}
else {

View File

@ -27,7 +27,7 @@ import NoteMapTypeWidget from "./type_widgets/note_map.js";
import WebViewTypeWidget from "./type_widgets/web_view.js";
import DocTypeWidget from "./type_widgets/doc.js";
import ContentWidgetTypeWidget from "./type_widgets/content_widget.js";
import AncillariesTypeWidget from "./type_widgets/ancillaries.js";
import AncillariesTypeWidget from "./type_widgets/attachments.js";
const TPL = `
<div class="note-detail">

View File

@ -12,9 +12,9 @@ const becca = require("../../becca/becca");
function getNoteRevisions(req) {
return becca.getNoteRevisionsFromQuery(`
SELECT note_revisions.*,
LENGTH(note_revision_contents.content) AS contentLength
LENGTH(blobs.content) AS contentLength
FROM note_revisions
JOIN note_revision_contents ON note_revisions.noteRevisionId = note_revision_contents.noteRevisionId
JOIN blobs ON note_revisions.blobId = blobs.blobId
WHERE noteId = ?
ORDER BY utcDateCreated DESC`, [req.params.noteId]);
}

View File

@ -4,18 +4,19 @@ const NotFoundError = require("../../errors/not_found_error");
function getNoteSize(req) {
const {noteId} = req.params;
const note = becca.getNote(noteId);
const noteSize = sql.getValue(`
SELECT
COALESCE((SELECT LENGTH(content) FROM note_contents WHERE noteId = ?), 0)
COALESCE((SELECT LENGTH(content) FROM blobs WHERE blobId = ?), 0)
+
COALESCE(
(SELECT SUM(LENGTH(content))
FROM note_revisions
JOIN note_revision_contents USING (noteRevisionId)
JOIN blobs USING (blobId)
WHERE note_revisions.noteId = ?),
0
)`, [noteId, noteId]);
)`, [note.blobId, noteId]);
return {
noteSize
@ -38,14 +39,15 @@ function getSubtreeSize(req) {
SELECT
COALESCE((
SELECT SUM(LENGTH(content))
FROM note_contents
JOIN param_list ON param_list.paramId = note_contents.noteId
FROM notes
JOIN blobs USING (blobId)
JOIN param_list ON param_list.paramId = notes.noteId
), 0)
+
COALESCE(
(SELECT SUM(LENGTH(content))
FROM note_revisions
JOIN note_revision_contents USING (noteRevisionId)
JOIN blobs USING (blobId)
JOIN param_list ON param_list.paramId = note_revisions.noteId),
0
)`);

View File

@ -12,6 +12,7 @@ const syncOptions = require('../../services/sync_options');
const dateUtils = require('../../services/date_utils');
const utils = require('../../services/utils');
const ws = require('../../services/ws');
const becca = require("../../becca/becca.js");
async function testSync() {
try {
@ -85,14 +86,15 @@ function forceFullSync() {
function forceNoteSync(req) {
const noteId = req.params.noteId;
const note = becca.getNote(noteId);
const now = dateUtils.utcNowDateTime();
sql.execute(`UPDATE notes SET utcDateModified = ? WHERE noteId = ?`, [now, noteId]);
entityChangesService.moveEntityChangeToTop('notes', noteId);
sql.execute(`UPDATE note_contents SET utcDateModified = ? WHERE noteId = ?`, [now, noteId]);
entityChangesService.moveEntityChangeToTop('note_contents', noteId);
sql.execute(`UPDATE blobs SET utcDateModified = ? WHERE blobId = ?`, [now, note.blobId]);
entityChangesService.moveEntityChangeToTop('blobs', note.blobId);
for (const branchId of sql.getColumn("SELECT branchId FROM branches WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE branches SET utcDateModified = ? WHERE branchId = ?`, [now, branchId]);
@ -109,17 +111,11 @@ function forceNoteSync(req) {
for (const noteRevisionId of sql.getColumn("SELECT noteRevisionId FROM note_revisions WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE note_revisions SET utcDateModified = ? WHERE noteRevisionId = ?`, [now, noteRevisionId]);
entityChangesService.moveEntityChangeToTop('note_revisions', noteRevisionId);
sql.execute(`UPDATE note_revision_contents SET utcDateModified = ? WHERE noteRevisionId = ?`, [now, noteRevisionId]);
entityChangesService.moveEntityChangeToTop('note_revision_contents', noteRevisionId);
}
for (const noteAncillaryId of sql.getColumn("SELECT noteAncillaryId FROM note_ancillaries WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE note_ancillaries SET utcDateModified = ? WHERE noteAncillaryId = ?`, [now, noteAncillaryId]);
entityChangesService.moveEntityChangeToTop('note_ancillaries', noteAncillaryId);
sql.execute(`UPDATE note_ancillary_contents SET utcDateModified = ? WHERE noteAncillaryId = ?`, [now, noteAncillaryId]);
entityChangesService.moveEntityChangeToTop('note_ancillary_contents', noteAncillaryId);
}
log.info(`Forcing note sync for ${noteId}`);

View File

@ -14,9 +14,8 @@ function getFullAnonymizationScript() {
const anonymizeScript = `
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share');
UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_revisions SET title = 'title';
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrNames});
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames});
@ -34,14 +33,11 @@ VACUUM;
}
function getLightAnonymizationScript() {
return `
UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL AND noteId NOT IN (
SELECT noteId FROM notes WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
);
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL AND noteRevisionId NOT IN (
SELECT noteRevisionId FROM note_revisions WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
);
`;
return `UPDATE blobs SET content = 'text' WHERE content IS NOT NULL AND blobId NOT IN (
SELECT blobId FROM notes WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
UNION ALL
SELECT blobId FROM note_revisions WHERE mime IN ('application/javascript;env=backend', 'application/javascript;env=frontend')
);`;
}
async function createAnonymizedCopy(type) {

View File

@ -656,11 +656,9 @@ class ConsistencyChecks {
findEntityChangeIssues() {
this.runEntityChangeChecks("notes", "noteId");
//this.runEntityChangeChecks("note_contents", "noteId");
this.runEntityChangeChecks("note_revisions", "noteRevisionId");
//this.runEntityChangeChecks("note_revision_contents", "noteRevisionId");
this.runEntityChangeChecks("note_ancillaries", "noteAncillaryId");
//this.runEntityChangeChecks("note_ancillary_contents", "noteAncillaryId");
this.runEntityChangeChecks("blobs", "blobId");
this.runEntityChangeChecks("branches", "branchId");
this.runEntityChangeChecks("attributes", "attributeId");
this.runEntityChangeChecks("etapi_tokens", "etapiTokenId");

View File

@ -104,7 +104,7 @@ function fillEntityChanges(entityName, entityPrimaryKey, condition = '') {
let utcDateChanged;
let isSynced;
if (entityName.endsWith("_contents")) {
if (entityName === 'blobs') {
// FIXME: hacky, not sure if it might cause some problems
hash = "fake value";
utcDateChanged = dateUtils.utcNowDateTime();
@ -147,12 +147,10 @@ function fillAllEntityChanges() {
sql.execute("DELETE FROM entity_changes WHERE isErased = 0");
fillEntityChanges("notes", "noteId");
fillEntityChanges("note_contents", "noteId");
fillEntityChanges("branches", "branchId");
fillEntityChanges("note_revisions", "noteRevisionId");
fillEntityChanges("note_revision_contents", "noteRevisionId");
fillEntityChanges("note_ancillaries", "noteAncillaryId");
fillEntityChanges("note_ancillary_contents", "noteAncillaryId");
fillEntityChanges("blobs", "blobId");
fillEntityChanges("attributes", "attributeId");
fillEntityChanges("etapi_tokens", "etapiTokenId");
fillEntityChanges("options", "name", 'isSynced = 1');

View File

@ -59,7 +59,7 @@ eventService.subscribe([ eventService.ENTITY_CHANGED, eventService.ENTITY_DELETE
});
eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
if (entityName === 'note_contents') {
if (entityName === 'note_contents') { // FIXME
runAttachedRelations(entity, 'runOnNoteContentChange', entity);
}
});

View File

@ -206,7 +206,7 @@ function importEnex(taskContext, file, parentNote) {
}
});
function updateDates(noteId, utcDateCreated, utcDateModified) {
function updateDates(note, utcDateCreated, utcDateModified) {
// it's difficult to force custom dateCreated and dateModified to Note entity, so we do it post-creation with SQL
sql.execute(`
UPDATE notes
@ -215,13 +215,13 @@ function importEnex(taskContext, file, parentNote) {
dateModified = ?,
utcDateModified = ?
WHERE noteId = ?`,
[utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, noteId]);
[utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, note.noteId]);
sql.execute(`
UPDATE note_contents
UPDATE blobs
SET utcDateModified = ?
WHERE noteId = ?`,
[utcDateModified, noteId]);
WHERE blobId = ?`,
[utcDateModified, note.blobId]);
}
function saveNote() {
@ -287,7 +287,7 @@ function importEnex(taskContext, file, parentNote) {
resourceNote.addAttribute(attr.type, attr.name, attr.value);
}
updateDates(resourceNote.noteId, utcDateCreated, utcDateModified);
updateDates(resourceNote, utcDateCreated, utcDateModified);
taskContext.increaseProgressCount();
@ -310,7 +310,7 @@ function importEnex(taskContext, file, parentNote) {
}
}
updateDates(imageNote.noteId, utcDateCreated, utcDateModified);
updateDates(imageNote, utcDateCreated, utcDateModified);
const imageLink = `<img src="${url}">`;
@ -337,7 +337,7 @@ function importEnex(taskContext, file, parentNote) {
noteService.asyncPostProcessContent(noteEntity, content);
updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
updateDates(noteEntity, utcDateCreated, utcDateModified);
}
saxStream.on("closetag", tag => {

View File

@ -14,7 +14,7 @@ const treeService = require("../tree");
const yauzl = require("yauzl");
const htmlSanitizer = require('../html_sanitizer');
const becca = require("../../becca/becca");
const BNoteAncillary = require("../../becca/entities/bnote_ancillary");
const BNoteAncillary = require("../../becca/entities/bnote_attachment.js");
/**
* @param {TaskContext} taskContext

View File

@ -44,9 +44,6 @@ function eraseNoteRevisions(noteRevisionIdsToErase) {
sql.executeMany(`DELETE FROM note_revisions WHERE noteRevisionId IN (???)`, noteRevisionIdsToErase);
sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_revisions' AND entityId IN (???)`, noteRevisionIdsToErase);
sql.executeMany(`DELETE FROM note_revision_contents WHERE noteRevisionId IN (???)`, noteRevisionIdsToErase);
sql.executeMany(`UPDATE entity_changes SET isErased = 1 WHERE entityName = 'note_revision_contents' AND entityId IN (???)`, noteRevisionIdsToErase);
}
module.exports = {

View File

@ -220,7 +220,7 @@ function createNewNote(params) {
entity: note
});
eventService.emit(eventService.ENTITY_CREATED, {
eventService.emit(eventService.ENTITY_CREATED, { // FIXME
entityName: 'note_contents',
entity: note
});
@ -499,7 +499,7 @@ function downloadImages(noteId, content) {
asyncPostProcessContent(origNote, updatedContent);
eventService.emit(eventService.ENTITY_CHANGED, {
entityName: 'note_contents',
entityName: 'note_contents', // FIXME
entity: origNote
});
@ -733,9 +733,6 @@ function eraseNotes(noteIdsToErase) {
sql.executeMany(`DELETE FROM notes WHERE noteId IN (???)`, noteIdsToErase);
setEntityChangesAsErased(sql.getManyRows(`SELECT * FROM entity_changes WHERE entityName = 'notes' AND entityId IN (???)`, noteIdsToErase));
sql.executeMany(`DELETE FROM note_contents WHERE noteId IN (???)`, noteIdsToErase);
setEntityChangesAsErased(sql.getManyRows(`SELECT * FROM entity_changes WHERE entityName = 'note_contents' AND entityId IN (???)`, noteIdsToErase));
// we also need to erase all "dependent" entities of the erased notes
const branchIdsToErase = sql.getManyRows(`SELECT branchId FROM branches WHERE noteId IN (???)`, noteIdsToErase)
.map(row => row.branchId);

View File

@ -42,7 +42,7 @@ class NoteContentFulltextExp extends Expression {
for (const row of sql.iterateRows(`
SELECT noteId, type, mime, content, isProtected
FROM notes JOIN note_contents USING (noteId)
FROM notes JOIN blobs USING (blobId)
WHERE type IN ('text', 'code', 'mermaid') AND isDeleted = 0`)) {
this.findInText(row, inputNoteSet, resultNoteSet);

View File

@ -103,7 +103,7 @@ function loadNeededInfoFromDatabase() {
noteId,
LENGTH(content) AS length
FROM notes
JOIN note_contents USING(noteId)
JOIN blobs USING(blobId)
WHERE notes.isDeleted = 0`);
for (const {noteId, length} of noteContentLengths) {
@ -122,7 +122,7 @@ function loadNeededInfoFromDatabase() {
LENGTH(content) AS length
FROM notes
JOIN note_revisions USING(noteId)
JOIN note_revision_contents USING(noteRevisionId)
JOIN blobs USING(blobId)
WHERE notes.isDeleted = 0`);
for (const {noteId, length} of noteRevisionContentLengths) {

View File

@ -321,7 +321,7 @@ function getEntityChangeRow(entityName, entityId) {
throw new Error(`Entity ${entityName} ${entityId} not found.`);
}
if (['note_contents', 'note_revision_contents', 'note_ancillary_contents'].includes(entityName) && entity.content !== null) {
if (entityName === 'blobs' && entity.content !== null) {
if (typeof entity.content === 'string') {
entity.content = Buffer.from(entity.content, 'UTF-8');
}

View File

@ -64,7 +64,7 @@ function updateNormalEntity(remoteEntityChange, remoteEntityRow, instanceId) {
|| localEntityChange.utcDateChanged < remoteEntityChange.utcDateChanged
|| localEntityChange.hash !== remoteEntityChange.hash // sync error, we should still update
) {
if (['note_contents', 'note_revision_contents', 'note_ancillary_contents'].includes(remoteEntityChange.entityName)) {
if (remoteEntityChange.entityName === 'blobs') {
remoteEntityRow.content = handleContent(remoteEntityRow.content);
}
@ -94,7 +94,7 @@ function updateNoteReordering(entityChange, entity, instanceId) {
function handleContent(content) {
// we always use Buffer object which is different from normal saving - there we use simple string type for
// "string notes". The problem is that in general it's not possible to detect whether a note_content
// "string notes". The problem is that in general it's not possible to detect whether a blob content
// is string note or note (syncs can arrive out of order)
content = content === null ? null : Buffer.from(content, 'base64');
@ -111,13 +111,11 @@ function eraseEntity(entityChange, instanceId) {
const entityNames = [
"notes",
"note_contents",
"branches",
"attributes",
"note_revisions",
"note_revision_contents",
"note_ancillaries",
"note_ancillary_contents"
"blobs",
];
if (!entityNames.includes(entityName)) {

View File

@ -147,13 +147,13 @@ function fillInAdditionalProperties(entityChange) {
// entities with higher number can reference the entities with lower number
const ORDERING = {
"etapi_tokens": 0,
"attributes": 1,
"branches": 1,
"note_contents": 1,
"note_reordering": 1,
"note_revision_contents": 2,
"note_revisions": 1,
"notes": 0,
"attributes": 2,
"branches": 2,
"blobs": 0,
"note_reordering": 2,
"note_revisions": 2,
"note_attachments": 3,
"notes": 1,
"options": 0
};

View File

@ -12,7 +12,7 @@ const CREDENTIALS = 'shareCredentials';
const isCredentials = attr => attr.type === 'label' && attr.name === CREDENTIALS;
class SNote extends AbstractShacaEntity {
constructor([noteId, title, type, mime, utcDateModified, isProtected]) {
constructor([noteId, title, type, mime, blobId, utcDateModified, isProtected]) {
super();
/** @param {string} */
@ -24,6 +24,8 @@ class SNote extends AbstractShacaEntity {
/** @param {string} */
this.mime = mime;
/** @param {string} */
this.blobId = blobId;
/** @param {string} */
this.utcDateModified = utcDateModified; // used for caching of images
/** @param {boolean} */
this.isProtected = isProtected;
@ -92,14 +94,14 @@ class SNote extends AbstractShacaEntity {
}
getContent(silentNotFoundError = false) {
const row = sql.getRow(`SELECT content FROM note_contents WHERE noteId = ?`, [this.noteId]);
const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
if (!row) {
if (silentNotFoundError) {
return undefined;
}
else {
throw new Error(`Cannot find note content for noteId=${this.noteId}`);
throw new Error(`Cannot find note content for noteId '${this.noteId}', blobId '${this.blobId}'`);
}
}

View File

@ -35,7 +35,7 @@ function load() {
const noteIdStr = noteIds.map(noteId => `'${noteId}'`).join(",");
const rawNoteRows = sql.getRawRows(`
SELECT noteId, title, type, mime, utcDateModified, isProtected
SELECT noteId, title, type, mime, blobId, utcDateModified, isProtected
FROM notes
WHERE isDeleted = 0
AND noteId IN (${noteIdStr})`);