310 lines
10 KiB
JavaScript
310 lines
10 KiB
JavaScript
const mysql = require("mysql");
|
|
const Select = require("./lib/Select");
|
|
const Insert = require("./lib/Insert");
|
|
const Delete = require("./lib/Delete");
|
|
const Update = require("./lib/Update");
|
|
const { CreateTable, Structure, AlterTable } = require("./lib/Tables");
|
|
|
|
class awSQL {
|
|
#instances = {};
|
|
#default;
|
|
|
|
/**
|
|
*
|
|
* @param {*} hostname
|
|
* @param {*} username
|
|
* @param {*} password
|
|
* @param {*} options
|
|
* @returns {Instance}
|
|
*/
|
|
createInstance = (hostname="localhost", username, password, options = {
|
|
charset:"utf8mb4",
|
|
defaultDatabase: false,
|
|
multipleStatements: false,
|
|
insecureAuth: false,
|
|
customIdentifier: false,
|
|
isDefault: false,
|
|
})=>{
|
|
const identifier = options.customIdentifier||`${username}@${hostname}`;
|
|
if (this.#instances[identifier]) throw new Error(`Can't create new instance with identifier "${identifier}": An instance with the same name already exists`);
|
|
const instance = new Instance(hostname, username, password, options.charset, options.defaultDatabase, options.multipleStatements, options.insecureAuth);
|
|
this.#instances[identifier] = instance;
|
|
if (options.createAsDefault) this.#default = identifier;
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {String} identifier
|
|
* @returns {Instance}
|
|
*/
|
|
getInstance = (identifier) => {
|
|
if (Object.keys(this.#instances).length===0) return undefined;
|
|
if (!identifier) return this.#default?this.#instances[this.#default]:this.#instances[Object.keys(this.#instances)[0]]; // If no identifier is set return default or first instance
|
|
return this.#instances[identifier];
|
|
}
|
|
|
|
listInstances = () => {
|
|
return Object.keys(this.#instances);
|
|
}
|
|
|
|
deleteInstance = (identifier) => {
|
|
if (!identifier) throw new Error("Can't delete Instance: No identifier set");
|
|
if (!this.#instances[identifier]) throw new Error(`Can't delete Instance '${identifier}': No Instance`);
|
|
this.#instances[identifier].destroy();
|
|
if (this.#default === identifier) this.#default = undefined;
|
|
delete this.#instances[identifier];
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class Instance {
|
|
#user;
|
|
#password;
|
|
#host;
|
|
#insecureAuth;
|
|
#multipleStatements;
|
|
#charset;
|
|
#connection;
|
|
|
|
#selectedDatabase;
|
|
|
|
constructor(hostname="localhost", username, password, charset="utf8mb4", defaultDatabase=false, multipleStatements=false, insecureAuth=false){
|
|
this.#host = hostname;
|
|
this.#user = username;
|
|
this.#password = password;
|
|
this.#charset = charset;
|
|
this.#multipleStatements = multipleStatements;
|
|
this.#insecureAuth = insecureAuth;
|
|
this.#selectedDatabase = defaultDatabase||username;
|
|
}
|
|
|
|
/**
|
|
* Connects the instance
|
|
*
|
|
*/
|
|
connect = () => {
|
|
return new Promise((resolve, reject) => {
|
|
const connection = mysql.createConnection({
|
|
user: this.#user,
|
|
password: this.#password,
|
|
host: this.#host,
|
|
insecureAuth: this.#insecureAuth,
|
|
multipleStatements: this.#multipleStatements,
|
|
charset: this.#charset
|
|
});
|
|
this.#connection = connection;
|
|
connection.connect((err) =>{
|
|
if (err) throw err;
|
|
resolve(`Connected to ${this.#host} with user ${this.#user}`);
|
|
});
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Destroys the instance
|
|
*/
|
|
destroy = () => {
|
|
if (this.#connection) this.#connection.end();
|
|
this.#connection = undefined;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Performs a raw query
|
|
* @param {String} queryString The sql query string to perform.
|
|
* @param {Array} values - An array holding all replacable ?-values from left to right.
|
|
* @returns {Any} - The individual result of your query
|
|
*/
|
|
queryRaw = (queryString, values) => {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.#connection) throw new Error("Querying failed: No connection");
|
|
this.#connection.query(queryString, values, (err, result) => {
|
|
if (err) throw err;
|
|
resolve(result);
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Returns a list of database names the user has access to
|
|
* @param {Boolean} excludeSchema - Whether to exclude the default database 'information_schema'
|
|
* @returns {Array}
|
|
*/
|
|
getDatabases = async (excludeSchema=false) => {
|
|
let dbs = await this.queryRaw("SHOW DATABASES;");
|
|
if (excludeSchema) dbs = dbs.filter((db)=>db.Database!=="information_schema")
|
|
return dbs.map(db => db.Database);
|
|
}
|
|
|
|
/**
|
|
* Selects a default database for future queries
|
|
* @param {String} name - Name of the database
|
|
* @returns {this}
|
|
*/
|
|
selectDatabase = (name) => {
|
|
this.#selectedDatabase = name;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of tables for the selected database
|
|
* - 'multipleStatements' must be active for this to work
|
|
* @param {String} [database] - Database to select. Can be empty as long as a default database was set with 'selectDatabase'
|
|
* @returns {Array}
|
|
*/
|
|
getTables = async (database) => {
|
|
if (!this.#multipleStatements) throw new Error("getTables: multipleStatements must be set to 'true' in instance options");
|
|
if (!this.#selectedDatabase && !database) throw new Error("getTables: No database selected");
|
|
const tables = (await this.queryRaw(`USE ${database||this.#selectedDatabase}; SHOW TABLES;`))[1];
|
|
return tables.map(table => table[`Tables_in_${database||this.#selectedDatabase}`]);
|
|
}
|
|
|
|
/**
|
|
* Prepares a new select query
|
|
* @param {String} from - Name of the table
|
|
* @param {...String} [columns] - Name of columns to select. Leave empty to select all
|
|
* @returns {Select}
|
|
*/
|
|
select = (from, ...columns) => {
|
|
return new Select(this, this.#selectedDatabase, from, columns);
|
|
}
|
|
|
|
|
|
/**
|
|
* Prepares a new query to insert data
|
|
* @param {String} into - Name of the table to insert into
|
|
* @returns {Insert}
|
|
*/
|
|
insert = (into) => {
|
|
return new Insert(this, this.#selectedDatabase, into);
|
|
}
|
|
|
|
/**
|
|
* Prepares a new delete query
|
|
* @param {String} from - Name of the table to delete from
|
|
* @returns {Delete}
|
|
*/
|
|
delete = (from) => {
|
|
return new Delete(this, this.#selectedDatabase, from);
|
|
}
|
|
|
|
/**
|
|
* Prepares a new update query
|
|
* @param {String} table - Name of the table to update data in
|
|
* @returns {Update}
|
|
*/
|
|
update = (table) => {
|
|
return new Update(this, this.#selectedDatabase, table);
|
|
}
|
|
|
|
/**
|
|
* Drops a whole database
|
|
* - Requires admin privileges
|
|
* @param {String} database - Name of the database to drop
|
|
* @returns
|
|
*/
|
|
dropDatabase = async (database) => {
|
|
return await this.queryRaw(`DROP DATABASE ${database};`);
|
|
}
|
|
|
|
/**
|
|
* Drops a whole table
|
|
* @param {String} table - Name of the table to drop
|
|
*/
|
|
dropTable = async (table) => {
|
|
if (!this.#selectedDatabase) throw new Error(`Can't drop table '${table}': Database not set`);
|
|
return await this.queryRaw(`DROP TABLE ${this.#selectedDatabase}.${table}`);
|
|
}
|
|
|
|
/**
|
|
* Creates a new database
|
|
* - Requires admin privileges
|
|
* @param {String} name - Name of the database to create
|
|
* @returns
|
|
*/
|
|
createDatabase = async (name) => {
|
|
return await this.queryRaw(`CREATE DATABASE ${name};`);
|
|
}
|
|
|
|
/**
|
|
* Prepares to create a new table
|
|
* @param {String} name - Name of the table to create
|
|
* @returns {CreateTable}
|
|
*/
|
|
createTable = (name) => {
|
|
return new CreateTable(this, this.#selectedDatabase, name);
|
|
}
|
|
|
|
/**
|
|
* Alters a table and updates to the new given structure.
|
|
*
|
|
* @param {*} name
|
|
*/
|
|
alterTable = (name) => {
|
|
return new AlterTable(this, this.#selectedDatabase, name);
|
|
}
|
|
|
|
/**
|
|
* Prepares to create a new table-structure
|
|
* @returns {Structure}
|
|
*/
|
|
createStructure = () => {
|
|
return new Structure();
|
|
}
|
|
|
|
/**
|
|
* Returns the structure object of a table
|
|
* @param {String} table - Name of table to get structure of
|
|
* @param {String} [database] - Name of the underlying database
|
|
* @returns {Structure}
|
|
*/
|
|
getStructure = async (table, database) => {
|
|
if (!this.#selectedDatabase && !database) throw new Error(`Can't get structure of table ${table}: Database not selected`);
|
|
return new Structure(await this.queryRaw(`DESCRIBE ${database||this.#selectedDatabase}.${table};`));
|
|
}
|
|
|
|
checkStructure = async (table, desiredStructure, database) => {
|
|
if (!this.#selectedDatabase && !database) throw new Error(`Can't get structure of table ${table}: Database not selected`);
|
|
const dbStruc = (await this.getStructure(table, database||this.#selectedDatabase)).get();
|
|
const result = {
|
|
errors: [],
|
|
passed: []
|
|
}
|
|
for (let col of dbStruc){
|
|
const desiredCol = desiredStructure.find((desCol) => desCol.Field === col.Field);
|
|
if (!desiredCol) {
|
|
result.errors.push(`${col.Field}: Missing completely`);
|
|
continue;
|
|
}
|
|
let breakOut = false;
|
|
for (let key in col){
|
|
if (col[key] !== desiredCol[key]){
|
|
result.errors.push(`${col.Field}.${key}: Required ${desiredCol[key]}, got ${col[key]}`);
|
|
breakOut=true;
|
|
}
|
|
}
|
|
if (!breakOut){
|
|
result.passed.push(`${col.Field}`);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns total amount of rows of a table
|
|
* @param {String} table - Table name
|
|
* @returns
|
|
*/
|
|
total = async (table) => {
|
|
return await new Select(this, this.#selectedDatabase, table).count(true).execute();
|
|
}
|
|
|
|
isConnected = () => {
|
|
if (this.#connection) return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
module.exports = {awSQL: new awSQL(), Structure}; |