first release
This commit is contained in:
commit
aa0b62be09
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
**/node_modules
|
||||||
|
**/out
|
||||||
|
package-lock.json
|
||||||
310
index.js
Normal file
310
index.js
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
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};
|
||||||
64
lib/Delete.js
Normal file
64
lib/Delete.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
class Delete {
|
||||||
|
#instance;
|
||||||
|
#database;
|
||||||
|
#from;
|
||||||
|
|
||||||
|
#where;
|
||||||
|
#whereValues;
|
||||||
|
#force;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(instance, defaultDatabase, from){
|
||||||
|
this.#instance = instance;
|
||||||
|
this.#database = defaultDatabase;
|
||||||
|
this.#from = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a database for this query
|
||||||
|
* @param {String} database - Name of the database
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
selectDatabase = (database) => {
|
||||||
|
this.#database = database;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a where-clause to the query
|
||||||
|
* - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection
|
||||||
|
* @param {String} string - The where-clause as a string with ? representing each values.
|
||||||
|
* @param {Array} values - Array containing values replacing the ? in the string (from left to right)
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
where = (string, values=[]) => {
|
||||||
|
this.#where = string;
|
||||||
|
this.#whereValues = values;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables deletion of all rows
|
||||||
|
*/
|
||||||
|
force = () => {
|
||||||
|
this.#force = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute = async () => {
|
||||||
|
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
|
||||||
|
if (!this.#database) throw new Error(`Can't execute query: Database not selected`);
|
||||||
|
|
||||||
|
if (!this.#where && !this.#force) throw new Error("Delete: Tried to delete all rows. If this was intentional, use .force() on Delete");
|
||||||
|
|
||||||
|
const values = [];
|
||||||
|
const whereString = this.#where?` WHERE ${this.#where}`:"";
|
||||||
|
this.#where&&values.push(...this.#whereValues);
|
||||||
|
|
||||||
|
const queryString = `DELETE FROM ${this.#database}.${this.#from}${whereString}`;
|
||||||
|
|
||||||
|
return await this.#instance.queryRaw(queryString, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Delete;
|
||||||
55
lib/Insert.js
Normal file
55
lib/Insert.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
class Insert {
|
||||||
|
#instance;
|
||||||
|
#database;
|
||||||
|
#into;
|
||||||
|
|
||||||
|
#data;
|
||||||
|
|
||||||
|
constructor(instance, defaultDatabase, into){
|
||||||
|
this.#instance = instance;
|
||||||
|
this.#database = defaultDatabase;
|
||||||
|
this.#into = into;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a database for this query
|
||||||
|
* @param {String} database - Name of the database
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
selectDatabase = (database) => {
|
||||||
|
this.#database = database;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data (rows) to insert
|
||||||
|
* @param {Array} objects - Array containing objects to insert, where the key represents the column-name. All objects must have the same structure!
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
data = (objects) => {
|
||||||
|
this.#data = objects;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute = async () => {
|
||||||
|
if (!this.#data) throw new Error("Insert: tried to insert without data");
|
||||||
|
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
|
||||||
|
if (!this.#database) throw new Error(`Can't execute query: Database not selected`);
|
||||||
|
|
||||||
|
const columns = Object.keys(this.#data[0]);
|
||||||
|
const queryString = `INSERT INTO ${this.#database}.${this.#into} (${columns.toString()}) VALUES ?`;
|
||||||
|
|
||||||
|
const values = [];
|
||||||
|
for (let obj of this.#data){
|
||||||
|
let thisRow = [];
|
||||||
|
for (let key of columns){
|
||||||
|
thisRow.push(obj[key]);
|
||||||
|
}
|
||||||
|
values.push(thisRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.#instance.queryRaw(queryString, [values]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Insert;
|
||||||
245
lib/Select.js
Normal file
245
lib/Select.js
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
|
||||||
|
class Select {
|
||||||
|
#instance;
|
||||||
|
#database;
|
||||||
|
#from;
|
||||||
|
#columns = [];
|
||||||
|
|
||||||
|
#distinct = false;
|
||||||
|
|
||||||
|
#where;
|
||||||
|
#whereValues;
|
||||||
|
#order = {
|
||||||
|
asc: [],
|
||||||
|
desc: []
|
||||||
|
};
|
||||||
|
#group;
|
||||||
|
|
||||||
|
#aggregator;
|
||||||
|
#join;
|
||||||
|
#having;
|
||||||
|
#havingValues;
|
||||||
|
#limit;
|
||||||
|
#aggregatorParse;
|
||||||
|
constructor(instance, defaultDatabase, from, columns){
|
||||||
|
this.#database = defaultDatabase;
|
||||||
|
this.#from = from;
|
||||||
|
this.#columns = columns||[];
|
||||||
|
this.#instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a database for this query
|
||||||
|
* @param {String} database - Name of the database
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
selectDatabase = (database) => {
|
||||||
|
this.#database = database;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the 'distinct' keyword for this query
|
||||||
|
* Should be called on only selected columns.
|
||||||
|
* - With 'distinct' only unique values are returned
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
distinct = () => {
|
||||||
|
this.#distinct = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a where-clause to the query
|
||||||
|
* - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection
|
||||||
|
* - If you are using joins, specify the table and column together: table.column
|
||||||
|
* @param {String} string - The where-clause as a string with ? representing each values.
|
||||||
|
* @param {Array} values - Array containing values replacing the ? in the string (from left to right)
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
where = (string, values=[]) => {
|
||||||
|
this.#where = string;
|
||||||
|
this.#whereValues = values;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as a where-clause, but allows for aggregation
|
||||||
|
* - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection
|
||||||
|
* - If you are using joins, specify the table and column together: table.column
|
||||||
|
* @param {String} string - The having-clause with possible aggregation and ? representing each values
|
||||||
|
* @param {Array} values - Array containing values replacing the ? in the string (from left to right)
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
having = (string, values = []) => {
|
||||||
|
this.#having = string;
|
||||||
|
this.#havingValues = values;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new sort order
|
||||||
|
* - Can be used multiple times to order by multiple columns
|
||||||
|
* @param {String} column - Column to order by
|
||||||
|
* @param {Boolean} desc - Sorty descending
|
||||||
|
* @param {"MIN"|"MAX"|"COUNT"|"SUM"|"AVG"} aggregation - The aggregation type to use
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
order = (column, desc=false, aggregation) => {
|
||||||
|
if (["MIN", "MAX", "COUNT", "SUM", "AVG"].includes(aggregation)){
|
||||||
|
switch(aggregation){
|
||||||
|
case "MIN":
|
||||||
|
column = `MIN(${column})`;
|
||||||
|
break;
|
||||||
|
case "MAX":
|
||||||
|
column = `MAX(${column})`;
|
||||||
|
break;
|
||||||
|
case "COUNT":
|
||||||
|
column = `COUNT(${column})`;
|
||||||
|
break;
|
||||||
|
case "SUM":
|
||||||
|
column = `SUM(${column})`;
|
||||||
|
break;
|
||||||
|
case "AVG":
|
||||||
|
column = `AVG(${column})`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!desc){
|
||||||
|
this.#order.asc.push(column);
|
||||||
|
}else{
|
||||||
|
this.#order.desc.push(column);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts number of entries of the first selected column
|
||||||
|
* @param {Boolean} doParse - Return only an integer, not the full query result
|
||||||
|
*/
|
||||||
|
count = (doParse=false) => {
|
||||||
|
this.#aggregator = "COUNT";
|
||||||
|
this.#aggregatorParse = doParse;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sums numerical rows of the first selected column
|
||||||
|
* @param {Boolean} doParse - Return only an integer, not the full query result
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
sum = (doParse=false) => {
|
||||||
|
this.#aggregator ="SUM";
|
||||||
|
this.#aggregatorParse = doParse;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Averages numerical rows of the first selected column
|
||||||
|
* @param {Boolean} doParse - Return only an integer, not the full query result
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
avg = (doParse=false) => {
|
||||||
|
this.#aggregator = "AVG";
|
||||||
|
this.#aggregatorParse = doParse;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups rows that have the same values into summary rows
|
||||||
|
* @param {...String} columns - The columns to group by
|
||||||
|
*/
|
||||||
|
group = (...columns) => {
|
||||||
|
this.#group = columns;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {"LEFT"|"INNER"|"RIGHT"|"FULL OUTER"} type
|
||||||
|
* @param {*} table
|
||||||
|
* @param {*} onOriginalColumn
|
||||||
|
* @param {*} onJoinedColumn
|
||||||
|
* @param {...any} columns
|
||||||
|
*/
|
||||||
|
join = (type, table, onOriginalColumn, onJoinedColumn, ...columns) => {
|
||||||
|
this.#join = {
|
||||||
|
type,
|
||||||
|
on: `%%FROM%%.${onOriginalColumn}=${table}.${onJoinedColumn}`,
|
||||||
|
columns,
|
||||||
|
table
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limits the query and specifies an offset
|
||||||
|
* @param {Number} number - Limits the query by specified rows
|
||||||
|
* @param {*} offset - Offset to start at
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
limit = (number, offset) => {
|
||||||
|
this.#limit = {
|
||||||
|
number,
|
||||||
|
offset
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paginates the query
|
||||||
|
* @param {Number} page - The page to get (Minimum 1)
|
||||||
|
* @param {Number} itemsPerPage - How many items a page should have
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
pagination = (page, itemsPerPage) => {
|
||||||
|
if (page<1) page=1;
|
||||||
|
this.#limit = {
|
||||||
|
number: itemsPerPage,
|
||||||
|
offset: itemsPerPage*(page-1)
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the prepared query
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
execute = async () => {
|
||||||
|
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
|
||||||
|
if (!this.#database) throw new Error(`Can't execute query: Database not selected`);
|
||||||
|
|
||||||
|
const values = [];
|
||||||
|
let columnString;
|
||||||
|
if (this.#join && this.#columns.length>0){
|
||||||
|
columnString = `${this.#columns.toString()},${this.#join.columns.toString()}`
|
||||||
|
}else{
|
||||||
|
columnString = this.#columns.length>0?this.#columns.toString():"*"
|
||||||
|
}
|
||||||
|
const distinctString = this.#distinct?"DISTINCT ":"";
|
||||||
|
|
||||||
|
|
||||||
|
const whereString = this.#where?` WHERE ${this.#where}`:"";
|
||||||
|
this.#where&&values.push(...this.#whereValues);
|
||||||
|
const havingString = this.#having?` HAVING ${this.#having}`:"";
|
||||||
|
this.#having&&values.push(...this.#havingValues);
|
||||||
|
|
||||||
|
const orderString = (this.#order.asc.length>0||this.#order.desc.length>0)?` ORDER BY ${this.#order.asc.length>0?this.#order.asc.toString()+" ASC,":""}${this.#order.desc.length>0?this.#order.desc.toString()+" DESC":""}`:"";
|
||||||
|
|
||||||
|
const groupString = this.#group?` GROUP BY ${this.#group.toString()}`:"";
|
||||||
|
|
||||||
|
const joinString = this.#join?` ${this.#join.type} JOIN ${this.#database}.${this.#join.table} ON ${this.#join.on.replace("%%FROM%%", this.#from)}`:"";
|
||||||
|
|
||||||
|
const limitString = this.#limit?` LIMIT ${this.#limit.number}${this.#limit.offset?` OFFSET ${this.#limit.offset}`:""}`:"";
|
||||||
|
|
||||||
|
const queryString = `SELECT ${this.#aggregator?this.#aggregator+"(":""}${distinctString}${columnString}${this.#aggregator?")":""} FROM ${this.#database}.${this.#from}${joinString}${whereString}${havingString}${limitString}${groupString}${orderString};`;
|
||||||
|
|
||||||
|
if (this.#aggregatorParse){
|
||||||
|
const result = await this.#instance.queryRaw(queryString, values);
|
||||||
|
return result[0][Object.keys(result[0])];
|
||||||
|
}
|
||||||
|
return await this.#instance.queryRaw(queryString, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Select;
|
||||||
529
lib/Tables.js
Normal file
529
lib/Tables.js
Normal file
@ -0,0 +1,529 @@
|
|||||||
|
|
||||||
|
class AlterTable {
|
||||||
|
#instance;
|
||||||
|
#database;
|
||||||
|
#name;
|
||||||
|
#structure;
|
||||||
|
constructor(instance, defaultDatabase, name){
|
||||||
|
this.#instance = instance;
|
||||||
|
this.#database = defaultDatabase;
|
||||||
|
this.#name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a database for this query
|
||||||
|
* @param {String} database - Name of the database
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
selectDatabase = (database) => {
|
||||||
|
this.#database = database;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new desired structure.
|
||||||
|
* - Drops columns that are existing in the current table but not in this structure
|
||||||
|
* - Adds columns that are missing in the current table
|
||||||
|
* - Modifies all other columns where at least one datatype is not matching
|
||||||
|
* @param {Structure} struc - New structure for the table
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
structure = (struc) => {
|
||||||
|
this.#structure = struc;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute = async () => {
|
||||||
|
if (!this.#database) throw new Error(`Can't alter table ${this.#name}: Database not selected`);
|
||||||
|
if (!this.#name) throw new Error(`Can't alter table on ${this.#database}: No name given`);
|
||||||
|
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
|
||||||
|
if (!this.#structure) throw new Error(`Can't alter table ${this.#database}.${this.#name}: No new structure given`);
|
||||||
|
const currentStruc = (await this.#instance.getStructure(this.#name, this.#database)).get();
|
||||||
|
const newStruc = this.#structure.get();
|
||||||
|
if (currentStruc.length===0) throw new Error(`Can't alter table ${this.#name}: Table does not exist`);
|
||||||
|
for (let col of currentStruc){
|
||||||
|
if (!newStruc.find(newCol => newCol.Field === col.Field)){
|
||||||
|
// DELETE COLUMN
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} DROP COLUMN \`${col.Field}\``);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let col of newStruc){
|
||||||
|
const oldStrucCol = currentStruc.find((oldCol) => oldCol.Field === col.Field);
|
||||||
|
if (!oldStrucCol){
|
||||||
|
// ADD COLUMN
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} ADD COLUMN \`${col.Field}\` ${col.Type}${col.Extra?` ${col.Extra}`:""}${col.Null==="YES"?" NULL":" NOT NULL"}${col.Default?` DEFAULT '${col.Default}'`:""}`);
|
||||||
|
if (col.Key){
|
||||||
|
switch(col.Key){
|
||||||
|
case "PRI":
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} ADD PRIMARY KEY (${col.Field});`);
|
||||||
|
break;
|
||||||
|
case "MUL":
|
||||||
|
console.log("HEY");
|
||||||
|
await this.#instance.queryRaw(`CREATE INDEX ${col.Field} ON ${this.#database}.${this.#name} (${col.Field})`);
|
||||||
|
break;
|
||||||
|
case "UNI":
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} ADD UNIQUE (${col.Field});`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (let key in col){
|
||||||
|
if (oldStrucCol[key] !== col[key]){
|
||||||
|
// UPDATE COLUMN
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} MODIFY COLUMN \`${col.Field}\` ${col.Type}${col.Extra?` ${col.Extra}`:""}${col.Null==="YES"?" NULL":" NOT NULL"}${col.Default?` DEFAULT '${col.Default}'`:""}`);
|
||||||
|
if (oldStrucCol.Key){
|
||||||
|
switch(oldStrucCol.Key){
|
||||||
|
case "PRI":
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} DROP PRIMARY KEY (${col.Field});`);
|
||||||
|
break;
|
||||||
|
case "MUL":
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} DROP INDEX ${col.Field};`);
|
||||||
|
break;
|
||||||
|
case "UNI":
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} DROP UNIQUE (${col.Field});`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (col.Key){
|
||||||
|
switch(col.Key){
|
||||||
|
case "PRI":
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} ADD PRIMARY KEY (${col.Field});`);
|
||||||
|
break;
|
||||||
|
case "MUL":
|
||||||
|
await this.#instance.queryRaw(`CREATE INDEX ${col.Field} ON ${this.#database}.${this.#name} (${col.Field})`);
|
||||||
|
break;
|
||||||
|
case "UNI":
|
||||||
|
await this.#instance.queryRaw(`ALTER TABLE ${this.#database}.${this.#name} ADD UNIQUE (${col.Field});`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await this.#instance.checkStructure(this.#name, newStruc, this.#database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateTable {
|
||||||
|
#instance;
|
||||||
|
#database;
|
||||||
|
#name;
|
||||||
|
#structure;
|
||||||
|
constructor(instance, defaultDatabase, name) {
|
||||||
|
this.#instance = instance;
|
||||||
|
this.#database = defaultDatabase;
|
||||||
|
this.#name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a database for this query
|
||||||
|
* @param {String} database - Name of the database
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
selectDatabase = (database) => {
|
||||||
|
this.#database = database;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new name
|
||||||
|
* @param {String} name
|
||||||
|
*/
|
||||||
|
name = (name) => {
|
||||||
|
this.#name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the structure for the table
|
||||||
|
* @param {Structure} struc - Instance of Structure
|
||||||
|
*/
|
||||||
|
structure = (struc) => {
|
||||||
|
this.#structure = struc;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the prepared query
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
execute = async () => {
|
||||||
|
if (!this.#database) throw new Error(`Can't create table ${this.#name}: Database not selected`);
|
||||||
|
if (!this.#name) throw new Error(`Can't create table on ${this.#database}: No name given`);
|
||||||
|
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
|
||||||
|
if (!this.#structure) throw new Error(`Can't create table ${this.#database}.${this.#name}: No structure given`);
|
||||||
|
const colObjs = this.#structure.get();
|
||||||
|
|
||||||
|
const coltypes = colObjs.map(obj => `\`${obj.Field}\` ${obj.Type}${obj.Null==="YES"?" NULL":" NOT NULL"}${obj.Default?` DEFAULT '${obj.Default}'`:""}${obj.Extra?` ${obj.Extra}`:""}`);
|
||||||
|
const constraints = colObjs.map(obj => `${obj.Key?obj.Key==="PRI"?` PRIMARY KEY (${obj.Field})`:obj.Key==="MUL"?` INDEX (${obj.Field})`:obj.Key==="UNI"?` UNIQUE (${obj.Key})`:"":""}`)
|
||||||
|
.filter(str => str !== ""); // Filter out empty
|
||||||
|
|
||||||
|
const queryString = `CREATE TABLE ${this.#database}.${this.#name} (${coltypes}${constraints.length>0?",":""}${constraints})`;
|
||||||
|
return await this.#instance.queryRaw(queryString);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ConstraintOptions
|
||||||
|
* @property {Boolean} [primary] - Whether this column should be primary
|
||||||
|
* @property {Boolean} [index] - Whether this column should be indexable (Faster query, slower insertion)
|
||||||
|
* @property {Boolean} [null] - Whether this column is null per default
|
||||||
|
* @property {Boolean} [unique] - Whether this column data should be unique
|
||||||
|
* @property {String} [default] - Set's the default data for this column
|
||||||
|
* @property {Boolean} [auto_increment] - Whether this column should be numerical auto_increment
|
||||||
|
* @property {Boolean} [unsigned] - Only on numerical: Whether this numerical field should be unsigned
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Structure {
|
||||||
|
#columns = [];
|
||||||
|
constructor(tableDescription) {
|
||||||
|
if (!tableDescription) return;
|
||||||
|
this.#columns = tableDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drops (Removes) a column name
|
||||||
|
* @param {String} name - The name of the column
|
||||||
|
*/
|
||||||
|
drop = (name) => {
|
||||||
|
let index;
|
||||||
|
for (let i = 0; i < this.#columns.length; i++){
|
||||||
|
if (this.#columns[i].Field === name) {
|
||||||
|
index=i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.#columns.splice(index, 1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(){
|
||||||
|
return this.#columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'char' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size - Length of characters. Min 0, Max 255. Default 1
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
*/
|
||||||
|
char = (name, size=1, options) => {
|
||||||
|
if (size < 0 || size > 255){
|
||||||
|
throw new Error(`Column datatype 'char' size must be a number between 0 and 255. Received: ${size}`);
|
||||||
|
}
|
||||||
|
this.#columns.push(parseColumnData(name, `char(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'varchar' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size - Length of characters. Min 0, Max 255. Default 1
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
*/
|
||||||
|
varchar = (name, size=8, options) => {
|
||||||
|
if (size < 0 || size > 255){
|
||||||
|
throw new Error(`Column datatype 'varchar' size must be a number between 0 and 65535. Received: ${size}`);
|
||||||
|
}
|
||||||
|
this.#columns.push(parseColumnData(name, `varchar(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'binary' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size - Length of data. Min 1
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
binary = (name, size=1, options) => {
|
||||||
|
if (size < 1){
|
||||||
|
throw new Error(`Column datatype 'binary' size must be a number above 0. Received: ${size}`);
|
||||||
|
}
|
||||||
|
this.#columns.push(parseColumnData(name, `binary(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'varbinary' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size - Length of data. Min 1
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
varbinary = (name, size=1, options) => {
|
||||||
|
if (size < 1){
|
||||||
|
throw new Error(`Column datatype 'varbinary' size must be a number above 0. Received: ${size}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#columns.push(parseColumnData(name, `varbinary(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'tinyblob' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
tinyblob = (name, options) => {
|
||||||
|
this.#columns.push(parseColumnData(name, `tinyblob`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'tinytext' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
tinytext = (name, options) => {
|
||||||
|
this.#columns.push(parseColumnData(name, `tinytext`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'text' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
text = (name, options) => {
|
||||||
|
this.#columns.push(parseColumnData(name, `text`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'blob' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size - Size in bytes. Min 1, Max 65535. Defaults to 65535
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
blob = (name, size=65535, options) => {
|
||||||
|
if (size < 1 || size > 65535) throw new Error(`Column datatype 'blob' size must be a number between 1 and 65535. Received: ${size}`);
|
||||||
|
this.#columns.push(parseColumnData(name, `blob(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'mediumtext' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
mediumtext = (name, options) => {
|
||||||
|
this.#columns.push(parseColumnData(name, `mediumtext`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'longtext' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
longtext = (name, options) => {
|
||||||
|
this.#columns.push(parseColumnData(name, `longtext`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'longblob' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
longblob = (name, options) => {
|
||||||
|
this.#columns.push(parseColumnData(name, `longblob`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'enum' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Array} vals - Array of possible values
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
enum = (name, vals=[], options) => {
|
||||||
|
if (!Array.isArray(vals)) throw new Error(`Column datatype 'enum': 'vals' must be of type Array`);
|
||||||
|
if (vals.length<=0) throw new Error(`Column datatype 'enum' must contain a list of possible values. Received undefined`);
|
||||||
|
this.#columns.push(parseColumnData(name, `enum(${vals.map(val=>`"${val}"`)})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'enum' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Array} vals - Array of possible values
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
set = (name, vals=[], options) => {
|
||||||
|
if (!Array.isArray(vals)) throw new Error(`Column datatype 'set': 'vals' must be of type Array`);
|
||||||
|
if (vals.length<=0) throw new Error(`Column datatype 'set' must contain a list of possible values. Received undefined`);
|
||||||
|
this.#columns.push(parseColumnData(name, `set(${vals.map(val=>`"${val}"`)})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'bit' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size - Min 1, Max 64. Default to 1
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
bit = (name, size=1, options) => {
|
||||||
|
if (size < 1 || size > 64) throw new Error(`Column datatype 'bit' size must be a number between 1 and 64. Received: ${size}`);
|
||||||
|
this.#columns.push(parseColumnData(name, `bit(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'tinyint' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size - Min 1, Max 255. Defaults to 255
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
tinyint = (name, size=255, options) => {
|
||||||
|
if (size < 1 || size > 255) throw new Error(`Column datatype 'tinyint' size must be a number between 1 and 255. Received: ${size}`);
|
||||||
|
|
||||||
|
this.#columns.push(parseColumnData(name, `tinyint(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'bool' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
bool = (name, options) => {
|
||||||
|
this.#columns.push(parseColumnData(name, `bool`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'smallint' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size Min 1, Max 255. Defaults to 255
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
smallint = (name, size=255, options) => {
|
||||||
|
if (size < 1 || size > 255) throw new Error(`Column datatype 'smallint' size must be a number between 1 and 255. Received: ${size}`);
|
||||||
|
this.#columns.push(parseColumnData(name, `smallint(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'mediumint' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size Min 1, Max 255. Defaults to 255
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
mediumint = (name, size=255, options) => {
|
||||||
|
if (size < 1 || size > 255) throw new Error(`Column datatype 'mediumint' size must be a number between 1 and 255. Received: ${size}`);
|
||||||
|
this.#columns.push(parseColumnData(name, `mediumint(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'int' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size Min 1, Max 255. Defaults to 255
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
int = (name, size=255, options) => {
|
||||||
|
if (size < 1 || size > 255) throw new Error(`Column datatype 'int' size must be a number between 1 and 255. Received: ${size}`);
|
||||||
|
this.#columns.push(parseColumnData(name, `int(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'bigint' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size Min 1, Max 255. Defaults to 255
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
bigint = (name, size=255, options) => {
|
||||||
|
if (size < 1 || size > 255) throw new Error(`Column datatype 'bigint' size must be a number between 1 and 255. Received: ${size}`);
|
||||||
|
this.#columns.push(parseColumnData(name, `bigint(${size})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'float' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} p - Precision. Min 1, Max 53. Defaults to 25
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
float = (name, p=25, options) => {
|
||||||
|
if (p < 1 || p > 53) throw new Error(`Column datatype 'float' size must be a number between 1 and 53. Received: ${p}`);
|
||||||
|
|
||||||
|
this.#columns.push(parseColumnData(name, `float(${p})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'double' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size - Min 1. Defaults to 16
|
||||||
|
* @param {Number} d - Double precision. Min 1. Defaults to 8
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
double = (name, size=16, d=8, options) => {
|
||||||
|
if (size < 1) throw new Error(`Column datatype 'double' size must be greater than 0. Received: ${p}`);
|
||||||
|
if (d < 1) throw new Error(`Column datatype 'double' d must be greater than 0. Received: ${p}`);
|
||||||
|
this.#columns.push(parseColumnData(name, `double(${size},${d})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new column of data type 'decimal' to this structure
|
||||||
|
* @param {String} name - Name of the column
|
||||||
|
* @param {Number} size - Min 1, Max 65. Defaults to 10
|
||||||
|
* @param {Number} d - Double precision. Min 0. Defaults to 0.
|
||||||
|
* @param {ConstraintOptions} options - Extra constraint options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
decimal = (name, size=10, d=0, options) => {
|
||||||
|
if (size < 1 || size > 65) throw new Error(`Column datatype 'decimal' size must be a number between 1 and 65. Received: ${size}`);
|
||||||
|
if (d < 0) throw new Error(`Column datatype 'decimal' d must be positive. Received: ${d}`);
|
||||||
|
|
||||||
|
this.#columns.push(parseColumnData(name, `decimal(${size},${d})`, options));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {String} name
|
||||||
|
* @param {String} type
|
||||||
|
* @param {ConstraintOptions} options
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function parseColumnData(name, type, options={}){
|
||||||
|
if (options.primary + options.unique + options.index > 1) throw new Error(`ConstrainError: Only one of 'primary', 'unique' or 'index' allowed`);
|
||||||
|
return {
|
||||||
|
Field: name,
|
||||||
|
Type: `${type}${options.unsigned?" UNSIGNED":""}`,
|
||||||
|
Null: options.null?"YES":"NO",
|
||||||
|
Key: options.primary?"PRI":options.index?"MUL":options.unique?"UNI":"",
|
||||||
|
Default: options.default?options.default:null,
|
||||||
|
Extra: options.auto_increment?"auto_increment":""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { CreateTable, Structure, AlterTable }
|
||||||
70
lib/Update.js
Normal file
70
lib/Update.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
class Update{
|
||||||
|
#instance;
|
||||||
|
#database;
|
||||||
|
#table;
|
||||||
|
|
||||||
|
#data;
|
||||||
|
#where;
|
||||||
|
#whereValues;
|
||||||
|
#force;
|
||||||
|
constructor(instance, defaultDatabase, table){
|
||||||
|
this.#instance = instance;
|
||||||
|
this.#database = defaultDatabase;
|
||||||
|
this.#table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates all matching rows with the given object
|
||||||
|
* @param {Object} object - The object with the data to update. Keys represent column names
|
||||||
|
*/
|
||||||
|
data = (object) => {
|
||||||
|
this.#data = object;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a database for this query
|
||||||
|
* @param {String} database - Name of the database
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
selectDatabase = (database) => {
|
||||||
|
this.#database = database;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables update of all rows
|
||||||
|
*/
|
||||||
|
force = () => {
|
||||||
|
this.#force = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a where-clause to the query
|
||||||
|
* - Values should be set as ? in the string and given in left-to-right order via the 'values'-array to minimize the risk of sql-injection
|
||||||
|
* @param {String} string - The where-clause as a string with ? representing each values.
|
||||||
|
* @param {Array} values - Array containing values replacing the ? in the string (from left to right)
|
||||||
|
* @returns {this}
|
||||||
|
*/
|
||||||
|
where = (string, values=[]) => {
|
||||||
|
this.#where = string;
|
||||||
|
this.#whereValues = values;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the prepared query
|
||||||
|
*/
|
||||||
|
execute = async () => {
|
||||||
|
if (!this.#instance.isConnected()) throw new Error(`Can't execute query: Instance has no connection`);
|
||||||
|
if (!this.#database) throw new Error(`Can't execute query: Database not selected`);
|
||||||
|
if (!this.#where && !this.#force) throw new Error("Update: Tried to update all rows. If this was intentional, use .force() on Update");
|
||||||
|
const values = [];
|
||||||
|
const queryString = `UPDATE ${this.#database}.${this.#table} SET ${Object.keys(this.#data).map(col=>{values.push(this.#data[col]);return `${col}=?`})}${this.#where&&` WHERE ${this.#where}`}`;
|
||||||
|
this.#where&&values.push(...this.#whereValues);
|
||||||
|
console.log(queryString);
|
||||||
|
return await this.#instance.queryRaw(queryString, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = Update;
|
||||||
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "awsql_refined",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"mysql": "^2.18.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib"
|
||||||
|
},
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user