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