Added the ability to execute sql outside of transactions with the database.sql method
This commit is contained in:
		
							parent
							
								
									da2a186c95
								
							
						
					
					
						commit
						7387e20b09
					
				@ -61,7 +61,7 @@ const db = await PawSQLite.open("test", {
 | 
			
		||||
 | 
			
		||||
### Querying the database:
 | 
			
		||||
 | 
			
		||||
You can query the database by using the `sql` method on a transaction object. For convenience, database objects also contain an `sql` method which is shorthand for `db.autoTransaction((tx) => tx.sql(sql, ...args));`. The `sql` method also allows you to bind parameters to the satatement. Bound parameters are escaped by whichever native SQLite implementation your chosen adapter uses.
 | 
			
		||||
You can query the database by using the `sql` method on a database or transaction object. The `sql` method also allows you to bind parameters to the satatement. Bound parameters are escaped by whichever native SQLite implementation your chosen adapter uses.
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
await db.sql("SELECT * FROM contacts WHERE name=?", "Paul");
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										278
									
								
								cjs/pawsqlite.js
									
									
									
									
									
								
							
							
						
						
									
										278
									
								
								cjs/pawsqlite.js
									
									
									
									
									
								
							@ -65,7 +65,11 @@ __webpack_require__.r(__webpack_exports__);
 | 
			
		||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
 | 
			
		||||
/* harmony export */   "Database": () => (/* binding */ Database)
 | 
			
		||||
/* harmony export */ });
 | 
			
		||||
/* harmony import */ var _transaction_manager_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./transaction_manager.mjs */ "./src/transaction_manager.mjs");
 | 
			
		||||
/* harmony import */ var _task_manager_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./task_manager.mjs */ "./src/task_manager.mjs");
 | 
			
		||||
/* harmony import */ var _transaction_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./transaction.mjs */ "./src/transaction.mjs");
 | 
			
		||||
