Ben Ashton
3 years ago
6 changed files with 0 additions and 267 deletions
@ -1,30 +0,0 @@ |
|||||||
import { TransactionManager } from "./transaction_manager.mjs"; |
|
||||||
|
|
||||||
|
|
||||||
export class DatabaseWrapper { |
|
||||||
constructor(dbName, version) { |
|
||||||
this.name = dbName; |
|
||||||
this.db = openDatabase(this.name, version, "", 5 * 1024 * 1024); |
|
||||||
this.transManager = new TransactionManager(this.db); |
|
||||||
} |
|
||||||
|
|
||||||
sql(sql, ...args) { |
|
||||||
const reg = /^\s*(BEGIN|END|COMMIT|ROLLBACK)(?:[^A-Z]|$)/i; |
|
||||||
const match = reg.exec(sql); |
|
||||||
if (match) { |
|
||||||
const statement = match[1].toUpperCase(); |
|
||||||
|
|
||||||
switch(statement) { |
|
||||||
case "BEGIN": |
|
||||||
return this.transManager.begin(); |
|
||||||
case "END": |
|
||||||
case "COMMIT": |
|
||||||
return this.transManager.commit(); |
|
||||||
case "ROLLBACK": |
|
||||||
return this.transManager.rollback(); |
|
||||||
} |
|
||||||
} else { |
|
||||||
return this.transManager.sql(sql, args); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,8 +0,0 @@ |
|||||||
export const ResponseWrapper = { |
|
||||||
success: (obj = {}) => ({ |
|
||||||
success: true, |
|
||||||
...obj |
|
||||||
}) |
|
||||||
}; |
|
||||||
|
|
||||||
|
|
@ -1,22 +0,0 @@ |
|||||||
export function mapResult(originalResult) { |
|
||||||
const newResult = {}; |
|
||||||
if (!originalResult) { |
|
||||||
return newResult; |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
newResult.insertId = originalResult.insertId; |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
newResult.rowsAffected = originalResult.rowsAffected; |
|
||||||
|
|
||||||
if ("rows" in originalResult) { |
|
||||||
newResult.rows = []; |
|
||||||
|
|
||||||
for (let i = 0; i < originalResult.rows.length; i++) { |
|
||||||
newResult.rows.push(originalResult.rows.item(i)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return newResult; |
|
||||||
} |
|
@ -1,156 +0,0 @@ |
|||||||
import { WebSQLAdapterError } from "./websql_adapter_error.mjs"; |
|
||||||
import { mapResult } from "./result_mapper.mjs"; |
|
||||||
import { log } from "../../log.mjs"; |
|
||||||
|
|
||||||
|
|
||||||
class Task { |
|
||||||
constructor(job, startsTransaction=false, endsTransaction=false) { |
|
||||||
this._job = job; |
|
||||||
|
|
||||||
if (startsTransaction && endsTransaction) { |
|
||||||
throw new Error("Task cannot start and end a transaction."); |
|
||||||
} |
|
||||||
|
|
||||||
this.startsTransaction = startsTransaction; |
|
||||||
this.endsTransaction = endsTransaction; |
|
||||||
|
|
||||||
this.result = new Promise ((resolve, reject) => { |
|
||||||
this._resolve = resolve; |
|
||||||
this._reject = reject; |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
run(tx) { |
|
||||||
return this._job(tx).then((result) => { |
|
||||||
this._resolve(result); |
|
||||||
return result; |
|
||||||
}, (e) => { |
|
||||||
this._reject(e); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
export class TransactionManager { |
|
||||||
constructor(db) { |
|
||||||
this.db = db; |
|
||||||
|
|
||||||
this._txCount = 0; |
|
||||||
this._tasks = []; |
|
||||||
this._processing = false; |
|
||||||
} |
|
||||||
|
|
||||||
begin() { |
|
||||||
return this._addTask(new Task((tx) => { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
if (tx) { |
|
||||||
reject(new WebSQLAdapterError("BEGIN called with an active " + |
|
||||||
"transaction. This should not happen")); |
|
||||||
return; |
|
||||||
} |
|
||||||
this.db.transaction((tx) => { |
|
||||||
resolve(tx); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}, true)).then(() => mapResult()); |
|
||||||
} |
|
||||||
|
|
||||||
sql(sql, args = []) { |
|
||||||
return this._addTask(new Task((tx) => { |
|
||||||
return this._executeSql(tx, sql, args); |
|
||||||
})); |
|
||||||
} |
|
||||||
|
|
||||||
commit() { |
|
||||||
return this._addTask(new Task((tx) => { |
|
||||||
return Promise.resolve(mapResult()); |
|
||||||
}, false, true)); |
|
||||||
} |
|
||||||
|
|
||||||
rollback() { |
|
||||||
// Hack to manually cause rollback:
|
|
||||||
// Intentionally cause an error with rollbackOnError set to true
|
|
||||||
return this._addTask(new Task((tx) => { |
|
||||||
return this._executeSql(tx, "", [], true).catch(() => mapResult()); |
|
||||||
}, false, true)); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
async _process() { |
|
||||||
if (this._processing) { |
|
||||||
return; |
|
||||||
} |
|
||||||
this._processing = true; |
|
||||||
|
|
||||||
let tx = null; |
|
||||||
let keepaliveCount = 0; |
|
||||||
|
|
||||||
while (true) { |
|
||||||
const tasks = this._tasks; |
|
||||||
this._tasks = []; |
|
||||||
|
|
||||||
|
|
||||||
if (tasks.length) { |
|
||||||
const promises = []; |
|
||||||
|
|
||||||
for (const task of tasks) { |
|
||||||
const promise = task.run(tx); |
|
||||||
|
|
||||||
if (task.startsTransaction) { |
|
||||||
tx = await promise; |
|
||||||
this._txCount++; |
|
||||||
} else { |
|
||||||
if (task.endsTransaction) { |
|
||||||
tx = null; |
|
||||||
keepaliveCount = 0; |
|
||||||
} |
|
||||||
|
|
||||||
promises.push(promise); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
await Promise.all(promises); |
|
||||||
} else { |
|
||||||
if (tx) { |
|
||||||
await this._nop(tx); |
|
||||||
keepaliveCount++; |
|
||||||
if (keepaliveCount % 5000 === 0) { |
|
||||||
log(`Transaction: ${ this._txCount } Keepalive: #${ keepaliveCount }`); |
|
||||||
} |
|
||||||
} else { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
this._processing = false; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
_addTask(task) { |
|
||||||
this._tasks.push(task); |
|
||||||
this._process(); |
|
||||||
return task.result; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
_executeSql(tx, sql, sqlArgs = [], rollbackOnError = false) { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
if (!tx) { |
|
||||||
reject(new WebSQLAdapterError("No transaction. This should not be " + |
|
||||||
" possible")); |
|
||||||
return; |
|
||||||
} |
|
||||||
tx.executeSql(sql, sqlArgs, (tx, result) => { |
|
||||||
resolve(mapResult(result)); |
|
||||||
}, (tx, e) => { |
|
||||||
reject(WebSQLAdapterError.from(e)); |
|
||||||
return rollbackOnError; |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
_nop(tx) { |
|
||||||
return this._executeSql(tx, "SELECT 1"); |
|
||||||
} |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
import { WebSQLAdapterError } from "./websql_adapter_error.mjs"; |
|
||||||
import { DatabaseWrapper } from "./database_wrapper.mjs"; |
|
||||||
import { ResponseWrapper } from "./response_wrapper.mjs"; |
|
||||||
import { log } from "../../log.mjs"; |
|
||||||
|
|
||||||
const databases = new Map(); |
|
||||||
|
|
||||||
export const WebSQLAdapter = { |
|
||||||
open: async (dbName) => { |
|
||||||
const version = "1.0"; |
|
||||||
|
|
||||||
if (!databases.has(dbName)) { |
|
||||||
databases.set(dbName, new DatabaseWrapper(dbName, version)); |
|
||||||
} |
|
||||||
|
|
||||||
return ResponseWrapper.success({ version }); |
|
||||||
}, |
|
||||||
|
|
||||||
close: async (dbName) => { |
|
||||||
databases.delete(dbName); |
|
||||||
return ResponseWrapper.success(); |
|
||||||
}, |
|
||||||
|
|
||||||
sql: async (dbName, sql, ...args) => { |
|
||||||
log(sql); |
|
||||||
|
|
||||||
const db = databases.get(dbName); |
|
||||||
if (!db) { |
|
||||||
throw new WebSQLAdapterError("Database not open"); |
|
||||||
} |
|
||||||
|
|
||||||
const result = await db.sql(sql, ...args); |
|
||||||
return ResponseWrapper.success(result); |
|
||||||
}, |
|
||||||
|
|
||||||
delete: async (dbName) => { |
|
||||||
throw new WebSQLAdapterError("Delete not implemented"); |
|
||||||
} |
|
||||||
}; |
|
@ -1,12 +0,0 @@ |
|||||||
import { PawSQLiteError } from "../../pawsqlite_error.mjs"; |
|
||||||
|
|
||||||
|
|
||||||
export class WebSQLAdapterError extends PawSQLiteError { |
|
||||||
static from(e) { |
|
||||||
let message = ""; |
|
||||||
if (e && e.message) { |
|
||||||
message = e.message; |
|
||||||
} |
|
||||||
return new WebSQLAdapterError(message); |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue