mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
Co-authored-by: himself65 <himself65@outlook.com> Co-authored-by: Peng Xiao <pengxiao@outlook.com>
153 lines
3.6 KiB
TypeScript
153 lines
3.6 KiB
TypeScript
import assert from 'assert';
|
|
import type { Database } from 'better-sqlite3';
|
|
import sqlite from 'better-sqlite3';
|
|
|
|
import { logger } from '../logger';
|
|
|
|
const schemas = [
|
|
`CREATE TABLE IF NOT EXISTS "updates" (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
data BLOB NOT NULL,
|
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS "blobs" (
|
|
key TEXT PRIMARY KEY NOT NULL,
|
|
data BLOB NOT NULL,
|
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
)`,
|
|
];
|
|
|
|
interface UpdateRow {
|
|
id: number;
|
|
data: Buffer;
|
|
timestamp: string;
|
|
}
|
|
|
|
interface BlobRow {
|
|
key: string;
|
|
data: Buffer;
|
|
timestamp: string;
|
|
}
|
|
|
|
/**
|
|
* A base class for SQLite DB adapter that provides basic methods around updates & blobs
|
|
*/
|
|
export abstract class BaseSQLiteAdapter {
|
|
db: Database | null = null;
|
|
abstract role: string;
|
|
|
|
constructor(public path: string) {}
|
|
|
|
ensureTables() {
|
|
assert(this.db, 'db is not connected');
|
|
this.db.exec(schemas.join(';'));
|
|
}
|
|
|
|
// todo: what if SQLite DB wrapper later is not sync?
|
|
connect(): Database | undefined {
|
|
if (this.db) {
|
|
return this.db;
|
|
}
|
|
logger.log(`[SQLiteAdapter][${this.role}] open db`, this.path);
|
|
const db = (this.db = sqlite(this.path));
|
|
this.ensureTables();
|
|
return db;
|
|
}
|
|
|
|
destroy() {
|
|
this.db?.close();
|
|
this.db = null;
|
|
}
|
|
|
|
addBlob(key: string, data: Uint8Array) {
|
|
try {
|
|
assert(this.db, 'db is not connected');
|
|
const statement = this.db.prepare(
|
|
'INSERT INTO blobs (key, data) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET data = ?'
|
|
);
|
|
statement.run(key, data, data);
|
|
return key;
|
|
} catch (error) {
|
|
logger.error('addBlob', error);
|
|
}
|
|
}
|
|
|
|
getBlob(key: string) {
|
|
try {
|
|
assert(this.db, 'db is not connected');
|
|
const statement = this.db.prepare('SELECT data FROM blobs WHERE key = ?');
|
|
const row = statement.get(key) as BlobRow;
|
|
if (!row) {
|
|
return null;
|
|
}
|
|
return row.data;
|
|
} catch (error) {
|
|
logger.error('getBlob', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
deleteBlob(key: string) {
|
|
try {
|
|
assert(this.db, 'db is not connected');
|
|
const statement = this.db.prepare('DELETE FROM blobs WHERE key = ?');
|
|
statement.run(key);
|
|
} catch (error) {
|
|
logger.error('deleteBlob', error);
|
|
}
|
|
}
|
|
|
|
getBlobKeys() {
|
|
try {
|
|
assert(this.db, 'db is not connected');
|
|
const statement = this.db.prepare('SELECT key FROM blobs');
|
|
const rows = statement.all() as BlobRow[];
|
|
return rows.map(row => row.key);
|
|
} catch (error) {
|
|
logger.error('getBlobKeys', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
getUpdates() {
|
|
try {
|
|
assert(this.db, 'db is not connected');
|
|
const statement = this.db.prepare('SELECT * FROM updates');
|
|
const rows = statement.all() as UpdateRow[];
|
|
return rows;
|
|
} catch (error) {
|
|
logger.error('getUpdates', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// add a single update to SQLite
|
|
addUpdateToSQLite(updates: Uint8Array[]) {
|
|
// batch write instead write per key stroke?
|
|
try {
|
|
assert(this.db, 'db is not connected');
|
|
const start = performance.now();
|
|
const statement = this.db.prepare(
|
|
'INSERT INTO updates (data) VALUES (?)'
|
|
);
|
|
const insertMany = this.db.transaction(updates => {
|
|
for (const d of updates) {
|
|
statement.run(d);
|
|
}
|
|
});
|
|
|
|
insertMany(updates);
|
|
|
|
logger.debug(
|
|
`[SQLiteAdapter][${this.role}] addUpdateToSQLite`,
|
|
'length:',
|
|
updates.length,
|
|
performance.now() - start,
|
|
'ms'
|
|
);
|
|
} catch (error) {
|
|
logger.error('addUpdateToSQLite', error);
|
|
}
|
|
}
|
|
}
|