durinn
Version:
A Node.js object-oriented programming framework
654 lines (533 loc) • 13.3 kB
text/typescript
;
/**
* Durinn Mysql Query builder and executor
* ----------------------------------------------------
* Example
* ----------------------------------------------------
* // - You can use it with callback or promise systems
*
* let query =
* (new Durinn.query('person'))
* .where('name','like','Samuel')
* .orderBy('name','desc')
* .groupBy('name')
* .select(function(result, response){
* console.log(result, response.rows);
* });
*
* query.then(([result, response, error]) => {
* console.log(result);
* console.log(response);
* console.log(error);
* });
* ----------------------------------------------------
*/
import Durinn from "../durinn";
import { Pool, FieldPacket, QueryError } from "mysql2";
interface Variables {
sql: string;
table: string;
joins: Joins;
conditions: {
limit: [] | [number] | [number, number];
wheres: Wheres;
orders: Orders;
groups: Groups;
};
}
interface Response {
rows: any[];
result: boolean;
fields: FieldPacket[] | null;
insertId: number | null;
changedRows: number;
affectedRows: number;
}
type Comparison = ">" | "<" | ">=" | "<=" | "==" | "!=" | "===" | "!==";
type Callback = (
result: boolean,
response: Response,
error?: QueryError | null
) => void;
type Join = { table: string; on: { from: string; to: string }[]; type: string };
type Joins = Join[];
type Wheres = {
field: string;
operator: string;
value: string;
escape: boolean;
}[];
type Order = "ASC" | "DESC" | "asc" | "desc";
type Orders = { field: string; order: Order }[];
type Groups = string[];
export default class Query {
pool: Pool;
response: Response;
variables: Variables;
connection = "release";
constructor(table?: string) {
this.pool = Durinn.pool;
this.variables = {
sql: "",
joins: [],
table: table || "",
conditions: {
limit: [],
wheres: [],
orders: [],
groups: []
}
};
this.response = {
rows: [],
fields: null,
result: false,
insertId: null,
changedRows: 0,
affectedRows: 0
};
}
public reset() {
this.resetResult();
this.resetVariables();
}
public table(name: string): this {
this.variables.table = name;
return this;
}
public async describe() {
const self = this;
const [result, response, error] = await new Query().exec(
"DESCRIBE " + self.variables.table
);
return response;
}
public get table_fields(): Promise<string[]> {
const self = this;
return new Promise(async resolve => {
const fields = [];
const describe = await self.describe();
for (let item of describe.rows) {
fields.push(item.Field);
}
resolve(fields);
});
}
public escape(value: any, escape: boolean = true) {
if (escape === false) return value;
if (value === null) return "null";
return this.pool.escape(value);
}
public escapeId(value: any, escape: boolean = true) {
if (escape === false) return value;
return this.pool.escapeId(value);
}
public where(
field: string | number,
operator: string,
value: string | number,
escape = true
) {
this.variables.conditions.wheres.push({
field: field.toString(),
operator: operator,
value: value.toString(),
escape: escape
});
return this;
}
public orderBy(field: string, order: Order = "ASC") {
this.variables.conditions.orders.push({ field: field, order: order });
return this;
}
public groupBy(field: string) {
this.variables.conditions.groups.push(field);
return this;
}
public join(
table: string,
on: { from: string; to: string }[] | string,
to?: string,
type = "INNER"
) {
let join: Join = { table: table, on: [], type: type };
if (typeof on === "object") {
join.on = on;
}
if (typeof on === "string" && typeof to === "string") {
join.on = [{ from: on, to: to }];
}
this.variables.joins.push(join);
return this;
}
public limit(records: number, offset?: number) {
if (typeof offset === "number") {
this.variables.conditions.limit = [records, offset];
} else {
this.variables.conditions.limit = [records];
}
return this;
}
public get rows() {
return this.response.rows;
}
public get fields() {
return this.response.fields;
}
public get result() {
return this.response.result;
}
public get insertId() {
return this.response.insertId;
}
public get changedRows() {
return this.response.changedRows;
}
public get affectedRows() {
return this.response.affectedRows;
}
public async exec(
sql?: string,
callback?: Callback
): Promise<[boolean, Response, QueryError | null]> {
const self = this;
self.variables.sql = sql || self.variables.sql;
return new Promise<[boolean, Response, (QueryError | null)]>(
(resolve, reject) => {
self.pool.getConnection(function(err, connection) {
if (err) {
reject(err);
return false;
}
connection.query(self.variables.sql, function(
error: any,
results: any,
fields: any
) {
if (self.connection === "destroy") {
connection.destroy();
} else {
connection.release();
}
self.resetResult();
if (error) {
console.error(self.variables.sql);
reject(error);
return false;
} else {
self.response = {
rows: results,
fields: fields || null,
result: true,
insertId: results.insertId || null,
changedRows: results.changedRows || 0,
affectedRows: results.affectedRows || 0
};
}
if (callback) {
callback(
self.response.result,
self.response,
error
);
}
return resolve([
self.response.result,
self.response,
error
]);
});
});
}
);
}
public async select(
callback?: Callback,
params: { fields?: string[]; ResultCheckBy?: Comparison } = {}
): Promise<[boolean, Response, QueryError | null]> {
const self = this;
let sql = `
SELECT ${(params.fields || ["*"]).join(",")} FROM ${self.variables.table}
${self.joins} ${self.wheres} ${self.groups} ${self.orders} ${
self.limits
}
`;
try {
let [result, response, error] = await self.exec(sql);
response.result = self.compare(
response.rows.length,
params.ResultCheckBy || ">",
0
);
if (typeof callback === "function") {
callback(response.result, response, error);
}
return [response.result, response, error];
} catch (e) {
throw e;
}
}
public async distinct(
callback?: Callback,
params: { fields?: string[]; ResultCheckBy?: Comparison } = {}
): Promise<[boolean, Response, QueryError | null]> {
const self = this;
let sql = `
SELECT DISTINCT ${(params.fields || ["*"]).join(",")} FROM ${
self.variables.table
}
${self.joins} ${self.wheres} ${self.groups} ${self.orders} ${
self.limits
}
`;
try {
let [result, response, error] = await self.exec(sql);
response.result = self.compare(
response.rows.length,
params.ResultCheckBy || ">",
0
);
if (typeof callback === "function") {
callback(response.result, response, error);
}
return [response.result, response, error];
} catch (e) {
throw e;
}
}
public async update(
update: { [s: string]: string | number | null },
callback?: Callback,
params: { ResultCheckBy?: Comparison; escapeValues?: boolean } = {},
safeMode: boolean = true
) {
const self = this;
const fields = await self.table_fields;
if (safeMode && self.wheres.length === 0) {
throw new Error(
"Durinn - SAFE MODE - There isn't a where condition in UPDATE query"
);
}
let set = [];
for (let i in update) {
if (fields.indexOf(i) > -1) {
set.push(
`${self.escapeId(i)} = ${self.escape(
update[i],
params.escapeValues
)}`
);
}
}
let sql = `
UPDATE ${self.variables.table} SET ${set.join(",")} ${self.wheres}
`;
try {
let [result, response, error] = await self.exec(sql);
response.result = self.compare(
response.changedRows,
params.ResultCheckBy || ">=",
0
);
if (typeof callback === "function") {
callback(response.result, response, error);
}
return [response.result, response, error];
} catch (e) {
throw e;
}
}
public async insert(
insert: { [s: string]: string | number | null },
callback?: Callback,
params: { escapeValues?: boolean } = {}
) {
const self = this;
const fields = await self.table_fields;
let keys: string[] = [];
let values: string[] = [];
for (let i in insert) {
if (fields.indexOf(i) > -1) {
keys.push(self.escapeId(i));
values.push(self.escape(insert[i], params.escapeValues));
}
}
let sql = `
INSERT INTO ${self.variables.table} (${keys.join(",")})
VALUES (${values.join(",")})
`;
try {
let [result, response, error] = await self.exec(sql);
response.result = response.insertId != 0;
if (typeof callback === "function") {
callback(response.result, response, error);
}
return [response.result, response, error];
} catch (e) {
throw e;
}
}
public async replace(
insert: { [s: string]: string | number | null },
callback?: Callback,
params: { escapeValues?: boolean } = {}
) {
const self = this;
const fields = await self.table_fields;
let keys: string[] = [];
let values: string[] = [];
for (let i in insert) {
if (fields.indexOf(i) > -1) {
keys.push(self.escapeId(i));
values.push(self.escape(insert[i], params.escapeValues));
}
}
let sql = `
REPLACE INTO ${self.variables.table} (${keys.join(",")})
VALUES (${values.join(",")})
`;
try {
let [result, response, error] = await self.exec(sql);
response.result = response.insertId != 0;
if (typeof callback === "function") {
callback(response.result, response, error);
}
return [response.result, response, error];
} catch (e) {
throw e;
}
}
public async delete(
callback?: Callback,
params: { ResultCheckBy?: Comparison } = {},
safeMode: boolean = true
) {
const self = this;
if (safeMode && self.wheres.length === 0) {
throw new Error(
"Durinn - SAFE MODE - There isn't a where condition in DELETE query"
);
}
let sql = `
DELETE FROM ${self.variables.table} ${self.wheres}
`;
try {
let [result, response, error] = await self.exec(sql);
response.result = self.compare(
response.affectedRows,
params.ResultCheckBy || ">",
0
);
if (typeof callback === "function") {
callback(response.result, response, error);
}
return [response.result, response, error];
} catch (e) {
throw e;
}
}
private resetResult() {
this.response = {
rows: [],
fields: null,
result: false,
insertId: null,
changedRows: 0,
affectedRows: 0
};
}
private resetVariables() {
this.variables = {
sql: "",
joins: [],
table: "",
conditions: {
limit: [],
wheres: [],
orders: [],
groups: []
}
};
}
private get joins(): string {
const self = this;
let result: string[] = [];
let joins: Joins = this.variables.joins;
if (joins.length === 0) return "";
for (let key in joins) {
let join = joins[key];
let relations = [];
for (let i in join.on) {
let relation = join.on[i];
relations.push(relation.from + " = " + relation.to);
}
result.push(
`${join.type} JOIN ${join.table} ON ${relations.join(" AND ")}`
);
}
return result.join("\n");
}
private get wheres(): string {
const self = this;
let result: string[] = [];
let wheres: Wheres = self.variables.conditions.wheres;
if (wheres.length === 0) return "";
for (let key in wheres) {
let where = wheres[key];
let value = where.escape ? self.escape(where.value) : where.value;
result.push(`${where.field} ${where.operator} ${value}`);
}
return ` WHERE ${result.join(" AND ")} `;
}
private get groups(): string {
const self = this;
let result: string[] = [];
let groups: Groups = self.variables.conditions.groups;
if (groups.length === 0) return "";
for (let key in groups) {
result.push(groups[key]);
}
return ` GROUP BY ${result.join(",")} `;
}
private get orders(): string {
const self = this;
let result: string[] = [];
let orders: Orders = self.variables.conditions.orders;
if (orders.length === 0) return "";
for (let key in orders) {
let order = orders[key];
result.push(`${order.field} ${order.order}`);
}
return ` ORDER BY ${result.join(",")} `;
}
private get limits(): string {
if (this.variables.conditions.limit.length == 0) return "";
return ` LIMIT ${this.variables.conditions.limit.join(",")} `;
}
private compare(
variable: number,
operator: string,
value: number
): boolean {
switch (operator) {
case ">":
return variable > value;
case "<":
return variable < value;
case ">=":
return variable >= value;
case "<=":
return variable <= value;
case "==":
return variable == value;
case "!=":
return variable != value;
case "===":
return variable === value;
case "!==":
return variable !== value;
default:
return false;
}
}
}