mysql-all-in-one
Version:
A package that allows you to have a complete interaction with a MYSQL database, allowing to connect to the database, retrieve data and create queries.
634 lines (631 loc) • 29.4 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataAccessObject = void 0;
const mysql2_1 = __importDefault(require("mysql2"));
const fs_1 = __importDefault(require("fs"));
const dbgate_query_splitter_1 = require("dbgate-query-splitter");
const types_1 = require("../QueryBuilder/types");
const utils_1 = require("../QueryBuilder/utils");
const QueryBuilder_1 = require("../QueryBuilder");
const types_2 = require("./types");
const utils_2 = require("./utils");
const types_3 = require("../QueryBuilder/insert/types");
const child_process_1 = require("child_process");
const generate_query_from_prepared_statement_1 = require("../QueryBuilder/generate_query_from_prepared_statement");
/**
* @description With a DataAccessObject instance is possible to execute commands, dump databases and load dumps (or any .sql file)
* @param {PoolOptions} connectionData
*/
class DataAccessObject {
/**
* @description With this instance is possible to execute commands (SELECT, INSERT, UPDATE, DELETE and any query in general), dump databases and load dumps (or any .sql file)
* @param connectionData Information to create a connection. DataAccessObject uses a mysql2 pool behind the scenes, see: https://www.npmjs.com/package/mysql2#using-connection-pools.
* @param options Extra options like `usePreparedStatements`
* @example const dao = new DataAccessObject({
* host: 'localhost',
* user: 'root',
* port: 3306,
* password: '',
* });
*/
constructor(connectionData, options) {
this.options = Object.assign(Object.assign({}, types_2.defaultDataAccessObjectOptions), options);
this.connectionData = connectionData;
this.pool = mysql2_1.default.createPool(Object.assign(Object.assign({}, connectionData), { multipleStatements: false }));
this.multipleStatementsPool = mysql2_1.default.createPool(Object.assign(Object.assign({}, connectionData), { multipleStatements: true }));
this.executionMethod =
this.options.usePreparedStatements === true ? this.execute : this.query;
// this.getPoolConnection(() => {
// //Connection is OK
// }).catch((err) => {
// console.error(
// "Could not start connection, check your connection credencials."
// );
// throw err;
// });
}
/**
* @description Creates a dump sql file.
* @param database Database to dump
* @param filePath File to output the dump (Won't create the folders if they don't exists).
* @example dumpDatabase('mydatabase', './folder/mydatabase.sql');
*/
dumpDatabase(database, filePath) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const { host, user, password, port } = this.connectionData;
(0, child_process_1.exec)(`mysqldump -h ${host}${port ? ` -P ${port}` : ""} -u ${user}${password ? ` -p${password}` : ""} ${database} > "${filePath}"`, (err) => {
if (err)
return reject(err);
resolve();
});
});
});
}
/**
* @description Closes all connections. Node.js event loop will not be blocked by DAO anymore.
*/
dispose() {
return new Promise((res, rej) => {
this.multipleStatementsPool.end((err) => {
if (err)
return rej(err);
this.pool.end((err) => {
if (err)
return rej(err);
res();
});
});
});
}
/**
* @description Will drop and recreate a database.
* @param database Database to empty
* @example emptyDatabase('mydatabase');
*/
emptyDatabase(database) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.query(`DROP DATABASE IF EXISTS ${(0, utils_1.putBackticks)(database)};`);
yield this.query(`CREATE DATABASE ${(0, utils_1.putBackticks)(database)};`);
}
catch (error) {
throw error;
}
});
}
/**
* @description Creates a new database from dump file (WARNING: if the database alredy exists it will be emptied).
* @param database Database to be created or emptied
* @param dumpFilePath Path to the dump file
* @example loadDump('mydatabase', './folder/mydatabase.sql');
*/
loadDump(database, dumpFilePath) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.emptyDatabase(database);
yield this.loadSqlFile(database, dumpFilePath);
}
catch (error) {
throw error;
}
});
}
/**
* @description Runs statements inside sql file on a specific database.
* @param database Database selected during statements execution.
* @param sqlFilePath Path to the sql file
* @example loadSqlFile('mydatabase', './folder/mysqlstatements.sql');
*/
loadSqlFile(database, sqlFilePath) {
return __awaiter(this, void 0, void 0, function* () {
try {
const sql = fs_1.default.readFileSync(sqlFilePath, "utf8");
const sqlStatements = (0, dbgate_query_splitter_1.splitQuery)(sql, dbgate_query_splitter_1.mysqlSplitterOptions);
const maxAllowedPacket = parseInt(yield this.getServerVariable("max_allowed_packet"));
if (maxAllowedPacket && typeof maxAllowedPacket === "number") {
const statementGroups = (0, utils_2.statementsMerge)(sqlStatements, maxAllowedPacket / 2);
yield this.getPoolConnection((conn) => __awaiter(this, void 0, void 0, function* () {
for (const statementGroup of statementGroups) {
yield this.connQuery(conn, statementGroup);
}
}), { database, multipleStatements: true });
}
}
catch (error) {
throw error;
}
});
}
/**
* @description Executes select command as a prepared statement and return its results.
* @param selectOpts Select object structure.
* @param opts Extra options about the command like `database`, `returnMode`, ...
* @example select({
* columns: ['id', 'foo', 'bar'],
* from: 'table',
* where: {id: 1},
* }, {
* database: 'foo',
* });
*/
select(selectOpts, opts, conn) {
return __awaiter(this, void 0, void 0, function* () {
try {
const { returnMode, specificColumn, specificRow, groupData, database, executionMode, } = Object.assign(Object.assign({}, types_2.defaultDataSelectOptions), opts);
const prepStatement = (0, QueryBuilder_1.select)(Object.assign(Object.assign({}, selectOpts), { returnPreparedStatement: true }));
let resultSet;
if (executionMode === "query") {
resultSet = (yield this.query(prepStatement, database, conn));
}
else if (executionMode === "prepared-statement") {
resultSet = (yield this.execute(prepStatement, database, conn));
}
else {
resultSet = (yield this.executionMethod(prepStatement, database, conn));
}
if (!Array.isArray(resultSet))
return resultSet;
if ((0, types_2.isGroupDataOptions)(groupData)) {
resultSet = (0, utils_2.group)(resultSet, groupData.by, groupData.columnGroups);
}
if (!(0, types_2.isDataPacket)(resultSet))
return null;
switch (returnMode) {
case "normal":
return (0, types_2.isDataPacket)(resultSet) ? resultSet : null;
case "firstRow":
return (0, types_2.isRowDataPacket)(resultSet === null || resultSet === void 0 ? void 0 : resultSet[0]) ? resultSet[0] : null;
case "firstColumn":
const columnValues = resultSet.map((row) => { var _a; return typeof row === "object" ? (_a = Object.values(row)) === null || _a === void 0 ? void 0 : _a[0] : undefined; });
return (0, types_2.isColumnValues)(columnValues) ? columnValues : null;
case "firstValue":
const firstRow = resultSet[0];
if (!(0, types_2.isRowDataPacket)(firstRow))
return null;
const firstRowValues = Object.values(firstRow);
return firstRowValues.length !== 0 && (0, types_1.isSqlValues)(firstRowValues[0])
? firstRowValues[0]
: null;
case "specific":
if (specificRow === undefined && specificColumn === undefined)
return resultSet;
if (specificRow !== undefined &&
typeof specificRow === "number" &&
resultSet.length > specificRow &&
(0, types_2.isRowDataPacket)(resultSet[specificRow])) {
return resultSet[specificRow];
}
if (specificColumn !== undefined &&
typeof specificColumn === "string" &&
resultSet.length !== 0 &&
resultSet.every((row) => (0, types_2.isRowDataPacket)(row) &&
row[specificColumn] !== undefined &&
(0, types_1.isSqlValues)(row[specificColumn]))) {
const columnValues = resultSet.map((row) => row[specificColumn]);
if ((0, types_2.isColumnValues)(columnValues))
return columnValues;
}
}
return null;
}
catch (error) {
throw error;
}
});
}
/**
* @description Executes delete command as a prepared statement and return number of rows deleted.
* @param table Table to delete from.
* @param whereOpts Optional where object to filter delete.
* @param opts Extra delete options like `ignore`, `quick`
* @returns Number of deleted rows
* @example delete('table', {id: 5}, {ignore: true});
*/
delete(table, whereOpts, opts, conn) {
return __awaiter(this, void 0, void 0, function* () {
try {
const { database } = Object.assign({}, opts);
opts === null || opts === void 0 ? true : delete opts.database;
const preparedStatement = (0, QueryBuilder_1.deleteFrom)(table, whereOpts, Object.assign(Object.assign({}, opts), { returnPreparedStatement: true }));
const result = (yield this.executionMethod(preparedStatement, database, conn));
return result.affectedRows;
}
catch (error) {
throw error;
}
});
}
/**
* @description Executes update command as a prepared statement and return the number of affected rows.
* @param table Target table.
* @param values Values to be updated (associative column: value).
* @param whereOpts Optional where object to filter update.
* @param opts Extra update options like `ignore`, `order`, `limit`
* @returns Number of affected rows;
* @example update('table', {name: 'foo', finished: 1}, {id: 3}, {ignore: true});
*/
update(table, values, whereOpts, opts, conn) {
return __awaiter(this, void 0, void 0, function* () {
try {
const { database } = Object.assign({}, opts);
opts === null || opts === void 0 ? true : delete opts.database;
const preparedStatement = (0, QueryBuilder_1.update)(table, values, whereOpts, Object.assign(Object.assign({}, opts), { returnPreparedStatement: true }));
const result = (yield this.executionMethod(preparedStatement, database, conn));
return result.affectedRows;
}
catch (error) {
throw error;
}
});
}
/**
* @description Will execute insert command as a prepared statement, by default will insert one row at a time, if you need to insert a large number of rows specify the option `rowsPerStatement` to insert more than one row per statement increassing performance.
* @param table Target table to insert.
* @param rows One or multiple rows to insert.
* @param opts Extra insert options like `rowsPerStatement`, `ignore`, `columns`.
* @returns Inserted ids (if);
* @example insert('table', [{foo: 'bar1'}, {foo: 'bar2'}], {ignore: true});
*/
insert(table, rows, opts, conn) {
return __awaiter(this, void 0, void 0, function* () {
try {
opts = Object.assign(Object.assign({}, opts), { returnPreparedStatement: true });
const { rowsPerStatement, database } = Object.assign({}, opts);
const isArrayOfRows = Array.isArray(rows);
opts === null || opts === void 0 ? true : delete opts.rowsPerStatement;
opts === null || opts === void 0 ? true : delete opts.database;
if ((0, types_3.isInsertRows)(rows)) {
if (!Array.isArray(rows))
rows = [rows];
if (rowsPerStatement !== undefined &&
rowsPerStatement !== null &&
typeof rowsPerStatement === "number" &&
rowsPerStatement > 0) {
const rowsGroups = (0, utils_2.arrayUnflat)(rows, rowsPerStatement);
for (const rowsGroup of rowsGroups) {
const preparedStatement = (0, QueryBuilder_1.insert)(table, rowsGroup, opts);
yield this.executionMethod(preparedStatement, database, conn);
}
return null;
}
const insertedIds = [];
for (const row of rows) {
const preparedStatement = (0, QueryBuilder_1.insert)(table, row, opts);
const result = (yield this.executionMethod(preparedStatement, database, conn));
insertedIds.push(result.insertId);
}
return insertedIds.length === 0
? null
: insertedIds.length === 1 && !isArrayOfRows
? insertedIds[0]
: insertedIds;
}
return null;
}
catch (error) {
throw error;
}
});
}
/**
* @description Executes an update if the primary key column is defined in the row, executes an insert otherwise. (you can define the primary key column passing the name in the option `primaryKey`)
* @param table Table to either update or insert
* @param rows Row to either update or insert
* @param opts Extra options like `primaryKey`
* @returns One or many affected ids
*/
upsert(table, rows, opts, conn) {
return __awaiter(this, void 0, void 0, function* () {
try {
opts = Object.assign(Object.assign({}, types_2.defaultUpsertOptions), opts);
const { primaryKey, database } = opts;
if (!(0, types_3.isInsertRows)(rows) || typeof primaryKey !== "string")
return null;
if (!Array.isArray(rows))
rows = [rows];
const affectedIds = [];
for (const row of rows) {
if (row[primaryKey] !== undefined) {
const where = {};
where[primaryKey] = row[primaryKey];
yield this.update(table, row, where, { database }, conn);
const primaryKeyValue = row[primaryKey];
if (primaryKeyValue !== null &&
primaryKeyValue !== undefined &&
(typeof primaryKeyValue === "number" ||
(typeof primaryKeyValue === "string" && !isNaN(+primaryKeyValue)))) {
if (typeof primaryKeyValue === "string") {
affectedIds.push(parseInt(primaryKeyValue));
continue;
}
affectedIds.push(primaryKeyValue);
}
continue;
}
const insertedId = yield this.insert(table, row, { database }, conn);
if (typeof insertedId === "number")
affectedIds.push(insertedId);
}
return affectedIds.length === 0
? null
: affectedIds.length === 1
? affectedIds[0]
: affectedIds;
}
catch (error) {
throw error;
}
});
}
/**
* @description Runs a query and return it's results, this command don't prepare and execute the statement.
* @param sql Query to execute.
* @param database Database selected during the query execution. If null will use default connection database passed on connectionData from DAO object.
* @returns Query response.
* @example query('SELECT * FROM `table`, 'mydatabase');
*/
query(sql, database, conn) {
return __awaiter(this, void 0, void 0, function* () {
try {
const connectionCallback = (conn) => __awaiter(this, void 0, void 0, function* () {
return yield this.connQuery(conn, sql);
});
if (conn !== undefined) {
return connectionCallback(conn);
}
return yield this.getPoolConnection(connectionCallback, {
database,
});
}
catch (error) {
throw error;
}
});
}
databaseExists(database) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.query(`USE ${database}`);
return true;
}
catch (err) {
return false;
}
});
}
/**
* @description Creates a transaction, able to execute Insert, Update, Delete or even Upsert. When finished, the transaction can be commited, or in case of failure, it can be rollbacked.
* @param database Database selected during the transaction.
* @returns Transaction Object
* @example const transaction = await dao.startTransaction();
try {
await transaction.insert("arquivo", {
module: "1",
moduleId: 123,
keyS3: "/row
",
name: "avc",
});
await transaction.insert("arquivo", {
module: "2",
moduleId: 123,
keyS3: "/row",
name: "avc",
});
await transaction.update(
"arquivo",
{ keyS3: "UPDATED_S3" },
{ module: 2 }
);
await transaction.delete("arquivo", { module: 1 });
await transaction.delete("unknow_table" , {
module: 2,
});
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
*/
startTransaction(database) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((res, rej) => {
try {
return this.getPoolConnection((conn) => {
return new Promise((resolveTransaction, rejectTransaction) => {
conn.beginTransaction((err) => {
if (err) {
rejectTransaction(err);
return rej(err);
}
return res({
select: (selectOpts, opts) => this.select(selectOpts, opts, conn),
delete: (table, whereOpts, opts) => this.delete(table, whereOpts, opts, conn),
insert: (table, rows, opts) => this.insert(table, rows, opts, conn),
update: (table, values, whereOpts, opts) => this.update(table, values, whereOpts, opts, conn),
upsert: (table, rows, opts) => this.upsert(table, rows, opts, conn),
query: (sql) => this.query(sql, database, conn),
commit: () => new Promise((res, rej) => {
conn.commit((err) => {
if (err) {
return conn.rollback(() => {
rej(err);
rejectTransaction(err);
});
}
resolveTransaction();
return res();
});
}),
rollback: () => __awaiter(this, void 0, void 0, function* () {
return new Promise((res, rej) => {
try {
return conn.rollback(function () {
resolveTransaction();
return res();
});
}
catch (error) {
rejectTransaction(error);
return rej(error);
}
});
}),
});
});
});
}, { database });
}
catch (error) {
rej(error);
}
});
});
}
execute(preparedStatement, database, conn) {
return __awaiter(this, void 0, void 0, function* () {
try {
const connectionCallback = (conn) => __awaiter(this, void 0, void 0, function* () {
try {
return yield this.connExecute(conn, preparedStatement);
}
catch (error) {
throw error;
}
});
if (conn !== undefined) {
return yield connectionCallback(conn);
}
return yield this.getPoolConnection(connectionCallback, {
database,
});
}
catch (error) {
throw error;
}
});
}
getPoolConnection(callback, opts) {
const { multipleStatements, database } = Object.assign(Object.assign({}, types_2.defaultGetPoolConnectionOptions), opts);
return new Promise((resolve, reject) => {
try {
(multipleStatements === true
? this.multipleStatementsPool
: this.pool).getConnection((err, conn) => __awaiter(this, void 0, void 0, function* () {
if (err) {
conn === null || conn === void 0 ? void 0 : conn.destroy();
reject(err);
return;
}
try {
yield this.connUseDatabase(conn, database);
resolve(yield callback(conn));
yield this.connUseDefaultDatabase(conn);
conn.release();
}
catch (error) {
try {
yield this.connUseDefaultDatabase(conn);
conn.release();
}
catch (error) {
conn.destroy();
}
return reject(error);
}
}));
}
catch (error) {
reject(error);
}
});
}
connUseDatabase(conn, database) {
return __awaiter(this, void 0, void 0, function* () {
const shouldChangeDatabase = (0, utils_1.isNotEmptyString)(database) && database !== this.connectionData.database;
// Change to the desired database
if (shouldChangeDatabase) {
yield this.connChangeUser(conn, {
database: database,
});
}
else if (this.connectionData.database !== conn.config.database &&
(0, utils_1.isNotEmptyString)(this.connectionData.database)) {
yield this.connChangeUser(conn, {
database: this.connectionData.database,
});
}
});
}
connUseDefaultDatabase(conn) {
return __awaiter(this, void 0, void 0, function* () {
try {
// Change back to the original connection database
if ((0, utils_1.isNotEmptyString)(this.connectionData.database)) {
yield this.connChangeUser(conn, {
database: this.connectionData.database,
});
}
}
catch (error) {
throw error;
}
});
}
connExecute(conn, preparedStatement) {
return new Promise((resolve, reject) => {
var _a;
conn.execute(preparedStatement.statement, (_a = preparedStatement.values) === null || _a === void 0 ? void 0 : _a.map((e) => (e === undefined ? null : e)), (err, result) => {
if (err)
return reject(err);
resolve(result);
});
});
}
connQuery(conn, sql) {
return new Promise((resolve, reject) => {
conn.query((0, types_1.isPreparedStatement)(sql)
? (0, generate_query_from_prepared_statement_1.generateQueryFromPreparedStatement)(sql)
: sql, (err, result) => {
if (err)
return reject(err);
resolve(result);
});
});
}
getServerVariable(variableName) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
try {
const result = (yield this.query((0, QueryBuilder_1.escStr) `SHOW VARIABLES LIKE ${variableName};`));
return (_a = result[0]) === null || _a === void 0 ? void 0 : _a.Value;
}
catch (error) {
throw error;
}
});
}
connChangeUser(conn, opts) {
return new Promise((resolve, reject) => {
conn.changeUser(opts, (err) => {
if (err)
return reject(err);
resolve();
});
});
}
}
exports.DataAccessObject = DataAccessObject;