Migrated WebSQL adapter to its own repository
This commit is contained in:
		
							parent
							
								
									4e9f5b2512
								
							
						
					
					
						commit
						aaf42d8f60
					
				| @ -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
	
	Block a user