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:
|
### 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
|
```javascript
|
||||||
await db.sql("SELECT * FROM contacts WHERE name=?", "Paul");
|
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 */ __webpack_require__.d(__webpack_exports__, {
|
||||||
/* harmony export */ "Database": () => (/* binding */ Database)
|
/* harmony export */ "Database": () => (/* binding */ Database)
|
||||||
/* harmony export */ });
|
/* 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.version = null;
|
||||||
this.path = 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() {
|
async open() {
|
||||||
@ -100,10 +104,13 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transaction() {
|
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) {
|
async autoTransaction(cb, inheritTx) {
|
||||||
let tx = inheritTx || this.transaction();
|
let tx = inheritTx || this.transaction();
|
||||||
let result;
|
let result;
|
||||||
@ -125,9 +132,23 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Helper method to start a transaction and execute a single SQL statement
|
// Execute a single SQL statement
|
||||||
sql(sql, ...args) {
|
async sql(sql, ...args) {
|
||||||
return this.autoTransaction((tx) => tx.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":
|
/***/ "./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":
|
||||||
/*!*****************************!*\
|
/*!*****************************!*\
|
||||||
!*** ./src/transaction.mjs ***!
|
!*** ./src/transaction.mjs ***!
|
||||||
@ -305,6 +447,8 @@ __webpack_require__.r(__webpack_exports__);
|
|||||||
/* harmony export */ });
|
/* harmony export */ });
|
||||||
/* harmony import */ var _result_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./result.mjs */ "./src/result.mjs");
|
/* 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 _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();
|
await this._waitUntilReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let result;
|
||||||
try {
|
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) {
|
} catch (e) {
|
||||||
if (this._rollbackOnError) {
|
if (this._rollbackOnError) {
|
||||||
await this.rollback();
|
await this.rollback();
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
return new _result_mjs__WEBPACK_IMPORTED_MODULE_0__.Result(result);
|
||||||
|
|
||||||
// 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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commit() {
|
commit() {
|
||||||
@ -394,21 +508,6 @@ class Transaction {
|
|||||||
await this._readyWait;
|
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() {
|
async _begin() {
|
||||||
const result = await this.adapter.sql(this.dbName, "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 {
|
export class Database {
|
||||||
@ -8,7 +10,7 @@ export class Database {
|
|||||||
this.version = null;
|
this.version = null;
|
||||||
this.path = null;
|
this.path = null;
|
||||||
|
|
||||||
this.transactionManager = new TransactionManager(dbName, this.adapter);
|
this.taskManager = new TaskManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
async open() {
|
async open() {
|
||||||
@ -32,10 +34,13 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transaction() {
|
transaction() {
|
||||||
return this.transactionManager.transaction();
|
return new Transaction(
|
||||||
|
this.dbName,
|
||||||
|
this.adapter,
|
||||||
|
this.taskManager.enqueue.bind(this.taskManager)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async autoTransaction(cb, inheritTx) {
|
async autoTransaction(cb, inheritTx) {
|
||||||
let tx = inheritTx || this.transaction();
|
let tx = inheritTx || this.transaction();
|
||||||
let result;
|
let result;
|
||||||
@ -57,8 +62,22 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Helper method to start a transaction and execute a single SQL statement
|
// Execute a single SQL statement
|
||||||
sql(sql, ...args) {
|
async sql(sql, ...args) {
|
||||||
return this.autoTransaction((tx) => tx.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 { Result } from "./result.mjs";
|
||||||
import { PawSQLiteError } from "./pawsqlite_error.mjs";
|
import { PawSQLiteError } from "./pawsqlite_error.mjs";
|
||||||
|
import { query } from "./query.mjs";
|
||||||
|
|
||||||
|
|
||||||
export class Transaction {
|
export class Transaction {
|
||||||
@ -25,46 +26,16 @@ export class Transaction {
|
|||||||
await this._waitUntilReady();
|
await this._waitUntilReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let result;
|
||||||
try {
|
try {
|
||||||
return await this._executeSQL(sql, ...args);
|
result = await this.adapter.sql(this.dbName, ...query(sql, ...args));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (this._rollbackOnError) {
|
if (this._rollbackOnError) {
|
||||||
await this.rollback();
|
await this.rollback();
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
return new Result(result);
|
||||||
|
|
||||||
// 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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commit() {
|
commit() {
|
||||||
@ -87,21 +58,6 @@ export class Transaction {
|
|||||||
await this._readyWait;
|
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() {
|
async _begin() {
|
||||||
const result = await this.adapter.sql(this.dbName, "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