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};