mirror of
https://github.com/zadam/trilium.git
synced 2025-11-11 15:55:52 +01:00
syncification
This commit is contained in:
@@ -19,12 +19,12 @@ class ConsistencyChecks {
|
||||
this.fixedIssues = false;
|
||||
}
|
||||
|
||||
async findAndFixIssues(query, fixerCb) {
|
||||
const results = await sql.getRows(query);
|
||||
findAndFixIssues(query, fixerCb) {
|
||||
const results = sql.getRows(query);
|
||||
|
||||
for (const res of results) {
|
||||
try {
|
||||
await fixerCb(res);
|
||||
fixerCb(res);
|
||||
|
||||
if (this.autoFix) {
|
||||
this.fixedIssues = true;
|
||||
@@ -40,9 +40,9 @@ class ConsistencyChecks {
|
||||
return results;
|
||||
}
|
||||
|
||||
async checkTreeCycles() {
|
||||
checkTreeCycles() {
|
||||
const childToParents = {};
|
||||
const rows = await sql.getRows("SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0");
|
||||
const rows = sql.getRows("SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0");
|
||||
|
||||
for (const row of rows) {
|
||||
const childNoteId = row.noteId;
|
||||
@@ -90,18 +90,18 @@ class ConsistencyChecks {
|
||||
}
|
||||
}
|
||||
|
||||
async findBrokenReferenceIssues() {
|
||||
await this.findAndFixIssues(`
|
||||
findBrokenReferenceIssues() {
|
||||
this.findAndFixIssues(`
|
||||
SELECT branchId, branches.noteId
|
||||
FROM branches
|
||||
LEFT JOIN notes USING (noteId)
|
||||
WHERE branches.isDeleted = 0
|
||||
AND notes.noteId IS NULL`,
|
||||
async ({branchId, noteId}) => {
|
||||
({branchId, noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const branch = await repository.getBranch(branchId);
|
||||
const branch = repository.getBranch(branchId);
|
||||
branch.isDeleted = true;
|
||||
await branch.save();
|
||||
branch.save();
|
||||
|
||||
logFix(`Branch ${branchId} has been deleted since it references missing note ${noteId}`);
|
||||
} else {
|
||||
@@ -109,18 +109,18 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT branchId, branches.noteId AS parentNoteId
|
||||
FROM branches
|
||||
LEFT JOIN notes ON notes.noteId = branches.parentNoteId
|
||||
WHERE branches.isDeleted = 0
|
||||
AND branches.branchId != 'root'
|
||||
AND notes.noteId IS NULL`,
|
||||
async ({branchId, parentNoteId}) => {
|
||||
({branchId, parentNoteId}) => {
|
||||
if (this.autoFix) {
|
||||
const branch = await repository.getBranch(branchId);
|
||||
const branch = repository.getBranch(branchId);
|
||||
branch.parentNoteId = 'root';
|
||||
await branch.save();
|
||||
branch.save();
|
||||
|
||||
logFix(`Branch ${branchId} was set to root parent since it was referencing missing parent note ${parentNoteId}`);
|
||||
} else {
|
||||
@@ -128,17 +128,17 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT attributeId, attributes.noteId
|
||||
FROM attributes
|
||||
LEFT JOIN notes USING (noteId)
|
||||
WHERE attributes.isDeleted = 0
|
||||
AND notes.noteId IS NULL`,
|
||||
async ({attributeId, noteId}) => {
|
||||
({attributeId, noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const attribute = await repository.getAttribute(attributeId);
|
||||
const attribute = repository.getAttribute(attributeId);
|
||||
attribute.isDeleted = true;
|
||||
await attribute.save();
|
||||
attribute.save();
|
||||
|
||||
logFix(`Attribute ${attributeId} has been deleted since it references missing source note ${noteId}`);
|
||||
} else {
|
||||
@@ -146,18 +146,18 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT attributeId, attributes.value AS noteId
|
||||
FROM attributes
|
||||
LEFT JOIN notes ON notes.noteId = attributes.value
|
||||
WHERE attributes.isDeleted = 0
|
||||
AND attributes.type = 'relation'
|
||||
AND notes.noteId IS NULL`,
|
||||
async ({attributeId, noteId}) => {
|
||||
({attributeId, noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const attribute = await repository.getAttribute(attributeId);
|
||||
const attribute = repository.getAttribute(attributeId);
|
||||
attribute.isDeleted = true;
|
||||
await attribute.save();
|
||||
attribute.save();
|
||||
|
||||
logFix(`Relation ${attributeId} has been deleted since it references missing note ${noteId}`)
|
||||
} else {
|
||||
@@ -166,24 +166,24 @@ class ConsistencyChecks {
|
||||
});
|
||||
}
|
||||
|
||||
async findExistencyIssues() {
|
||||
findExistencyIssues() {
|
||||
// principle for fixing inconsistencies is that if the note itself is deleted (isDeleted=true) then all related entities should be also deleted (branches, attributes)
|
||||
// but if note is not deleted, then at least one branch should exist.
|
||||
|
||||
// the order here is important - first we might need to delete inconsistent branches and after that
|
||||
// another check might create missing branch
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT branchId,
|
||||
noteId
|
||||
FROM branches
|
||||
JOIN notes USING (noteId)
|
||||
WHERE notes.isDeleted = 1
|
||||
AND branches.isDeleted = 0`,
|
||||
async ({branchId, noteId}) => {
|
||||
({branchId, noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const branch = await repository.getBranch(branchId);
|
||||
const branch = repository.getBranch(branchId);
|
||||
branch.isDeleted = true;
|
||||
await branch.save();
|
||||
branch.save();
|
||||
|
||||
logFix(`Branch ${branchId} has been deleted since associated note ${noteId} is deleted.`);
|
||||
} else {
|
||||
@@ -191,18 +191,18 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT branchId,
|
||||
parentNoteId
|
||||
FROM branches
|
||||
JOIN notes AS parentNote ON parentNote.noteId = branches.parentNoteId
|
||||
WHERE parentNote.isDeleted = 1
|
||||
AND branches.isDeleted = 0
|
||||
`, async ({branchId, parentNoteId}) => {
|
||||
`, ({branchId, parentNoteId}) => {
|
||||
if (this.autoFix) {
|
||||
const branch = await repository.getBranch(branchId);
|
||||
const branch = repository.getBranch(branchId);
|
||||
branch.isDeleted = true;
|
||||
await branch.save();
|
||||
branch.save();
|
||||
|
||||
logFix(`Branch ${branchId} has been deleted since associated parent note ${parentNoteId} is deleted.`);
|
||||
} else {
|
||||
@@ -210,15 +210,15 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT DISTINCT notes.noteId
|
||||
FROM notes
|
||||
LEFT JOIN branches ON notes.noteId = branches.noteId AND branches.isDeleted = 0
|
||||
WHERE notes.isDeleted = 0
|
||||
AND branches.branchId IS NULL
|
||||
`, async ({noteId}) => {
|
||||
`, ({noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const branch = await new Branch({
|
||||
const branch = new Branch({
|
||||
parentNoteId: 'root',
|
||||
noteId: noteId,
|
||||
prefix: 'recovered'
|
||||
@@ -231,7 +231,7 @@ class ConsistencyChecks {
|
||||
});
|
||||
|
||||
// there should be a unique relationship between note and its parent
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteId,
|
||||
parentNoteId
|
||||
FROM branches
|
||||
@@ -239,9 +239,9 @@ class ConsistencyChecks {
|
||||
GROUP BY branches.parentNoteId,
|
||||
branches.noteId
|
||||
HAVING COUNT(1) > 1`,
|
||||
async ({noteId, parentNoteId}) => {
|
||||
({noteId, parentNoteId}) => {
|
||||
if (this.autoFix) {
|
||||
const branches = await repository.getEntities(
|
||||
const branches = repository.getEntities(
|
||||
`SELECT *
|
||||
FROM branches
|
||||
WHERE noteId = ?
|
||||
@@ -254,7 +254,7 @@ class ConsistencyChecks {
|
||||
// delete all but the first branch
|
||||
for (const branch of branches.slice(1)) {
|
||||
branch.isDeleted = true;
|
||||
await branch.save();
|
||||
branch.save();
|
||||
|
||||
logFix(`Removing branch ${branch.branchId} since it's parent-child duplicate of branch ${origBranch.branchId}`);
|
||||
}
|
||||
@@ -264,17 +264,17 @@ class ConsistencyChecks {
|
||||
});
|
||||
}
|
||||
|
||||
async findLogicIssues() {
|
||||
await this.findAndFixIssues(`
|
||||
findLogicIssues() {
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteId, type
|
||||
FROM notes
|
||||
WHERE isDeleted = 0
|
||||
AND type NOT IN ('text', 'code', 'render', 'file', 'image', 'search', 'relation-map', 'book')`,
|
||||
async ({noteId, type}) => {
|
||||
({noteId, type}) => {
|
||||
if (this.autoFix) {
|
||||
const note = await repository.getNote(noteId);
|
||||
const note = repository.getNote(noteId);
|
||||
note.type = 'file'; // file is a safe option to recover notes if type is not known
|
||||
await note.save();
|
||||
note.save();
|
||||
|
||||
logFix(`Note ${noteId} type has been change to file since it had invalid type=${type}`)
|
||||
} else {
|
||||
@@ -282,29 +282,29 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT notes.noteId
|
||||
FROM notes
|
||||
LEFT JOIN note_contents USING (noteId)
|
||||
WHERE note_contents.noteId IS NULL`,
|
||||
async ({noteId}) => {
|
||||
({noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const note = await repository.getNote(noteId);
|
||||
const note = repository.getNote(noteId);
|
||||
|
||||
if (note.isProtected) {
|
||||
// this is wrong for non-erased notes but we cannot set a valid value for protected notes
|
||||
await sql.upsert("note_contents", "noteId", {
|
||||
sql.upsert("note_contents", "noteId", {
|
||||
noteId: noteId,
|
||||
content: null,
|
||||
hash: "consistency_checks",
|
||||
utcDateModified: dateUtils.utcNowDateTime()
|
||||
});
|
||||
|
||||
await syncTableService.addNoteContentSync(noteId);
|
||||
syncTableService.addNoteContentSync(noteId);
|
||||
}
|
||||
else {
|
||||
// empty string might be wrong choice for some note types but it's a best guess
|
||||
await note.setContent(note.isErased ? null : '');
|
||||
note.setContent(note.isErased ? null : '');
|
||||
}
|
||||
|
||||
logFix(`Note ${noteId} content was set to empty string since there was no corresponding row`);
|
||||
@@ -313,18 +313,18 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteId
|
||||
FROM notes
|
||||
JOIN note_contents USING (noteId)
|
||||
WHERE isDeleted = 0
|
||||
AND isProtected = 0
|
||||
AND content IS NULL`,
|
||||
async ({noteId}) => {
|
||||
({noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const note = await repository.getNote(noteId);
|
||||
const note = repository.getNote(noteId);
|
||||
// empty string might be wrong choice for some note types but it's a best guess
|
||||
await note.setContent('');
|
||||
note.setContent('');
|
||||
|
||||
logFix(`Note ${noteId} content was set to empty string since it was null even though it is not deleted`);
|
||||
} else {
|
||||
@@ -332,13 +332,13 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteId
|
||||
FROM notes
|
||||
JOIN note_contents USING (noteId)
|
||||
WHERE isErased = 1
|
||||
AND content IS NOT NULL`,
|
||||
async ({noteId}) => {
|
||||
({noteId}) => {
|
||||
|
||||
// we always fix this issue because there does not seem to be a good way to prevent it.
|
||||
// Scenario in which this can happen:
|
||||
@@ -355,23 +355,23 @@ class ConsistencyChecks {
|
||||
//
|
||||
// So instead we just fix such cases afterwards here.
|
||||
|
||||
await sql.execute(`UPDATE note_contents SET content = NULL WHERE noteId = ?`, [noteId]);
|
||||
sql.execute(`UPDATE note_contents SET content = NULL WHERE noteId = ?`, [noteId]);
|
||||
|
||||
logFix(`Note ${noteId} content has been set to null since the note is erased`);
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteId, noteRevisionId
|
||||
FROM notes
|
||||
JOIN note_revisions USING (noteId)
|
||||
WHERE notes.isErased = 1
|
||||
AND note_revisions.isErased = 0`,
|
||||
async ({noteId, noteRevisionId}) => {
|
||||
({noteId, noteRevisionId}) => {
|
||||
if (this.autoFix) {
|
||||
const noteRevision = await repository.getNoteRevision(noteRevisionId);
|
||||
const noteRevision = repository.getNoteRevision(noteRevisionId);
|
||||
noteRevision.isErased = true;
|
||||
await noteRevision.setContent(null);
|
||||
await noteRevision.save();
|
||||
noteRevision.setContent(null);
|
||||
noteRevision.save();
|
||||
|
||||
logFix(`Note revision ${noteRevisionId} has been erased since its note ${noteId} is also erased.`);
|
||||
} else {
|
||||
@@ -379,18 +379,18 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT note_revisions.noteRevisionId
|
||||
FROM note_revisions
|
||||
LEFT JOIN note_revision_contents USING (noteRevisionId)
|
||||
WHERE note_revision_contents.noteRevisionId IS NULL
|
||||
AND note_revisions.isProtected = 0`,
|
||||
async ({noteRevisionId}) => {
|
||||
({noteRevisionId}) => {
|
||||
if (this.autoFix) {
|
||||
const noteRevision = await repository.getNoteRevision(noteRevisionId);
|
||||
await noteRevision.setContent(null);
|
||||
const noteRevision = repository.getNoteRevision(noteRevisionId);
|
||||
noteRevision.setContent(null);
|
||||
noteRevision.isErased = true;
|
||||
await noteRevision.save();
|
||||
noteRevision.save();
|
||||
|
||||
logFix(`Note revision content ${noteRevisionId} was created and set to erased since it did not exist.`);
|
||||
} else {
|
||||
@@ -398,17 +398,17 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteRevisionId
|
||||
FROM note_revisions
|
||||
JOIN note_revision_contents USING (noteRevisionId)
|
||||
WHERE isErased = 0
|
||||
AND content IS NULL`,
|
||||
async ({noteRevisionId}) => {
|
||||
({noteRevisionId}) => {
|
||||
if (this.autoFix) {
|
||||
const noteRevision = await repository.getNoteRevision(noteRevisionId);
|
||||
const noteRevision = repository.getNoteRevision(noteRevisionId);
|
||||
noteRevision.isErased = true;
|
||||
await noteRevision.save();
|
||||
noteRevision.save();
|
||||
|
||||
logFix(`Note revision ${noteRevisionId} content was set to empty string since it was null even though it is not erased`);
|
||||
} else {
|
||||
@@ -416,15 +416,15 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteRevisionId
|
||||
FROM note_revisions
|
||||
JOIN note_revision_contents USING (noteRevisionId)
|
||||
WHERE isErased = 1
|
||||
AND content IS NOT NULL`,
|
||||
async ({noteRevisionId}) => {
|
||||
({noteRevisionId}) => {
|
||||
if (this.autoFix) {
|
||||
await sql.execute(`UPDATE note_revision_contents SET content = NULL WHERE noteRevisionId = ?`, [noteRevisionId]);
|
||||
sql.execute(`UPDATE note_revision_contents SET content = NULL WHERE noteRevisionId = ?`, [noteRevisionId]);
|
||||
|
||||
logFix(`Note revision ${noteRevisionId} content was set to null since the note revision is erased`);
|
||||
}
|
||||
@@ -433,16 +433,16 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT noteId
|
||||
FROM notes
|
||||
WHERE isErased = 1
|
||||
AND isDeleted = 0`,
|
||||
async ({noteId}) => {
|
||||
({noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const note = await repository.getNote(noteId);
|
||||
const note = repository.getNote(noteId);
|
||||
note.isDeleted = true;
|
||||
await note.save();
|
||||
note.save();
|
||||
|
||||
logFix(`Note ${noteId} was set to deleted since it is erased`);
|
||||
}
|
||||
@@ -451,23 +451,23 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT parentNoteId
|
||||
FROM branches
|
||||
JOIN notes ON notes.noteId = branches.parentNoteId
|
||||
WHERE notes.isDeleted = 0
|
||||
AND notes.type == 'search'
|
||||
AND branches.isDeleted = 0`,
|
||||
async ({parentNoteId}) => {
|
||||
({parentNoteId}) => {
|
||||
if (this.autoFix) {
|
||||
const branches = await repository.getEntities(`SELECT *
|
||||
const branches = repository.getEntities(`SELECT *
|
||||
FROM branches
|
||||
WHERE isDeleted = 0
|
||||
AND parentNoteId = ?`, [parentNoteId]);
|
||||
|
||||
for (const branch of branches) {
|
||||
branch.parentNoteId = 'root';
|
||||
await branch.save();
|
||||
branch.save();
|
||||
|
||||
logFix(`Child branch ${branch.branchId} has been moved to root since it was a child of a search note ${parentNoteId}`)
|
||||
}
|
||||
@@ -476,17 +476,17 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT attributeId
|
||||
FROM attributes
|
||||
WHERE isDeleted = 0
|
||||
AND type = 'relation'
|
||||
AND value = ''`,
|
||||
async ({attributeId}) => {
|
||||
({attributeId}) => {
|
||||
if (this.autoFix) {
|
||||
const relation = await repository.getAttribute(attributeId);
|
||||
const relation = repository.getAttribute(attributeId);
|
||||
relation.isDeleted = true;
|
||||
await relation.save();
|
||||
relation.save();
|
||||
|
||||
logFix(`Removed relation ${relation.attributeId} of name "${relation.name} with empty target.`);
|
||||
} else {
|
||||
@@ -494,7 +494,7 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT attributeId,
|
||||
type
|
||||
FROM attributes
|
||||
@@ -503,11 +503,11 @@ class ConsistencyChecks {
|
||||
AND type != 'label-definition'
|
||||
AND type != 'relation'
|
||||
AND type != 'relation-definition'`,
|
||||
async ({attributeId, type}) => {
|
||||
({attributeId, type}) => {
|
||||
if (this.autoFix) {
|
||||
const attribute = await repository.getAttribute(attributeId);
|
||||
const attribute = repository.getAttribute(attributeId);
|
||||
attribute.type = 'label';
|
||||
await attribute.save();
|
||||
attribute.save();
|
||||
|
||||
logFix(`Attribute ${attributeId} type was changed to label since it had invalid type '${type}'`);
|
||||
} else {
|
||||
@@ -515,18 +515,18 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT attributeId,
|
||||
attributes.noteId
|
||||
FROM attributes
|
||||
JOIN notes ON attributes.noteId = notes.noteId
|
||||
WHERE attributes.isDeleted = 0
|
||||
AND notes.isDeleted = 1`,
|
||||
async ({attributeId, noteId}) => {
|
||||
({attributeId, noteId}) => {
|
||||
if (this.autoFix) {
|
||||
const attribute = await repository.getAttribute(attributeId);
|
||||
const attribute = repository.getAttribute(attributeId);
|
||||
attribute.isDeleted = true;
|
||||
await attribute.save();
|
||||
attribute.save();
|
||||
|
||||
logFix(`Removed attribute ${attributeId} because owning note ${noteId} is also deleted.`);
|
||||
} else {
|
||||
@@ -534,7 +534,7 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT attributeId,
|
||||
attributes.value AS targetNoteId
|
||||
FROM attributes
|
||||
@@ -542,11 +542,11 @@ class ConsistencyChecks {
|
||||
WHERE attributes.type = 'relation'
|
||||
AND attributes.isDeleted = 0
|
||||
AND notes.isDeleted = 1`,
|
||||
async ({attributeId, targetNoteId}) => {
|
||||
({attributeId, targetNoteId}) => {
|
||||
if (this.autoFix) {
|
||||
const attribute = await repository.getAttribute(attributeId);
|
||||
const attribute = repository.getAttribute(attributeId);
|
||||
attribute.isDeleted = true;
|
||||
await attribute.save();
|
||||
attribute.save();
|
||||
|
||||
logFix(`Removed attribute ${attributeId} because target note ${targetNoteId} is also deleted.`);
|
||||
} else {
|
||||
@@ -555,8 +555,8 @@ class ConsistencyChecks {
|
||||
});
|
||||
}
|
||||
|
||||
async runSyncRowChecks(entityName, key) {
|
||||
await this.findAndFixIssues(`
|
||||
runSyncRowChecks(entityName, key) {
|
||||
this.findAndFixIssues(`
|
||||
SELECT
|
||||
${key} as entityId
|
||||
FROM
|
||||
@@ -564,9 +564,9 @@ class ConsistencyChecks {
|
||||
LEFT JOIN sync ON sync.entityName = '${entityName}' AND entityId = ${key}
|
||||
WHERE
|
||||
sync.id IS NULL AND ` + (entityName === 'options' ? 'options.isSynced = 1' : '1'),
|
||||
async ({entityId}) => {
|
||||
({entityId}) => {
|
||||
if (this.autoFix) {
|
||||
await syncTableService.addEntitySync(entityName, entityId);
|
||||
syncTableService.addEntitySync(entityName, entityId);
|
||||
|
||||
logFix(`Created missing sync record for entityName=${entityName}, entityId=${entityId}`);
|
||||
} else {
|
||||
@@ -574,7 +574,7 @@ class ConsistencyChecks {
|
||||
}
|
||||
});
|
||||
|
||||
await this.findAndFixIssues(`
|
||||
this.findAndFixIssues(`
|
||||
SELECT
|
||||
id, entityId
|
||||
FROM
|
||||
@@ -583,9 +583,9 @@ class ConsistencyChecks {
|
||||
WHERE
|
||||
sync.entityName = '${entityName}'
|
||||
AND ${key} IS NULL`,
|
||||
async ({id, entityId}) => {
|
||||
({id, entityId}) => {
|
||||
if (this.autoFix) {
|
||||
await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
|
||||
sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
|
||||
|
||||
logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`);
|
||||
} else {
|
||||
@@ -594,43 +594,43 @@ class ConsistencyChecks {
|
||||
});
|
||||
}
|
||||
|
||||
async findSyncRowsIssues() {
|
||||
await this.runSyncRowChecks("notes", "noteId");
|
||||
await this.runSyncRowChecks("note_contents", "noteId");
|
||||
await this.runSyncRowChecks("note_revisions", "noteRevisionId");
|
||||
await this.runSyncRowChecks("branches", "branchId");
|
||||
await this.runSyncRowChecks("recent_notes", "noteId");
|
||||
await this.runSyncRowChecks("attributes", "attributeId");
|
||||
await this.runSyncRowChecks("api_tokens", "apiTokenId");
|
||||
await this.runSyncRowChecks("options", "name");
|
||||
findSyncRowsIssues() {
|
||||
this.runSyncRowChecks("notes", "noteId");
|
||||
this.runSyncRowChecks("note_contents", "noteId");
|
||||
this.runSyncRowChecks("note_revisions", "noteRevisionId");
|
||||
this.runSyncRowChecks("branches", "branchId");
|
||||
this.runSyncRowChecks("recent_notes", "noteId");
|
||||
this.runSyncRowChecks("attributes", "attributeId");
|
||||
this.runSyncRowChecks("api_tokens", "apiTokenId");
|
||||
this.runSyncRowChecks("options", "name");
|
||||
}
|
||||
|
||||
async runAllChecks() {
|
||||
runAllChecks() {
|
||||
this.unrecoveredConsistencyErrors = false;
|
||||
this.fixedIssues = false;
|
||||
|
||||
await this.findBrokenReferenceIssues();
|
||||
this.findBrokenReferenceIssues();
|
||||
|
||||
await this.findExistencyIssues();
|
||||
this.findExistencyIssues();
|
||||
|
||||
await this.findLogicIssues();
|
||||
this.findLogicIssues();
|
||||
|
||||
await this.findSyncRowsIssues();
|
||||
this.findSyncRowsIssues();
|
||||
|
||||
// root branch should always be expanded
|
||||
await sql.execute("UPDATE branches SET isExpanded = 1 WHERE branchId = 'root'");
|
||||
sql.execute("UPDATE branches SET isExpanded = 1 WHERE branchId = 'root'");
|
||||
|
||||
if (this.unrecoveredConsistencyErrors) {
|
||||
// we run this only if basic checks passed since this assumes basic data consistency
|
||||
|
||||
await this.checkTreeCycles();
|
||||
this.checkTreeCycles();
|
||||
}
|
||||
|
||||
return !this.unrecoveredConsistencyErrors;
|
||||
}
|
||||
|
||||
async showEntityStat(name, query) {
|
||||
const map = await sql.getMap(query);
|
||||
showEntityStat(name, query) {
|
||||
const map = sql.getMap(query);
|
||||
|
||||
map[0] = map[0] || 0;
|
||||
map[1] = map[1] || 0;
|
||||
@@ -638,20 +638,20 @@ class ConsistencyChecks {
|
||||
log.info(`${name} deleted: ${map[1]}, not deleted ${map[0]}`);
|
||||
}
|
||||
|
||||
async runDbDiagnostics() {
|
||||
await this.showEntityStat("Notes", `SELECT isDeleted, count(1)
|
||||
runDbDiagnostics() {
|
||||
this.showEntityStat("Notes", `SELECT isDeleted, count(1)
|
||||
FROM notes
|
||||
GROUP BY isDeleted`);
|
||||
await this.showEntityStat("Note revisions", `SELECT isErased, count(1)
|
||||
this.showEntityStat("Note revisions", `SELECT isErased, count(1)
|
||||
FROM note_revisions
|
||||
GROUP BY isErased`);
|
||||
await this.showEntityStat("Branches", `SELECT isDeleted, count(1)
|
||||
this.showEntityStat("Branches", `SELECT isDeleted, count(1)
|
||||
FROM branches
|
||||
GROUP BY isDeleted`);
|
||||
await this.showEntityStat("Attributes", `SELECT isDeleted, count(1)
|
||||
this.showEntityStat("Attributes", `SELECT isDeleted, count(1)
|
||||
FROM attributes
|
||||
GROUP BY isDeleted`);
|
||||
await this.showEntityStat("API tokens", `SELECT isDeleted, count(1)
|
||||
this.showEntityStat("API tokens", `SELECT isDeleted, count(1)
|
||||
FROM api_tokens
|
||||
GROUP BY isDeleted`);
|
||||
}
|
||||
@@ -659,12 +659,12 @@ class ConsistencyChecks {
|
||||
async runChecks() {
|
||||
let elapsedTimeMs;
|
||||
|
||||
await syncMutexService.doExclusively(async () => {
|
||||
await syncMutexService.doExclusively(() => {
|
||||
const startTime = new Date();
|
||||
|
||||
await this.runDbDiagnostics();
|
||||
this.runDbDiagnostics();
|
||||
|
||||
await this.runAllChecks();
|
||||
this.runAllChecks();
|
||||
|
||||
elapsedTimeMs = Date.now() - startTime.getTime();
|
||||
});
|
||||
@@ -687,25 +687,23 @@ function logError(message) {
|
||||
log.info("Consistency error: " + message);
|
||||
}
|
||||
|
||||
async function runPeriodicChecks() {
|
||||
const autoFix = await optionsService.getOptionBool('autoFixConsistencyIssues');
|
||||
|
||||
function runPeriodicChecks() {
|
||||
const autoFix = optionsService.getOptionBool('autoFixConsistencyIssues');
|
||||
|
||||
const consistencyChecks = new ConsistencyChecks(autoFix);
|
||||
await consistencyChecks.runChecks();
|
||||
consistencyChecks.runChecks();
|
||||
}
|
||||
|
||||
async function runOnDemandChecks(autoFix) {
|
||||
function runOnDemandChecks(autoFix) {
|
||||
const consistencyChecks = new ConsistencyChecks(autoFix);
|
||||
await consistencyChecks.runChecks();
|
||||
consistencyChecks.runChecks();
|
||||
}
|
||||
|
||||
sqlInit.dbReady.then(() => {
|
||||
setInterval(cls.wrap(runPeriodicChecks), 60 * 60 * 1000);
|
||||
setInterval(cls.wrap(runPeriodicChecks), 60 * 60 * 1000);
|
||||
|
||||
// kickoff checks soon after startup (to not block the initial load)
|
||||
setTimeout(cls.wrap(runPeriodicChecks), 20 * 1000);
|
||||
});
|
||||
// kickoff checks soon after startup (to not block the initial load)
|
||||
setTimeout(cls.wrap(runPeriodicChecks), 20 * 1000);
|
||||
|
||||
module.exports = {
|
||||
runOnDemandChecks
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user