/* harmony import */ var _query_mjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./query.mjs */ "./src/query.mjs");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -76,7 +80,7 @@ class Database {
 | 
			
		||||
    this.version = null;
 | 
			
		||||
    this.path = null;
 | 
			
		||||
 | 
			
		||||
    this.transactionManager = new _transaction_manager_mjs__WEBPACK_IMPORTED_MODULE_0__.TransactionManager(dbName, this.adapter);
 | 
			
		||||
    this.taskManager = new _task_manager_mjs__WEBPACK_IMPORTED_MODULE_0__.TaskManager();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async open() {
 | 
			
		||||
@ -100,10 +104,13 @@ class Database {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  transaction() {
 | 
			
		||||
    return this.transactionManager.transaction();
 | 
			
		||||
    return new _transaction_mjs__WEBPACK_IMPORTED_MODULE_1__.Transaction(
 | 
			
		||||
      this.dbName,
 | 
			
		||||
      this.adapter,
 | 
			
		||||
      this.taskManager.enqueue.bind(this.taskManager)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  async autoTransaction(cb, inheritTx) {
 | 
			
		||||
    let tx = inheritTx || this.transaction();
 | 
			
		||||
    let result;
 | 
			
		||||
@ -125,9 +132,23 @@ class Database {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  // Helper method to start a transaction and execute a single SQL statement
 | 
			
		||||
  sql(sql, ...args) {
 | 
			
		||||
    return this.autoTransaction((tx) => tx.sql(sql, ...args));
 | 
			
		||||
  // Execute a single SQL statement
 | 
			
		||||
  async sql(sql, ...args) {
 | 
			
		||||
    const completeCb = await this.taskManager.enqueue();
 | 
			
		||||
 | 
			
		||||
    let result;
 | 
			
		||||
    let error;
 | 
			
		||||
    try {
 | 
			
		||||
      result = await this.adapter.sql(this.dbName, ...(0,_query_mjs__WEBPACK_IMPORTED_MODULE_2__.query)(sql, ...args));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      error = e;
 | 
			
		||||
    }
 | 
			
		||||
    completeCb();
 | 
			
		||||
 | 
			
		||||
    if (error) {
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
    return new Result(result);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -258,6 +279,68 @@ class PawSQLiteError extends Error {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/query.mjs":
 | 
			
		||||
/*!***********************!*\
 | 
			
		||||
  !*** ./src/query.mjs ***!
 | 
			
		||||
  \***********************/
 | 
			
		||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
 | 
			
		||||
 | 
			
		||||
__webpack_require__.r(__webpack_exports__);
 | 
			
		||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
 | 
			
		||||
/* harmony export */   "query": () => (/* binding */ query),
 | 
			
		||||
/* harmony export */   "buildQuery": () => (/* binding */ buildQuery),
 | 
			
		||||
/* harmony export */   "validateQuery": () => (/* binding */ validateQuery)
 | 
			
		||||
/* harmony export */ });
 | 
			
		||||
function query(sql, ...args) {
 | 
			
		||||
  validateQuery(sql, ...args);
 | 
			
		||||
  return buildQuery(sql, ...args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Allow for slightly more complex parameter substitution.
 | 
			
		||||
// Instances of "???" will be replaced by the same number of comma-separated
 | 
			
		||||
// question marks as items in the corresponding nested parateter array
 | 
			
		||||
// eg. buildQuery("SELECT (???) FROM ?", [["col1", "col2"], "table1"])
 | 
			
		||||
// would output: ["SELECT (?, ?) FROM ?", ["col1", "col2", "table1"]]
 | 
			
		||||
function buildQuery(sql, ...args) {
 | 
			
		||||
  const parts = sql.split("???");
 | 
			
		||||
  const subParamLengths = args
 | 
			
		||||
    .filter(Array.isArray)
 | 
			
		||||
    .map((a) => a.length);
 | 
			
		||||
  if (parts.length !== subParamLengths.length + 1) {
 | 
			
		||||
    throw new PawSQLiteError("Unable to build query: sub-" +
 | 
			
		||||
      "paramters do not match sub-paramters in query");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const newQuery = parts.reduce((p1, p2, i) => {
 | 
			
		||||
    const length = subParamLengths[i - 1];
 | 
			
		||||
    return p1 + new Array(length).fill("?").join(", ") + p2;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const flatParams = args.reduce((acc, v) => {
 | 
			
		||||
    if (Array.isArray(v)) {
 | 
			
		||||
      Array.prototype.push.apply(acc, v);
 | 
			
		||||
    } else {
 | 
			
		||||
      acc.push(v);
 | 
			
		||||
    }
 | 
			
		||||
    return acc;
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return [newQuery, ...flatParams];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function validateQuery(sql, ...args) {
 | 
			
		||||
  const reg = /^\s*(BEGIN|END|COMMIT|ROLLBACK)(?:[^A-Z]|$)/i;
 | 
			
		||||
  const match = reg.exec(sql);
 | 
			
		||||
  if (match) {
 | 
			
		||||
    const statement = match[1].toUpperCase();
 | 
			
		||||
    throw new PawSQLiteError("Manually managing transactions is " +
 | 
			
		||||
      "forbidden. Found: \"" + statement + "\" statement.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/result.mjs":
 | 
			
		||||
@ -293,6 +376,65 @@ class Result extends Array {
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/task_manager.mjs":
 | 
			
		||||
/*!******************************!*\
 | 
			
		||||
  !*** ./src/task_manager.mjs ***!
 | 
			
		||||
  \******************************/
 | 
			
		||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
 | 
			
		||||
 | 
			
		||||
__webpack_require__.r(__webpack_exports__);
 | 
			
		||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
 | 
			
		||||
/* harmony export */   "TaskManager": () => (/* binding */ TaskManager)
 | 
			
		||||
/* harmony export */ });
 | 
			
		||||
class TaskManager {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this._queue = [];
 | 
			
		||||
    this._inTask = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  enqueue() {
 | 
			
		||||
    let completeSignal;
 | 
			
		||||
    let taskComplete = new Promise((r, _) => {
 | 
			
		||||
      completeSignal = r;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let readySignal;
 | 
			
		||||
    let dbReady = new Promise((r, _) => {
 | 
			
		||||
      readySignal = () => {
 | 
			
		||||
        r(completeSignal);
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._queue.push({readySignal, taskComplete});
 | 
			
		||||
 | 
			
		||||
    this._processQueue();
 | 
			
		||||
    return dbReady;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _processQueue() {
 | 
			
		||||
    // We're already processing the queue
 | 
			
		||||
    if (this._inTask) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while (true) {
 | 
			
		||||
      let item = this._queue.shift();
 | 
			
		||||
      if (!item) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this._inTask = true;
 | 
			
		||||
 | 
			
		||||
      item.readySignal();
 | 
			
		||||
      await item.taskComplete;
 | 
			
		||||
 | 
			
		||||
      this._inTask = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/transaction.mjs":
 | 
			
		||||
/*!*****************************!*\
 | 
			
		||||
  !*** ./src/transaction.mjs ***!
 | 
			
		||||
@ -305,6 +447,8 @@ __webpack_require__.r(__webpack_exports__);
 | 
			
		||||
/* harmony export */ });
 | 
			
		||||
/* harmony import */ var _result_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./result.mjs */ "./src/result.mjs");
 | 
			
		||||
/* harmony import */ var _pawsqlite_error_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./pawsqlite_error.mjs */ "./src/pawsqlite_error.mjs");
 | 
			
		||||
/* harmony import */ var _query_mjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./query.mjs */ "./src/query.mjs");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -332,46 +476,16 @@ class Transaction {
 | 
			
		||||
      await this._waitUntilReady();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let result;
 | 
			
		||||
    try {
 | 
			
		||||
      return await this._executeSQL(sql, ...args);
 | 
			
		||||
      result = await this.adapter.sql(this.dbName, ...(0,_query_mjs__WEBPACK_IMPORTED_MODULE_2__.query)(sql, ...args));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if (this._rollbackOnError) {
 | 
			
		||||
        await this.rollback();
 | 
			
		||||
      }
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Allow for slightly more complex parameter substitution.
 | 
			
		||||
  // Instances of "???" will be replaced by the same number of comma-separated
 | 
			
		||||
  // question marks as items in the corresponding nested parateter array
 | 
			
		||||
  // eg. buildQuery("SELECT (???) FROM ?", [["col1", "col2"], "table1"])
 | 
			
		||||
  // would output: ["SELECT (?, ?) FROM ?", ["col1", "col2", "table1"]]
 | 
			
		||||
  buildQuery(sql, ...args) {
 | 
			
		||||
    const parts = sql.split("???");
 | 
			
		||||
    const subParamLengths = args
 | 
			
		||||
      .filter(Array.isArray)
 | 
			
		||||
      .map((a) => a.length);
 | 
			
		||||
    if (parts.length !== subParamLengths.length + 1) {
 | 
			
		||||
      throw new _pawsqlite_error_mjs__WEBPACK_IMPORTED_MODULE_1__.PawSQLiteError("Unable to build query: sub-" +
 | 
			
		||||
        "paramters do not match sub-paramters in query");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const newQuery = parts.reduce((p1, p2, i) => {
 | 
			
		||||
      const length = subParamLengths[i - 1];
 | 
			
		||||
      return p1 + new Array(length).fill("?").join(", ") + p2;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const flatParams = args.reduce((acc, v) => {
 | 
			
		||||
      if (Array.isArray(v)) {
 | 
			
		||||
        Array.prototype.push.apply(acc, v);
 | 
			
		||||
      } else {
 | 
			
		||||
        acc.push(v);
 | 
			
		||||
      }
 | 
			
		||||
      return acc;
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    return [newQuery, ...flatParams];
 | 
			
		||||
    return new _result_mjs__WEBPACK_IMPORTED_MODULE_0__.Result(result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  commit() {
 | 
			
		||||
@ -394,21 +508,6 @@ class Transaction {
 | 
			
		||||
    await this._readyWait;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _executeSQL(sql, ...args) {
 | 
			
		||||
    const reg = /^\s*(BEGIN|END|COMMIT|ROLLBACK)(?:[^A-Z]|$)/i;
 | 
			
		||||
    const match = reg.exec(sql);
 | 
			
		||||
    if (match) {
 | 
			
		||||
      const statement = match[1].toUpperCase();
 | 
			
		||||
      throw new _pawsqlite_error_mjs__WEBPACK_IMPORTED_MODULE_1__.PawSQLiteError("Manually managing transactions is " +
 | 
			
		||||
        "forbidden. Found: \"" + statement + "\" statement.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = await this.adapter.sql(this.dbName,
 | 
			
		||||
      ...this.buildQuery(sql, ...args));
 | 
			
		||||
 | 
			
		||||
    return new _result_mjs__WEBPACK_IMPORTED_MODULE_0__.Result(result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _begin() {
 | 
			
		||||
    const result = await this.adapter.sql(this.dbName, "BEGIN");
 | 
			
		||||
  }
 | 
			
		||||
@ -444,75 +543,6 @@ class Transaction {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***/ }),
 | 
			
		||||
 | 
			
		||||
/***/ "./src/transaction_manager.mjs":
 | 
			
		||||
/*!*************************************!*\
 | 
			
		||||
  !*** ./src/transaction_manager.mjs ***!
 | 
			
		||||
  \*************************************/
 | 
			
		||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
 | 
			
		||||
 | 
			
		||||
__webpack_require__.r(__webpack_exports__);
 | 
			
		||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
 | 
			
		||||
/* harmony export */   "TransactionManager": () => (/* binding */ TransactionManager)
 | 
			
		||||
/* harmony export */ });
 | 
			
		||||
/* harmony import */ var _transaction_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./transaction.mjs */ "./src/transaction.mjs");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TransactionManager {
 | 
			
		||||
  constructor(dbName, adapter) {
 | 
			
		||||
    this.dbName = dbName;
 | 
			
		||||
    this.adapter = adapter;
 | 
			
		||||
    this._queue = [];
 | 
			
		||||
    this._inTransaction = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  transaction() {
 | 
			
		||||
    return new _transaction_mjs__WEBPACK_IMPORTED_MODULE_0__.Transaction(this.dbName, this.adapter,
 | 
			
		||||
      this.enqueue.bind(this));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  enqueue() {
 | 
			
		||||
    let completeSignal;
 | 
			
		||||
    let transactionComplete = new Promise((r, _) => {
 | 
			
		||||
      completeSignal = r;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let readySignal;
 | 
			
		||||
    let dbReady = new Promise((r, _) => {
 | 
			
		||||
      readySignal = () => {
 | 
			
		||||
        r(completeSignal);
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._queue.push({readySignal, transactionComplete});
 | 
			
		||||
 | 
			
		||||
    this._processQueue();
 | 
			
		||||
    return dbReady;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _processQueue() {
 | 
			
		||||
    // We're already processing the queue
 | 
			
		||||
    if (this._inTransaction) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while (true) {
 | 
			
		||||
      let item = this._queue.shift();
 | 
			
		||||
      if (!item) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this._inTransaction = true;
 | 
			
		||||
 | 
			
		||||
      item.readySignal();
 | 
			
		||||
      await item.transactionComplete;
 | 
			
		||||
 | 
			
		||||
      this._inTransaction = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/***/ })
 | 
			
		||||
 | 
			
		||||
/******/ 	});
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -1,4 +1,6 @@
 | 
			
		||||
import { TransactionManager } from "./transaction_manager.mjs";
 | 
			
		||||
import { TaskManager } from "./task_manager.mjs";
 | 
			
		||||
import { Transaction } from "./transaction.mjs";
 | 
			
		||||
import { query } from "./query.mjs";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class Database {
 | 
			
		||||
@ -8,7 +10,7 @@ export class Database {
 | 
			
		||||
    this.version = null;
 | 
			
		||||
    this.path = null;
 | 
			
		||||
 | 
			
		||||
    this.transactionManager = new TransactionManager(dbName, this.adapter);
 | 
			
		||||
    this.taskManager = new TaskManager();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async open() {
 | 
			
		||||
@ -32,10 +34,13 @@ export class Database {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  transaction() {
 | 
			
		||||
    return this.transactionManager.transaction();
 | 
			
		||||
    return new Transaction(
 | 
			
		||||
      this.dbName,
 | 
			
		||||
      this.adapter,
 | 
			
		||||
      this.taskManager.enqueue.bind(this.taskManager)
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  async autoTransaction(cb, inheritTx) {
 | 
			
		||||
    let tx = inheritTx || this.transaction();
 | 
			
		||||
    let result;
 | 
			
		||||
@ -57,8 +62,22 @@ export class Database {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  // Helper method to start a transaction and execute a single SQL statement
 | 
			
		||||
  sql(sql, ...args) {
 | 
			
		||||
    return this.autoTransaction((tx) => tx.sql(sql, ...args));
 | 
			
		||||
  // Execute a single SQL statement
 | 
			
		||||
  async sql(sql, ...args) {
 | 
			
		||||
    const completeCb = await this.taskManager.enqueue();
 | 
			
		||||
 | 
			
		||||
    let result;
 | 
			
		||||
    let error;
 | 
			
		||||
    try {
 | 
			
		||||
      result = await this.adapter.sql(this.dbName, ...query(sql, ...args));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      error = e;
 | 
			
		||||
    }
 | 
			
		||||
    completeCb();
 | 
			
		||||
 | 
			
		||||
    if (error) {
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
    return new Result(result);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								src/query.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/query.mjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
export function query(sql, ...args) {
 | 
			
		||||
  validateQuery(sql, ...args);
 | 
			
		||||
  return buildQuery(sql, ...args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Allow for slightly more complex parameter substitution.
 | 
			
		||||
// Instances of "???" will be replaced by the same number of comma-separated
 | 
			
		||||
// question marks as items in the corresponding nested parateter array
 | 
			
		||||
// eg. buildQuery("SELECT (???) FROM ?", [["col1", "col2"], "table1"])
 | 
			
		||||
// would output: ["SELECT (?, ?) FROM ?", ["col1", "col2", "table1"]]
 | 
			
		||||
export function buildQuery(sql, ...args) {
 | 
			
		||||
  const parts = sql.split("???");
 | 
			
		||||
  const subParamLengths = args
 | 
			
		||||
    .filter(Array.isArray)
 | 
			
		||||
    .map((a) => a.length);
 | 
			
		||||
  if (parts.length !== subParamLengths.length + 1) {
 | 
			
		||||
    throw new PawSQLiteError("Unable to build query: sub-" +
 | 
			
		||||
      "paramters do not match sub-paramters in query");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const newQuery = parts.reduce((p1, p2, i) => {
 | 
			
		||||
    const length = subParamLengths[i - 1];
 | 
			
		||||
    return p1 + new Array(length).fill("?").join(", ") + p2;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const flatParams = args.reduce((acc, v) => {
 | 
			
		||||
    if (Array.isArray(v)) {
 | 
			
		||||
      Array.prototype.push.apply(acc, v);
 | 
			
		||||
    } else {
 | 
			
		||||
      acc.push(v);
 | 
			
		||||
    }
 | 
			
		||||
    return acc;
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return [newQuery, ...flatParams];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function validateQuery(sql, ...args) {
 | 
			
		||||
  const reg = /^\s*(BEGIN|END|COMMIT|ROLLBACK)(?:[^A-Z]|$)/i;
 | 
			
		||||
  const match = reg.exec(sql);
 | 
			
		||||
  if (match) {
 | 
			
		||||
    const statement = match[1].toUpperCase();
 | 
			
		||||
    throw new PawSQLiteError("Manually managing transactions is " +
 | 
			
		||||
      "forbidden. Found: \"" + statement + "\" statement.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								src/task_manager.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/task_manager.mjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
export class TaskManager {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this._queue = [];
 | 
			
		||||
    this._inTask = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  enqueue() {
 | 
			
		||||
    let completeSignal;
 | 
			
		||||
    let taskComplete = new Promise((r, _) => {
 | 
			
		||||
      completeSignal = r;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let readySignal;
 | 
			
		||||
    let dbReady = new Promise((r, _) => {
 | 
			
		||||
      readySignal = () => {
 | 
			
		||||
        r(completeSignal);
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._queue.push({readySignal, taskComplete});
 | 
			
		||||
 | 
			
		||||
    this._processQueue();
 | 
			
		||||
    return dbReady;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _processQueue() {
 | 
			
		||||
    // We're already processing the queue
 | 
			
		||||
    if (this._inTask) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while (true) {
 | 
			
		||||
      let item = this._queue.shift();
 | 
			
		||||
      if (!item) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this._inTask = true;
 | 
			
		||||
 | 
			
		||||
      item.readySignal();
 | 
			
		||||
      await item.taskComplete;
 | 
			
		||||
 | 
			
		||||
      this._inTask = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
import { Result } from "./result.mjs";
 | 
			
		||||
import { PawSQLiteError } from "./pawsqlite_error.mjs";
 | 
			
		||||
import { query } from "./query.mjs";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class Transaction {
 | 
			
		||||
@ -25,46 +26,16 @@ export class Transaction {
 | 
			
		||||
      await this._waitUntilReady();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let result;
 | 
			
		||||
    try {
 | 
			
		||||
      return await this._executeSQL(sql, ...args);
 | 
			
		||||
      result = await this.adapter.sql(this.dbName, ...query(sql, ...args));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if (this._rollbackOnError) {
 | 
			
		||||
        await this.rollback();
 | 
			
		||||
      }
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Allow for slightly more complex parameter substitution.
 | 
			
		||||
  // Instances of "???" will be replaced by the same number of comma-separated
 | 
			
		||||
  // question marks as items in the corresponding nested parateter array
 | 
			
		||||
  // eg. buildQuery("SELECT (???) FROM ?", [["col1", "col2"], "table1"])
 | 
			
		||||
  // would output: ["SELECT (?, ?) FROM ?", ["col1", "col2", "table1"]]
 | 
			
		||||
  buildQuery(sql, ...args) {
 | 
			
		||||
    const parts = sql.split("???");
 | 
			
		||||
    const subParamLengths = args
 | 
			
		||||
      .filter(Array.isArray)
 | 
			
		||||
      .map((a) => a.length);
 | 
			
		||||
    if (parts.length !== subParamLengths.length + 1) {
 | 
			
		||||
      throw new PawSQLiteError("Unable to build query: sub-" +
 | 
			
		||||
        "paramters do not match sub-paramters in query");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const newQuery = parts.reduce((p1, p2, i) => {
 | 
			
		||||
      const length = subParamLengths[i - 1];
 | 
			
		||||
      return p1 + new Array(length).fill("?").join(", ") + p2;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const flatParams = args.reduce((acc, v) => {
 | 
			
		||||
      if (Array.isArray(v)) {
 | 
			
		||||
        Array.prototype.push.apply(acc, v);
 | 
			
		||||
      } else {
 | 
			
		||||
        acc.push(v);
 | 
			
		||||
      }
 | 
			
		||||
      return acc;
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    return [newQuery, ...flatParams];
 | 
			
		||||
    return new Result(result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  commit() {
 | 
			
		||||
@ -87,21 +58,6 @@ export class Transaction {
 | 
			
		||||
    await this._readyWait;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _executeSQL(sql, ...args) {
 | 
			
		||||
    const reg = /^\s*(BEGIN|END|COMMIT|ROLLBACK)(?:[^A-Z]|$)/i;
 | 
			
		||||
    const match = reg.exec(sql);
 | 
			
		||||
    if (match) {
 | 
			
		||||
      const statement = match[1].toUpperCase();
 | 
			
		||||
      throw new PawSQLiteError("Manually managing transactions is " +
 | 
			
		||||
        "forbidden. Found: \"" + statement + "\" statement.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = await this.adapter.sql(this.dbName,
 | 
			
		||||
      ...this.buildQuery(sql, ...args));
 | 
			
		||||
 | 
			
		||||
    return new Result(result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _begin() {
 | 
			
		||||
    const result = await this.adapter.sql(this.dbName, "BEGIN");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,55 +0,0 @@
 | 
			
		||||
import { Transaction } from "./transaction.mjs";
 | 
			
		||||
 | 
			
		||||
export class TransactionManager {
 | 
			
		||||
  constructor(dbName, adapter) {
 | 
			
		||||
    this.dbName = dbName;
 | 
			
		||||
    this.adapter = adapter;
 | 
			
		||||
    this._queue = [];
 | 
			
		||||
    this._inTransaction = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  transaction() {
 | 
			
		||||
    return new Transaction(this.dbName, this.adapter,
 | 
			
		||||
      this.enqueue.bind(this));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  enqueue() {
 | 
			
		||||
    let completeSignal;
 | 
			
		||||
    let transactionComplete = new Promise((r, _) => {
 | 
			
		||||
      completeSignal = r;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let readySignal;
 | 
			
		||||
    let dbReady = new Promise((r, _) => {
 | 
			
		||||
      readySignal = () => {
 | 
			
		||||
        r(completeSignal);
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this._queue.push({readySignal, transactionComplete});
 | 
			
		||||
 | 
			
		||||
    this._processQueue();
 | 
			
		||||
    return dbReady;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _processQueue() {
 | 
			
		||||
    // We're already processing the queue
 | 
			
		||||
    if (this._inTransaction) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while (true) {
 | 
			
		||||
      let item = this._queue.shift();
 | 
			
		||||
      if (!item) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this._inTransaction = true;
 | 
			
		||||
 | 
			
		||||
      item.readySignal();
 | 
			
		||||
      await item.transactionComplete;
 | 
			
		||||
 | 
			
		||||
      this._inTransaction = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user