diff --git a/README.md b/README.md index 2295b41..1013541 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ test(function testQueryInsert() { assertEquals( sql.trim(), - 'INSERT INTO `users` (`name`,`password`,`id`) VALUES ("Enok","foo",1) ("Man","bar",2)' + 'INSERT INTO `users` (`name`,`password`,`id`) VALUES ("Enok","foo",1), ("Man","bar",2)' ); }); diff --git a/join.ts b/join.ts index 384cb61..e3ef720 100644 --- a/join.ts +++ b/join.ts @@ -1,26 +1,26 @@ -import { replaceParams } from "./util.ts"; +import { replaceParams } from './util.ts'; export class Join { - value: string = ""; + value = ''; constructor(type: string, readonly table: string, readonly alias?: string) { - const name = alias ? "?? ??" : "??"; + const name = alias ? '?? ??' : '??'; this.value = replaceParams(`${type} ${name}`, [table, alias]); } static inner(table: string, alias?: string): Join { - return new Join("INNER JOIN", table, alias); + return new Join('INNER JOIN', table, alias); } static full(table: string, alias?: string): Join { - return new Join("FULL OUTER JOIN", table, alias); + return new Join('FULL OUTER JOIN', table, alias); } static left(table: string, alias?: string): Join { - return new Join("LEFT OUTER JOIN", table, alias); + return new Join('LEFT OUTER JOIN', table, alias); } static right(table: string, alias?: string): Join { - return new Join("RIGHT OUTER JOIN", table, alias); + return new Join('RIGHT OUTER JOIN', table, alias); } on(a: string, b: string) { diff --git a/query.ts b/query.ts index c80b12c..47ebc9d 100644 --- a/query.ts +++ b/query.ts @@ -1,10 +1,10 @@ -import { assert, replaceParams } from "./deps.ts"; -import { Order } from "./order.ts"; -import { Where } from "./where.ts"; -import { Join } from "./join.ts"; +import { assert, replaceParams } from './deps.ts'; +import { Order } from './order.ts'; +import { Where } from './where.ts'; +import { Join } from './join.ts'; export class Query { - private _type?: "select" | "insert" | "update" | "delete"; + private _type?: 'select' | 'insert' | 'update' | 'delete'; private _table?: string; private _where: string[] = []; private _joins: string[] = []; @@ -18,33 +18,33 @@ export class Query { private get orderSQL() { if (this._orders && this._orders.length) { - return `ORDER BY ` + this._orders.map((order) => order.value).join(", "); + return `ORDER BY ` + this._orders.map((order) => order.value).join(', '); } } private get whereSQL() { if (this._where && this._where.length) { - return `WHERE ` + this._where.join(" AND "); + return `WHERE ` + this._where.join(' AND '); } } private get havingSQL() { if (this._having && this._having.length) { - return `HAVING ` + this._having.join(" AND "); + return `HAVING ` + this._having.join(' AND '); } } private get joinSQL() { if (this._joins && this._joins.length) { - return this._joins.join(" "); + return this._joins.join(' '); } } private get groupSQL() { if (this._groupBy && this._groupBy.length) { return ( - "GROUP BY " + - this._groupBy.map((f) => replaceParams("??", [f])).join(", ") + 'GROUP BY ' + + this._groupBy.map((f) => replaceParams('??', [f])).join(', ') ); } } @@ -56,10 +56,10 @@ export class Query { private get selectSQL() { return [ - "SELECT", - this._fields.join(", "), - "FROM", - replaceParams("??", [this._table]), + 'SELECT', + this._fields.join(', '), + 'FROM', + replaceParams('??', [this._table]), this.joinSQL, this.whereSQL, this.groupSQL, @@ -68,7 +68,7 @@ export class Query { this.limitSQL, ] .filter((str) => str) - .join(" "); + .join(' '); } private get insertSQL() { @@ -77,11 +77,10 @@ export class Query { const values = this._insertValues.map((row) => { return fields.map((key) => row[key]!); }); - return replaceParams(`INSERT INTO ?? ?? VALUES ${"? ".repeat(len)}`, [ - this._table, - fields, - ...values, - ]); + return replaceParams( + `INSERT INTO ?? ?? VALUES ${values.map((v) => `?`).join(', ')}`, + [this._table, fields, ...values] + ); } private get updateSQL() { @@ -90,16 +89,16 @@ export class Query { .map((key) => { return replaceParams(`?? = ?`, [key, this._updateValue[key]]); }) - .join(", "); + .join(', '); return [ replaceParams(`UPDATE ?? SET ${set}`, [this._table]), this.whereSQL, - ].join(" "); + ].join(' '); } private get deleteSQL() { return [replaceParams(`DELETE FROM ??`, [this._table]), this.whereSQL].join( - " ", + ' ' ); } @@ -119,7 +118,7 @@ export class Query { } where(where: Where | string) { - if (typeof where === "string") { + if (typeof where === 'string') { this._where.push(where); } else { this._where.push(where.value); @@ -128,7 +127,7 @@ export class Query { } having(where: Where | string) { - if (typeof where === "string") { + if (typeof where === 'string') { this._having.push(where); } else { this._having.push(where.value); @@ -142,7 +141,7 @@ export class Query { } join(join: Join | string) { - if (typeof join === "string") { + if (typeof join === 'string') { this._joins.push(join); } else { this._joins.push(join.value); @@ -151,24 +150,24 @@ export class Query { } select(...fields: string[]) { - this._type = "select"; + this._type = 'select'; assert(fields.length > 0); this._fields = this._fields.concat( fields.map((field) => { - if (field.toLocaleLowerCase().indexOf(" as ") > -1) { + if (field.toLocaleLowerCase().indexOf(' as ') > -1) { return field; - } else if (field.split(".").length > 1) { - return replaceParams("??.??", field.split(".")); + } else if (field.split('.').length > 1) { + return replaceParams('??.??', field.split('.')); } else { - return replaceParams("??", [field]); + return replaceParams('??', [field]); } - }), + }) ); return this; } insert(data: Object[] | Object) { - this._type = "insert"; + this._type = 'insert'; if (!(data instanceof Array)) { data = [data]; } @@ -177,14 +176,14 @@ export class Query { } update(data: Object) { - this._type = "update"; + this._type = 'update'; this._updateValue = data; return this; } delete(table?: string) { if (table) this._table = table; - this._type = "delete"; + this._type = 'delete'; return this; } @@ -207,16 +206,16 @@ export class Query { build(): string { assert(!!this._table); switch (this._type) { - case "select": + case 'select': return this.selectSQL; - case "insert": + case 'insert': return this.insertSQL; - case "update": + case 'update': return this.updateSQL; - case "delete": + case 'delete': return this.deleteSQL; default: - return ""; + return ''; } } } diff --git a/test/query.ts b/test/query.ts index aa03f19..1174480 100644 --- a/test/query.ts +++ b/test/query.ts @@ -23,7 +23,7 @@ test("testQueryInsert", function () { assertEquals( sql.trim(), - 'INSERT INTO `users` (`name`,`password`,`id`) VALUES ("Enok","foo",1) ("Man","bar",2)', + 'INSERT INTO `users` (`name`,`password`,`id`) VALUES ("Enok","foo",1), ("Man","bar",2)', ); }); diff --git a/test/util.ts b/test/util.ts index f0f8213..6b96011 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,59 +1,59 @@ -import { assertEquals, assertThrows, replaceParams } from "../deps.ts"; +import { assertEquals, assertThrows, replaceParams } from '../deps.ts'; const { test } = Deno; -test("testReplaceDate", function () { - const date = new Date("2019-01-01 12:12:12"); - assertEquals(replaceParams("?", [date]), `"2019-01-01 12:12:12.000"`); +test('testReplaceDate', function () { + const date = new Date('2019-01-01 12:12:12'); + assertEquals(replaceParams('?', [date]), `"2019-01-01 12:12:12.000"`); - const dateWithMS = new Date("2020-07-04T12:51:53.728"); - assertEquals(replaceParams("?", [dateWithMS]), `"2020-07-04 12:51:53.728"`); + const dateWithMS = new Date('2020-07-04T12:51:53.728'); + assertEquals(replaceParams('?', [dateWithMS]), `"2020-07-04 12:51:53.728"`); }); -test("testIdReplace", async function () { +test('testIdReplace', function () { assertEquals( - replaceParams(`'??' "??" ?? ?`, ["a", "b"]), - `'??' "??" \`a\` "b"`, + replaceParams(`'??' "??" ?? ?`, ['a', 'b']), + `'??' "??" \`a\` "b"` ); - assertEquals(replaceParams("?? ?", null), "?? ?"); - assertEquals(replaceParams("?? ?", []), "?? ?"); - assertEquals(replaceParams("?? ?", [null, null]), "`` NULL"); + assertEquals(replaceParams('?? ?', null), '?? ?'); + assertEquals(replaceParams('?? ?', []), '?? ?'); + assertEquals(replaceParams('?? ?', [null, null]), '`` NULL'); - assertEquals(replaceParams("??", ["user.id"]), "`user`.`id`"); - assertEquals(replaceParams("??", ["user.*"]), "`user`.*"); + assertEquals(replaceParams('??', ['user.id']), '`user`.`id`'); + assertEquals(replaceParams('??', ['user.*']), '`user`.*'); assertEquals( - replaceParams("??", ["user.id as user_id"]), - "`user`.`id` AS `user_id`", + replaceParams('??', ['user.id as user_id']), + '`user`.`id` AS `user_id`' ); - assertEquals(replaceParams("?? ?", ["id", "val"]), '`id` "val"'); - assertEquals(replaceParams("??", ["id"]), "`id`"); - assertEquals(replaceParams("??", [1]), "`1`"); - assertEquals(replaceParams("??", [true]), "`true`"); - assertEquals(replaceParams("?", ["string"]), `"string"`); - assertEquals(replaceParams("?", ["str\\ing"]), `"str\\\\ing"`); - assertEquals(replaceParams("?", [123]), `123`); - assertEquals(replaceParams("?", [`"test"`]), '"\\"test\\""'); - assertEquals(replaceParams("?", [`\\"test"`]), '"\\\\\\"test\\""'); - assertEquals(replaceParams("?", [["a", "b", "c", "d"]]), '("a","b","c","d")'); - assertEquals(replaceParams("?", [[1, 2, 3, 4]]), "(1,2,3,4)"); - assertEquals(replaceParams("??", [["a", "b", "c"]]), "(`a`,`b`,`c`)"); - - let keys: string[] = ["a", "b", "c"]; - assertEquals(replaceParams("??", [keys]), "(`a`,`b`,`c`)"); + assertEquals(replaceParams('?? ?', ['id', 'val']), '`id` "val"'); + assertEquals(replaceParams('??', ['id']), '`id`'); + assertEquals(replaceParams('??', [1]), '`1`'); + assertEquals(replaceParams('??', [true]), '`true`'); + assertEquals(replaceParams('?', ['string']), `"string"`); + assertEquals(replaceParams('?', ['str\\ing']), `"str\\\\ing"`); + assertEquals(replaceParams('?', [123]), `123`); + assertEquals(replaceParams('?', [`"test"`]), '"\\"test\\""'); + assertEquals(replaceParams('?', [`\\"test"`]), '"\\\\\\"test\\""'); + assertEquals(replaceParams('?', [['a', 'b', 'c', 'd']]), '("a","b","c","d")'); + assertEquals(replaceParams('?', [[1, 2, 3, 4]]), '(1,2,3,4)'); + assertEquals(replaceParams('??', [['a', 'b', 'c']]), '(`a`,`b`,`c`)'); + + const keys: string[] = ['a', 'b', 'c']; + assertEquals(replaceParams('??', [keys]), '(`a`,`b`,`c`)'); assertEquals( - replaceParams("??", [Object.keys({ a: 1, b: 1, c: 1 })]), - "(`a`,`b`,`c`)", + replaceParams('??', [Object.keys({ a: 1, b: 1, c: 1 })]), + '(`a`,`b`,`c`)' ); const query = replaceParams( `select ??, ?? from ?? where ?? = ? and ?? = ? and is_admin = ?`, - ["name", "email", "users", "id", 1, "name", "manyuanrong", true], + ['name', 'email', 'users', 'id', 1, 'name', 'manyuanrong', true] ); assertEquals( query, - 'select `name`, `email` from `users` where `id` = 1 and `name` = "manyuanrong" and is_admin = true', + 'select `name`, `email` from `users` where `id` = 1 and `name` = "manyuanrong" and is_admin = true' ); - assertThrows(() => replaceParams("?", [{}]), Error); + assertThrows(() => replaceParams('?', [{}]), Error); }); diff --git a/util.ts b/util.ts index 117eeb4..e9f3b1b 100644 --- a/util.ts +++ b/util.ts @@ -1,89 +1,97 @@ -type SupportTypeElement = string | Date | number | boolean | null | undefined +type SupportTypeElement = + | string + | Date + | number + | boolean + | null + | undefined + // deno-lint-ignore ban-types + | {}; // Array -type SupportType = SupportTypeElement | SupportTypeElement[] +type SupportType = SupportTypeElement | SupportTypeElement[]; -export function replaceParams(sql: string, params?: null | SupportType[]): string { +export function replaceParams( + sql: string, + params?: null | SupportType[] +): string { if (!params) return sql; let paramIndex = 0; - sql = sql.replace(/('[^'\\]*(?:\\.[^'\\]*)*')|("[^"\\]*(?:\\.[^"\\]*)*")|(\?\?)|(\?)/g, (str) => { - if (paramIndex >= params.length) return str; - // ignore - if (/".*"/g.test(str) || /'.*'/g.test(str)) { - return str; - } - // identifier - if (str === "??") { - const val = params[paramIndex++]; - if (val instanceof Array) { - return `(${val.map((item) => replaceParams("??", [item])).join(",")})`; - } else if (val === "*") { - return val; - } else if (typeof val === "string" && val.includes(".")) { - // a.b => `a`.`b` - const _arr = val.split("."); - return replaceParams(_arr.map(() => "??").join("."), _arr); - } else if ( - typeof val === "string" && - (val.includes(" as ") || val.includes(" AS ")) - ) { - // a as b => `a` AS `b` - const newVal = val.replace(" as ", " AS "); - const _arr = newVal.split(" AS "); - return replaceParams(_arr.map(() => "??").join(" AS "), _arr); - } else { - return ["`", val, "`"].join(""); + sql = sql.replace( + /('[^'\\]*(?:\\.[^'\\]*)*')|("[^"\\]*(?:\\.[^"\\]*)*")|(\?\?)|(\?)/g, + (str) => { + if (paramIndex >= params.length) return str; + // ignore + if (/".*"/g.test(str) || /'.*'/g.test(str)) { + return str; } - } - // value - const val = params[paramIndex++]; - if (val === null) return "NULL"; - switch (typeof val) { - case "object": - if (val instanceof Date) return `"${formatDate(val)}"`; + // identifier + if (str === '??') { + const val = params[paramIndex++]; if (val instanceof Array) { - return `(${val.map((item) => replaceParams("?", [item])).join(",")})`; + return `(${val + .map((item) => replaceParams('??', [item])) + .join(',')})`; + } else if (val === '*') { + return val; + } else if (typeof val === 'string' && val.includes('.')) { + // a.b => `a`.`b` + const _arr = val.split('.'); + return replaceParams(_arr.map(() => '??').join('.'), _arr); + } else if ( + typeof val === 'string' && + (val.includes(' as ') || val.includes(' AS ')) + ) { + // a as b => `a` AS `b` + const newVal = val.replace(' as ', ' AS '); + const _arr = newVal.split(' AS '); + return replaceParams(_arr.map(() => '??').join(' AS '), _arr); + } else { + return ['`', val, '`'].join(''); } - throw new Error(`Unsupported argument type in your sql query: ${val.constructor.name}`); - case "string": - return `"${escapeString(val)}"`; - case "undefined": - return "NULL"; - case "number": - case "boolean": - default: - return `${val}`; + } + // value + const val = params[paramIndex++]; + if (val === null) return 'NULL'; + switch (typeof val) { + case 'object': + if (val instanceof Date) return `"${formatDate(val)}"`; + if (val instanceof Array) { + return `(${val + .map((item) => replaceParams('?', [item])) + .join(',')})`; + } + throw new Error( + `Unsupported argument type in your sql query: ${ + // deno-lint-ignore ban-types + (val as object).constructor.name + }` + ); + case 'string': + return `"${escapeString(val)}"`; + case 'undefined': + return 'NULL'; + case 'number': + case 'boolean': + default: + return `${val}`; + } } - }); + ); return sql; } function formatDate(date: Date) { const year = date.getFullYear(); - const month = (date.getMonth() + 1).toString().padStart(2, "0"); - const days = date - .getDate() - .toString() - .padStart(2, "0"); - const hours = date - .getHours() - .toString() - .padStart(2, "0"); - const minutes = date - .getMinutes() - .toString() - .padStart(2, "0"); - const seconds = date - .getSeconds() - .toString() - .padStart(2, "0"); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const days = date.getDate().toString().padStart(2, '0'); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + const seconds = date.getSeconds().toString().padStart(2, '0'); // Date does not support microseconds precision, so we only keep the milliseconds part. - const milliseconds = date - .getMilliseconds() - .toString() - .padStart(3, "0"); + const milliseconds = date.getMilliseconds().toString().padStart(3, '0'); return `${year}-${month}-${days} ${hours}:${minutes}:${seconds}.${milliseconds}`; } function escapeString(str: string) { - return str.replaceAll("\\", "\\\\").replaceAll('"', '\\"'); + return str.replaceAll('\\', '\\\\').replaceAll('"', '\\"'); }