first release

This commit is contained in:
Sam 2024-11-22 04:36:31 +01:00
commit aa0b62be09
8 changed files with 1294 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
**/node_modules
**/out
package-lock.json

310
index.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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": ""
}