From 3af23ab12077fdde8b3544a0fec7f3acbe862db5 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 14:29:19 +0100 Subject: [PATCH 01/24] feat:databaseDriverAdaptations - addedd Postgres|Sqlite --- PostgresClient.ts | 89 +++++++++ README.md | 36 +++- SqliteClient.ts | 112 +++++++++++ mod.ts | 7 + postgres_deps.ts | 24 +++ sqlite_deps.ts | 16 ++ src/Reflect.ts | 8 +- src/drivers/base.ts | 3 + src/drivers/mysql.ts | 7 + src/drivers/postgres.ts | 7 + src/drivers/sqlite.ts | 7 + src/dso.ts | 110 +++++++++-- src/model.ts | 173 +++++++++++++++-- src/sync.ts | 77 ++++++-- src/transaction.ts | 35 +++- test.db | Bin 0 -> 16384 bytes test.ts | 52 ++++- test/model.ts | 406 +++++++++++++++++++++++++++++++++++++++- util.ts | 23 +++ 19 files changed, 1123 insertions(+), 69 deletions(-) create mode 100644 PostgresClient.ts create mode 100644 SqliteClient.ts create mode 100644 postgres_deps.ts create mode 100644 sqlite_deps.ts create mode 100644 src/drivers/base.ts create mode 100644 src/drivers/mysql.ts create mode 100644 src/drivers/postgres.ts create mode 100644 src/drivers/sqlite.ts create mode 100644 test.db diff --git a/PostgresClient.ts b/PostgresClient.ts new file mode 100644 index 0000000..294f2b6 --- /dev/null +++ b/PostgresClient.ts @@ -0,0 +1,89 @@ +import { + Client, + PostgresError, + log, + QueryResult, + QueryConfig, +} from "./postgres_deps.ts"; + +/** Transaction processor */ +export interface TransactionProcessor { + (connection: PostgresClient): Promise; +} + +export class PostgresClient { + protected client: Client; + + constructor(config: object) { + this.client = new Client(config); + } + + async connect(): Promise { + return this.client.connect(); + } + + // TODO: can we use more specific type for args? + async query(text: string): Promise { + return this.client.query(text); + } + + // TODO: can we use more specific type for args? + async execute(text: string): Promise { + return this.client.query(text); + } + + async multiQuery(queries: QueryConfig[]): Promise { + const result: QueryResult[] = []; + + for (const query of queries) { + result.push(await this.client.query(query)); + } + + return result; + } + + async end(): Promise { + await this.client.end(); + } + + /** + * Use a connection for transaction processor + * + * @param fn transation processor + */ + async useConnection(fn: (conn: PostgresClient) => Promise) { + if (!this.client) { + throw new Error("Unconnected"); + } + try { + const result = await fn(this); + return result; + } catch (error) { + throw new PostgresError( + { severity: "high", code: "TA", message: "transactions" }, + ); + } + } + + /** + * Execute a transaction process, and the transaction successfully + * returns the return value of the transaction process + * @param processor transation processor + */ + async transaction(processor: TransactionProcessor): Promise { + return await this.useConnection(async (connection) => { + try { + await connection.query("BEGIN"); + const result = await processor(connection); + await connection.query("COMMIT"); + return result; + } catch (error) { + log.info(`ROLLBACK: ${error.message}`); + await connection.query("ROLLBACK"); + throw new PostgresError( + { severity: "high", code: "TA", message: "transactions" }, + ); + } + }); + } +} diff --git a/README.md b/README.md index 52be142..e096a2a 100644 --- a/README.md +++ b/README.md @@ -51,16 +51,21 @@ class UserModel extends BaseModel { } const userModel = dso.define(UserModel); +/* + +export interface Config extends Base { + type: type: string; // MySQl|Postgres|Sqlite + clientConfig?: ClientConfig | object; MySQL client Config or an object + client?: Client | PostgresClient | SqliteClient; +} + +*/ async function main() { // The database must be created before linking - await dso.connect({ - hostname: "127.0.0.1", - port: 3306, - username: "root", - password: "", - db: "dbname" - }); + await dso.connect(mysqlConfig); + await dso.connect(postgresConfig); + await dso.connect(sqliteConfig); /* When installing or initializing a database, @@ -80,6 +85,14 @@ async function main() { password: "password" }); + // You can add records using insertRowsAffected method (works for only MySQL, Postgres and Sqlite) + // Returns the number of rows inserted inserted + const insertId = await userModel.insertRowsAffected({ + name: "user1", + password: "password", + phoneNumber: "08135539123" + }); + // You can use the Model.findById method to get a record const user = await userModel.findById(1); @@ -138,6 +151,7 @@ dso.showQueryLog = true; #### dso.connect You need to use this method to link to the database before you can manipulate the database +See the approprite configuration object specified earlier. Example below is for MySQL ```ts await dso.connect({ @@ -183,7 +197,8 @@ export default const userModel = dso.define(UserModel); // userModel.findById(...) // userModel.findAll(...) // userModel.findOne(...) -// userModel.insert(...) +// userModel.insert(...) // works for MySQL and Sqlite ONLY +// userModel.insertRowsAffected(...) // userModel.update(...) // userModel.delete(...) ``` @@ -203,7 +218,8 @@ await dso.sync(force); Create and start a transaction. -New `Model` instances must be obtained through `getModel(Model)`. Otherwise, it will not be controlled by transactions. +New `Model` instances must be obtained through `getModel(Model)`. Otherwise, it will not be controlled by transactions. Transaction takes a second `driverType:string` parameter which can be +"MYSQL"| "POSTGRES" | "SQLITE" (ignores capitalization). ```ts const result = await dso.transaction(async trans => { @@ -213,7 +229,7 @@ const result = await dso.transaction(async trans => { userId = await userModel.insert({ nickName: "foo", password: "bar", phoneNumber: "08135539123" }); topicId = await topicModel.insert({ title: "zoo", userId }); return true; -}); +}, "MYSQL"); ``` ### Top Level Types diff --git a/SqliteClient.ts b/SqliteClient.ts new file mode 100644 index 0000000..d516e78 --- /dev/null +++ b/SqliteClient.ts @@ -0,0 +1,112 @@ +import { DB } from "https://deno.land/x/sqlite/mod.ts"; +import { log } from "./sqlite_deps.ts"; +import SqliteError from "https://deno.land/x/sqlite/src/error.ts"; +import { Rows } from "./sqlite_deps.ts"; + +/** Transaction processor */ +export interface TransactionProcessor { + (connection: SqliteClient): Promise; +} + +export class SqliteClient { + protected db: DB; + constructor(path: string) { + this.db = new DB(path); + } + + query(sql: string): Rows { + return this.db.query(sql); + } + + execute(sql: string): Rows { + return this.db.query(sql); + } + + /** + * DB.close + * + * Close database handle. This must be called if + * DB is no longer used, to avoid leaking file + * resources. + * + * If force is specified, any on-going transactions + * will be closed. + */ + close(force: boolean = false) { + this.db.close(force); + } + + /** + * DB.lastInsertRowId + * + * Get last inserted row id. This corresponds to + * the SQLite function `sqlite3_last_insert_rowid`. + * + * By default, it will return 0 if there is no row + * inserted yet. + */ + get lastInsertRowId(): number { + return this.db.lastInsertRowId; + } + + /** + * DB.changes + * + * Return the number of rows modified, inserted or + * deleted by the most recently completed query. + * This corresponds to the SQLite function + * `sqlite3_changes`. + */ + get changes(): number { + return this.db.changes; + } + + /** + * DB.totalChanges + * + * Return the number of rows modified, inserted or + * deleted since the database was opened. + * This corresponds to the SQLite function + * `sqlite3_total_changes`. + */ + get totalChanges(): number { + return this.db.totalChanges; + } + + /** + * Use a connection for transaction processor + * + * @param fn transation processor + */ + async useConnection(fn: (conn: this) => Promise) { + if (!this.db) { + throw new Error("Unconnected"); + } + try { + const result = await fn(this); + return result; + } catch (error) { + throw new SqliteError("connection", 2); + } + } + + /** + * Execute a transaction process, and the transaction successfully + * returns the return value of the transaction process + * @param processor transation processor + */ + async transaction(processor: TransactionProcessor): Promise { + return await this.useConnection(async (connection) => { + try { + await connection.query("BEGIN"); + const result = await processor(connection); + await connection.query("COMMIT"); + return result; + } catch (error) { + log.info(`ROLLBACK: ${error.message}`); + await connection.query("ROLLBACK"); + throw new SqliteError("transaction", 1); + } + }); + } +} diff --git a/mod.ts b/mod.ts index de16307..35adb08 100644 --- a/mod.ts +++ b/mod.ts @@ -7,6 +7,13 @@ export { replaceParams, Where, } from "./deps.ts"; + +export { + PostgresClient, +} from "./PostgresClient.ts"; +export { + SqliteClient, +} from "./SqliteClient.ts"; export { dso } from "./src/dso.ts"; export * from "./src/field.ts"; export * from "./src/index.ts"; diff --git a/postgres_deps.ts b/postgres_deps.ts new file mode 100644 index 0000000..06b518e --- /dev/null +++ b/postgres_deps.ts @@ -0,0 +1,24 @@ +export { + Pool, + PostgresError, + Client, +} from "https://deno.land/x/postgres/mod.ts"; + +export { + Connection, +} from "https://deno.land/x/postgres/connection.ts"; + +export { + Query as QueryPostgres, + QueryConfig, + QueryResult, +} from "https://deno.land/x/postgres/query.ts"; + +export { + ConnectionOptions, + createParams, +} from "https://deno.land/x/postgres/connection_params.ts"; + +export { + log, +} from "https://deno.land/x/mysql/src/logger.ts"; diff --git a/sqlite_deps.ts b/sqlite_deps.ts new file mode 100644 index 0000000..dbfb750 --- /dev/null +++ b/sqlite_deps.ts @@ -0,0 +1,16 @@ +export { + getStr, + setStr, + setArr, +} from "https://deno.land/x/sqlite/src/wasm.ts"; + +export { Status, Values } from "https://deno.land/x/sqlite/src/constants.ts"; + +export { + Rows, + Empty, +} from "https://deno.land/x/sqlite/src/rows.ts"; + +export { + log, +} from "https://deno.land/x/mysql/src/logger.ts"; diff --git a/src/Reflect.ts b/src/Reflect.ts index eab0600..2f77c2b 100644 --- a/src/Reflect.ts +++ b/src/Reflect.ts @@ -780,13 +780,13 @@ namespace Reflect { process.env && process.env["REFLECT_METADATA_USE_MAP_POLYFILL"] === "true"; const _Map: typeof Map = !usePolyfill && - typeof Map === "function" && - typeof Map.prototype.entries === "function" + typeof Map === "function" && + typeof Map.prototype.entries === "function" ? Map : CreateMapPolyfill(); const _Set: typeof Set = !usePolyfill && - typeof Set === "function" && - typeof Set.prototype.entries === "function" + typeof Set === "function" && + typeof Set.prototype.entries === "function" ? Set : CreateSetPolyfill(); const _WeakMap: typeof WeakMap = diff --git a/src/drivers/base.ts b/src/drivers/base.ts new file mode 100644 index 0000000..486bf8b --- /dev/null +++ b/src/drivers/base.ts @@ -0,0 +1,3 @@ +export interface Base { + type: string; // MySQl|Postgres|Sqlite +} diff --git a/src/drivers/mysql.ts b/src/drivers/mysql.ts new file mode 100644 index 0000000..82e720e --- /dev/null +++ b/src/drivers/mysql.ts @@ -0,0 +1,7 @@ +import { Base } from "./base.ts"; +import { ClientConfig, Client } from "../../deps.ts"; + +export interface MysqlConfig extends Base { + clientConfig: ClientConfig; + client?: Client; +} diff --git a/src/drivers/postgres.ts b/src/drivers/postgres.ts new file mode 100644 index 0000000..c9706e9 --- /dev/null +++ b/src/drivers/postgres.ts @@ -0,0 +1,7 @@ +import { Base } from "./base.ts"; +import { PostgresClient } from "../../PostgresClient.ts"; + +export interface PostgresConfig extends Base { + clientConfig: object; + client?: PostgresClient; +} diff --git a/src/drivers/sqlite.ts b/src/drivers/sqlite.ts new file mode 100644 index 0000000..572103b --- /dev/null +++ b/src/drivers/sqlite.ts @@ -0,0 +1,7 @@ +import { Base } from "./base.ts"; +import { SqliteClient } from "../../SqliteClient.ts"; + +export interface SqliteConfig extends Base { + clientConfig: object; + client?: SqliteClient; +} diff --git a/src/dso.ts b/src/dso.ts index f2bbca1..ec98df9 100644 --- a/src/dso.ts +++ b/src/dso.ts @@ -1,20 +1,40 @@ -import { Client, ClientConfig } from "../deps.ts"; +import { Client } from "../deps.ts"; import { BaseModel } from "./model.ts"; import { sync } from "./sync.ts"; import { Transaction } from "./transaction.ts"; +import { PostgresConfig } from "./drivers/postgres.ts"; +import { MysqlConfig } from "./drivers/mysql.ts"; +import { SqliteConfig } from "./drivers/sqlite.ts"; +import { SqliteClient } from "../SqliteClient.ts"; +import { PostgresClient } from "../PostgresClient.ts"; /** @ignore */ let _client: Client; +/** @ignore */ +let _clientPostgres: PostgresClient; + +/** @ignore */ +let _clientSqlite: SqliteClient; + /** @ignore */ let _models: BaseModel[] = []; +/** @ignore */ +export type _ClientType = { + mysql?: Client; + postgres?: PostgresClient; + sqlite?: SqliteClient; +}; +/** @ignore */ +let _configClientReturn: _ClientType; + /** * Global dso instance */ export const dso = { /** - * set true will show exucte/query sql + * set true will show execute/query sql */ showQueryLog: false, @@ -23,18 +43,49 @@ export const dso = { * @param force set true, will drop table before create table */ async sync(force: boolean = false): Promise { - for (const model of _models) { - await sync(_client, model, force); + if (_configClientReturn["mysql"]) { + for (const model of _models) { + await sync(_client, model, force); + } + } else if (_configClientReturn["postgres"]) { + for (const model of _models) { + await sync(_clientPostgres, model, force); + } + } else if (_configClientReturn["sqlite"]) { + for (const model of _models) { + await sync(_clientSqlite, model, force); + } } }, /** - * Database client + * MySQL Database client */ get client(): Client { return _client; }, + /** + * Postgres Database client + */ + get clientPostgres(): PostgresClient { + return _clientPostgres; + }, + + /** + * Sqlite Database client + */ + get clientSqlite(): SqliteClient { + return _clientSqlite; + }, + + /** + * Current driver client + */ + get configClientReturn(): _ClientType { + return _configClientReturn; + }, + /** * all models */ @@ -52,23 +103,54 @@ export const dso = { return model; }, + /* + * Transaction object selected for each driver + */ transaction: Transaction.transaction, /** - * connect to database + * connect to database mysql | postgres | Sqlite * @param config client config */ - async connect(config: ClientConfig | Client) { - if (config instanceof Client) { - _client = config; - } else { - _client = new Client(); - await _client.connect(config); + async connect( + config: T, + ): Promise<_ClientType | undefined> { + if (config["type"].toUpperCase() === "POSTGRES") { + _clientPostgres = new PostgresClient(config["clientConfig"]); + await _clientPostgres.connect(); + + return _configClientReturn = { + postgres: _clientPostgres, + }; + } else if (config["type"].toUpperCase() === "MYSQL") { + if (config["client"] && config["client"] instanceof Client) { + _client = config["client"]; + } else { + _client = new Client(); + await _client.connect(config["clientConfig"]); + } + return _configClientReturn = { + mysql: _client, + }; + } else if (config["type"].toUpperCase() === "SQLITE") { + const configgy: any = config["clientConfig"]; + + _clientSqlite = new SqliteClient(configgy["database"]); + + return _configClientReturn = { + sqlite: _clientSqlite, + }; } - return _client; + return undefined; }, close(): void { - _client.close(); + if (_configClientReturn["mysql"]) { + _client.close(); + } else if (_configClientReturn["postgres"]) { + _clientPostgres.end(); + } else if (_configClientReturn["sqlite"]) { + _clientSqlite.close(); + } }, }; diff --git a/src/model.ts b/src/model.ts index c721ead..0917d51 100644 --- a/src/model.ts +++ b/src/model.ts @@ -1,7 +1,11 @@ -import { assert, Connection, Join, Order, Query, Where } from "../deps.ts"; +import { assert, Join, Order, Query, Where } from "../deps.ts"; import { dso } from "./dso.ts"; import { Defaults, FieldOptions, FieldType } from "./field.ts"; import { Index, IndexType } from "./index.ts"; +import { replaceBackTick, rowsPostgres } from "../util.ts"; +import { PostgresClient } from "../PostgresClient.ts"; +import { SqliteClient } from "../SqliteClient.ts"; +import { Connection } from "../deps.ts"; export interface QueryOptions { fields?: string[]; @@ -31,7 +35,7 @@ export class BaseModel { created_at?: Date; updated_at?: Date; - constructor(public connection?: Connection) {} + constructor(public connection?: Connection | PostgresClient | SqliteClient) {} /** get model name */ get modelName(): string { @@ -161,8 +165,25 @@ export class BaseModel { where: options, }; } - const result = await this.query(this.optionsToQuery(options).limit(0, 1)); - return this.convertModel(result[0]); + let result; + let resultSqlite: any; + let resultPostgres: any; + let converted: ModelFields | undefined; + if (dso.configClientReturn.sqlite != null) { + resultSqlite = await this.querySqlite(this.optionsToQuery(options)); + const resultArray = [...resultSqlite]; + converted = this.convertModel(resultArray[0]); + } else if (dso.configClientReturn.postgres != null) { + resultPostgres = await this.queryPostgres(this.optionsToQuery(options)); + + converted = this.convertModel(rowsPostgres(resultPostgres)[0]); + } else { + result = await this.query(this.optionsToQuery(options).limit(0, 1)); + + converted = this.convertModel(result[0]); + } + + return converted; } /** @@ -170,12 +191,23 @@ export class BaseModel { * @param where */ async delete(where: Where): Promise { - const result = await this.execute( - this.builder() - .delete() - .where(where), - ); - return result.affectedRows ?? 0; + const query = this.builder().delete().where(where); + let result: any; + let deleteCounts: number | undefined; + let resultPostgres: any; + + if (dso.configClientReturn.sqlite != null) { + await this.executeQuerySqlite(query); + deleteCounts = dso.clientSqlite.changes; + } else if (dso.configClientReturn.postgres != null) { + resultPostgres = await this.executeQueryPostGres(query); + deleteCounts = parseInt(resultPostgres.rowCount); + } else { + result = await this.execute(query); + deleteCounts = result.affectedRows; + } + + return deleteCounts ?? 0; } /** find all records by given conditions */ @@ -198,8 +230,42 @@ export class BaseModel { /** insert record */ async insert(fields: Partial): Promise { const query = this.builder().insert(this.convertObject(fields)); - const result = await this.execute(query); - return result.lastInsertId; + + let result: any; + let idReturn: number; + + if (dso.configClientReturn.sqlite != null) { + await this.executeQuerySqlite(query); + idReturn = dso.clientSqlite.lastInsertRowId; + } else { + result = await this.execute(query); + idReturn = result.lastInsertId; + } + + return idReturn; + } + + /** insert record */ + async insertRowsAffected(fields: Partial): Promise { + const query = this.builder().insert(this.convertObject(fields)); + + let resultPostgres: any; + let result: any; + + let updateCounts; + + if (dso.configClientReturn.sqlite != null) { + await this.executeQuerySqlite(query); + updateCounts = dso.clientSqlite.changes; + } else if (dso.configClientReturn.postgres != null) { + resultPostgres = await this.executeQueryPostGres(query); + updateCounts = parseInt(resultPostgres.rowCount); + } else { + result = await this.execute(query); + updateCounts = result.affectedRows; + } + + return updateCounts; } /** update records by given conditions */ @@ -220,8 +286,23 @@ export class BaseModel { .update(this.convertObject(data)) .where(where ?? ""); - const result = await this.execute(query); - return result.affectedRows; + let result: any; + let resultPostgres: any; + + let updateCounts; + + if (dso.configClientReturn.sqlite != null) { + await this.executeQuerySqlite(query); + updateCounts = dso.clientSqlite.changes; + } else if (dso.configClientReturn.postgres != null) { + resultPostgres = await this.executeQueryPostGres(query); + updateCounts = parseInt(resultPostgres.rowCount); + } else { + result = await this.execute(query); + updateCounts = result.affectedRows; + } + + return updateCounts; } /** @@ -238,6 +319,35 @@ export class BaseModel { return result; } + /** + * query custom + * @param query + */ + async querySqlite(query: Query): Promise { + const sql = query.build(); + console.log(replaceBackTick(sql)); + dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); + const result: any = this.connection + ? await this.connection.query(replaceBackTick(sql)) + : await dso.clientSqlite.query(replaceBackTick(sql)).asObjects(); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + + /** + * query custom + * @param query + */ + async queryPostgres(query: Query): Promise { + const sql = query.build(); + dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); + const result: any = this.connection + ? await this.connection.query(replaceBackTick(sql)) + : await dso.clientPostgres.query(replaceBackTick(sql)); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + /** * excute custom * @param query @@ -248,6 +358,41 @@ export class BaseModel { const result = this.connection ? await this.connection.execute(sql) : await dso.client.execute(sql); + + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + + /** + * excute custom + * @param query + */ + async executeQueryPostGres(query: Query) { + const sql = query.build(); + + dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); + + const result = this.connection + ? await this.connection.query(replaceBackTick(sql)) + : await dso.clientPostgres.query(replaceBackTick(sql)); + + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + + /** + * excute custom + * @param query + */ + async executeQuerySqlite(query: Query) { + const sql = query.build(); + console.log(replaceBackTick(sql)); + dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); + + const result = this.connection + ? await this.connection.query(replaceBackTick(sql)) + : await dso.clientSqlite.query(replaceBackTick(sql)); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; } diff --git a/src/sync.ts b/src/sync.ts index 6ab56ea..10d2298 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -3,10 +3,21 @@ import { dso } from "../mod.ts"; import { FieldType, Defaults } from "./field.ts"; import { BaseModel } from "./model.ts"; import { columnIndexesList, Index } from "./index.ts"; +import { replaceBackTick } from "../util.ts"; +import { SqliteClient } from "../SqliteClient.ts"; +import { PostgresClient } from "../PostgresClient.ts"; -export async function sync(client: Client, model: BaseModel, force: boolean) { +export async function sync( + client: Client | PostgresClient | SqliteClient, + model: BaseModel, + force: boolean, +) { if (force) { - await client.execute(`DROP TABLE IF EXISTS ${model.modelName}`); + if (client instanceof Client) { + await client.execute(`DROP TABLE IF EXISTS ${model.modelName}`); + } else { + await client.query(`DROP TABLE IF EXISTS ${model.modelName}`); + } } let defs = model.modelFields @@ -18,7 +29,17 @@ export async function sync(client: Client, model: BaseModel, force: boolean) { type = `VARCHAR(${field.length || 255})`; break; case FieldType.INT: - type = `INT(${field.length || 11})`; + if (client instanceof Client) { + type = `INT(${field.length || 11})`; + } else if (client instanceof SqliteClient) { + type = `INTEGER `; + } else { + if (field.autoIncrement) { + type = `SERIAL`; + } else { + type = `INT`; + } + } break; case FieldType.DATE: type = `TIMESTAMP`; @@ -38,6 +59,7 @@ export async function sync(client: Client, model: BaseModel, force: boolean) { break; } } + def += ` ${type}`; if (field.notNull) def += " NOT NULL"; if (field.default != null) { @@ -47,13 +69,21 @@ export async function sync(client: Client, model: BaseModel, force: boolean) { def += ` DEFAULT ${field.default}`; } } - if (field.autoIncrement) def += " AUTO_INCREMENT"; + if (client instanceof Client) { + if (field.autoIncrement) def += " AUTO_INCREMENT"; + } + if (field.autoUpdate) { assert( field.type === FieldType.DATE, "AutoUpdate only support Date field", ); - def += ` ON UPDATE CURRENT_TIMESTAMP()`; + + if (client instanceof Client) { + def += ` ON UPDATE CURRENT_TIMESTAMP()`; + } else { + // not yet supported def += ON UPDATE CURRENT_TIMESTAMP(); + } } return def; }) @@ -76,17 +106,36 @@ export async function sync(client: Client, model: BaseModel, force: boolean) { defs += `, ${columnIndexesList[index.type]} (${index.columns.join(", ")})`; }); - const sql = [ - "CREATE TABLE IF NOT EXISTS", - model.modelName, - "(", - defs, - ")", - "ENGINE=InnoDB DEFAULT CHARSET=utf8;", - ].join(" "); + let sql; + + if (client instanceof Client) { + sql = [ + "CREATE TABLE IF NOT EXISTS", + model.modelName, + "(", + defs, + ")", + "ENGINE=InnoDB DEFAULT CHARSET=utf8;", + ].join(" "); + } else { + sql = [ + "CREATE TABLE IF NOT EXISTS", + model.modelName, + "(", + defs, + ");", + ].join(" "); + console.log(sql); + } console.log(sql); dso.showQueryLog && console.log(`\n[ DSO:SYNC ]\nSQL:\t ${sql}\n`); - const result = await client.execute(sql); + let result; + if (client instanceof Client) { + result = await client.execute(sql); + } else { + result = await client.query(replaceBackTick(sql)); + } + dso.showQueryLog && console.log(`REUSLT:\t`, result, `\n`); } diff --git a/src/transaction.ts b/src/transaction.ts index d7819d1..a4c77fd 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -1,20 +1,43 @@ import { Connection } from "../deps.ts"; import { dso } from "./dso.ts"; import { BaseModel } from "./model.ts"; +import { PostgresClient } from "../PostgresClient.ts"; +import { SqliteClient } from "../SqliteClient.ts"; export class Transaction { - constructor(private _conn: Connection) {} - getModel(Model: { new (conn: Connection): T }): T { + constructor(private _conn: Connection | PostgresClient | SqliteClient) {} + getModel( + Model: { new (conn: Connection | PostgresClient | SqliteClient): T }, + ): T { const model = new Model(this._conn); return model; } static async transaction( processor: (transaction: Transaction) => Promise, + driverType: string, ): Promise { - return (await dso.client.transaction(async (conn) => { - const trans = new Transaction(conn); - return await processor(trans); - })) as T; + if (driverType.toUpperCase() == "MYSQL") { + return ( + await dso.client.transaction(async (conn) => { + const trans = new Transaction(conn); + return await processor(trans); + }) + ) as T; + } else if (driverType.toUpperCase() == "POSTGRES") { + return ( + await dso.clientPostgres.transaction(async (conn) => { + const trans = new Transaction(conn); + return await processor(trans); + }) + ) as T; + } else { + return ( + await dso.clientSqlite.transaction(async (conn) => { + const trans = new Transaction(conn); + return await processor(trans); + }) + ) as T; + } } } diff --git a/test.db b/test.db new file mode 100644 index 0000000000000000000000000000000000000000..0063b8e26da17a81c24756eeb3e4e71fb739f4da GIT binary patch literal 16384 zcmeI%!E4h{90%~1rfyVdQxFY1`2#Nvw3;RjY=fb(MlH5=%hJIP60#=4VB6FrRq}&65WYrYWs0j4>}B;x~|&yx)5X`F;4k!y|_WZ9n$tGzf=IOe!lgj$;pq z7-Q$jnNQBe@j1C!?Q@HPORnea?)|St;TGeIXRLTu`X=1IWHyE%009U<00Izz00bZa zf&WiH_?bHrMUnedkDZf&H;KG3N}F8MG<3@#OMlolC=F4CCS%u$J=ab)FjlK$99nv3 zpB@>H^&|QzZ-l;Pg#P%C!uzbbx%jD##m%+%hN0Z@+7cQPGedlVMSIho`G%a0mY8Yh> zs-n!`_sv#EH;?I=aZDB8O(MN+>!4>)WffNe)&vCp4kvHdRZUZO>RN+e>nn|1N%+OC@p+zcIi6>Q z#s7cdOV%Ke4+J0p0SG_<0uX=z1Rwwb2tXjS0{6HLRb_=6imb>rdAlYzsIKlR@~)y> J5PBa3zX4p3CMN&@ literal 0 HcmV?d00001 diff --git a/test.ts b/test.ts index f26ffed..8be1365 100644 --- a/test.ts +++ b/test.ts @@ -1,8 +1,9 @@ import { Client } from "./deps.ts"; import { dso } from "./mod.ts"; import "./test/model.ts"; +import { ClientConfig } from "./deps.ts"; -const config = { +const config: ClientConfig = { hostname: "127.0.0.1", port: 3306, poolSize: 3, @@ -15,11 +16,16 @@ const config = { const client = new Client(); dso.showQueryLog = false; +const mysqlConfig = { + type: "MYSQL", + clientConfig: { ...config, db: "test_orm" }, +}; + export async function clientTest(fn: Function) { Deno.test({ name: fn.name, fn: async () => { - await dso.connect({ ...config, db: "test_orm" }); + await dso.connect(mysqlConfig); await dso.sync(true); await fn(); dso.close(); @@ -35,3 +41,45 @@ async function main() { } await main(); + +const config2 = { + user: "thankgodukachukwu", + database: "test_orm", + hostname: "127.0.0.1", + password: "", + port: 5432, +}; + +const postgresConfig = { + type: "POSTGRES", + clientConfig: config2, +}; + +export async function clientTestPostgres(fn: Function) { + Deno.test({ + name: fn.name, + fn: async () => { + await dso.connect(postgresConfig); + await dso.sync(true); + await fn(); + dso.close(); + }, + }); +} + +const sqliteConfig = { + type: "SQLITE", + clientConfig: { database: "test.db" }, +}; + +export async function clientTestSQLITE(fn: Function) { + Deno.test({ + name: fn.name, + fn: async () => { + await dso.connect(sqliteConfig); + await dso.sync(true); + await fn(); + dso.close(); + }, + }); +} diff --git a/test/model.ts b/test/model.ts index 4b782d9..1b9908d 100644 --- a/test/model.ts +++ b/test/model.ts @@ -9,7 +9,9 @@ import { Query, Where, } from "../mod.ts"; -import { clientTest } from "../test.ts"; +import { clientTest, clientTestPostgres, clientTestSQLITE } from "../test.ts"; + +/** deno test --allow-net --allow-read --allow-write -c tsconfig.json */ @Model("users") class UserModel extends BaseModel { @@ -50,8 +52,8 @@ class TopicModel extends BaseModel { title?: string; } -const userModel = dso.define(UserModel); -const topicModel = dso.define(TopicModel); +let userModel = dso.define(UserModel); +let topicModel = dso.define(TopicModel); clientTest(async function testInsert() { assertEquals( @@ -70,6 +72,41 @@ clientTest(async function testInsert() { }), 2, ); + assertEquals( + await userModel.insert({ + nickName: "foo", + password: "bar", + phoneNumber: "08135536124", + }), + 3, + ); +}); + +clientTest(async function testInsertRowsAffected() { + assertEquals( + await userModel.insertRowsAffected({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539123", + }), + 1, + ); + assertEquals( + await userModel.insertRowsAffected({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539124", + }), + 1, + ); + assertEquals( + await userModel.insertRowsAffected({ + nickName: "foo", + password: "bar", + phoneNumber: "08135536124", + }), + 1, + ); }); clientTest(async function testUpdate() { @@ -174,9 +211,9 @@ clientTest(async function testTransactionFail() { topicId = await topicModel.insert({ title: "zoo", userId }); let user = await userModel.findById(userId!); assert(!!user); - await userModel.query(new Query().table("notexixts").select("*")); + await userModel.query(new Query().table("notexists").select("*")); return true; - }); + }, "MYSQL"); }); const user = await userModel.findById(userId!); const topic = await topicModel.findById(topicId!); @@ -195,7 +232,366 @@ clientTest(async function testTransactionSuccess() { let user = await userModel.findById(userId!); assert(!!user); return true; + }, "Mysql"); + const user = await userModel.findById(userId!); + const topic = await userModel.findById(topicId!); + assertEquals(result, true); + assert(!!topic); + assert(!!user); +}); + +userModel = dso.define(UserModel); +topicModel = dso.define(TopicModel); + +clientTestSQLITE(async function testInsert() { + assertEquals( + await userModel.insert({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539123", + }), + 1, + ); + assertEquals( + await userModel.insert({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539124", + }), + 2, + ); + assertEquals( + await userModel.insert({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539154", + }), + 3, + ); +}); + +clientTestSQLITE(async function testInsertRowsAffected() { + assertEquals( + await userModel.insertRowsAffected({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539123", + }), + 1, + ); + assertEquals( + await userModel.insertRowsAffected({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539124", + }), + 1, + ); + assertEquals( + await userModel.insertRowsAffected({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539154", + }), + 1, + ); +}); + +clientTestSQLITE(async function testUpdate() { + const id: number | undefined = await userModel.insert( + { nickName: "foo", phoneNumber: "08135539123" }, + ); + console.log(id); + assertEquals( + await userModel.update({ + id, + password: "BAR", + }), + 1, + ); + const user = await userModel.findById(id!); + + assertEquals(user, { + updated_at: user?.updated_at, + created_at: user?.created_at, + defaultVal: 0, + id: 1, + nickName: "foo", + password: "BAR", + phoneNumber: "08135539123", + }); +}); + +clientTestSQLITE(async function testFindOneByWhere() { + await userModel.insert({ nickName: "foo", phoneNumber: "08135539123" }); + await topicModel.insert({ title: "foo", userId: 1 }); + const user = await userModel.findOne( + Where.and( + Where.field("id").eq(1), + Where.field("password").isNull(), + Where.field("default_val").lt(10), + ), + ); + const topic = await topicModel.findById(1); + assertEquals(user, { + id: 1, + nickName: "foo", + password: null, + defaultVal: 0, + phoneNumber: "08135539123", + updated_at: user?.updated_at, + created_at: user?.created_at, }); + assert(!!topic?.created_at); + assertEquals(topic, { + updated_at: topic?.updated_at, + created_at: topic?.created_at, + id: 1, + title: "foo", + userId: 1, + }); +}); + +clientTestSQLITE(async function testDelete() { + await userModel.insert({ nickName: "foo" }); + await userModel.insert({ nickName: "bar" }); + await userModel.insert({ nickName: "noo" }); + const count = await userModel.delete( + Where.or(Where.field("id").eq(1), Where.field("nick_name").eq("noo")), + ); + + assertEquals(count, 2); +}); + +clientTestSQLITE(async function testFindOneByOptions() { + await userModel.insert({ nickName: "foo" }); + await topicModel.insert({ title: "foo", userId: 1 }); + const user = await userModel.findOne({ + where: Where.and( + Where.field("id").eq(1), + Where.field("password").isNull(), + Where.field("default_val").lt(10), + ), + }); + const topic = await topicModel.findOne({ + where: Where.field("topics.id").eq(1), + fields: ["topics.*", "users.nick_name as userNickName"], + join: [Join.left("users").on("users.id", "topics.user_id")], + }); + assert(!!topic?.created_at); + assert(!!topic?.updated_at); + assertEquals(topic, { + id: 1, + title: "foo", + userId: 1, + userNickName: "foo", + updated_at: topic?.updated_at, + created_at: topic?.created_at, + }); +}); + +clientTestSQLITE(async function testTransactionSuccess() { + let topicId: number | undefined; + let userId: number | undefined; + const result = await dso.transaction(async (trans) => { + const userModel = trans.getModel(UserModel); + const topicModel = trans.getModel(TopicModel); + userId = await userModel.insert( + { nickName: "foo", password: "bar" }, + ); + topicId = await topicModel.insert({ title: "zoo", userId }); + let user = await userModel.findById(userId!); + assert(!!user); + return true; + }, "SQLITE"); + const user = await userModel.findById(userId!); + const topic = await userModel.findById(topicId!); + assertEquals(result, true); + assert(!!topic); + assert(!!user); +}); + +clientTestSQLITE(async function testTransactionFail() { + let userId: number | undefined; + let topicId: number | undefined; + await assertThrowsAsync(async () => { + await dso.transaction(async (trans) => { + const userModel = trans.getModel(UserModel); + const topicModel = trans.getModel(TopicModel); + userId = await userModel.insert({ nickName: "foo", password: "bar" }); + topicId = await topicModel.insert({ title: "zoo", userId }); + let user = await userModel.findById(userId!); + assert(!!user); + await userModel.query(new Query().table("notexists").select("*")); + return true; + }, "SQLITE"); + }); + const user = await userModel.findById(userId!); + const topic = await topicModel.findById(topicId!); + assert(!user); + assert(!topic); +}); + +userModel = dso.define(UserModel); +topicModel = dso.define(TopicModel); + +clientTestPostgres(async function testInsert() { + assertEquals( + await userModel.insertRowsAffected({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539123", + }), + 1, + ); + assertEquals( + await userModel.insertRowsAffected({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539124", + }), + 1, + ); + assertEquals( + await userModel.insertRowsAffected({ + nickName: "foo", + password: "bar", + phoneNumber: "08135539154", + }), + 1, + ); +}); + +clientTestPostgres(async function testUpdate() { + const id: number | undefined = await userModel.insertRowsAffected( + { nickName: "foo", phoneNumber: "08135539123" }, + ); + assertEquals( + await userModel.update({ + id, + password: "BAR", + }), + 1, + ); + const user = await userModel.findById(id!); + assertEquals(user, { + updated_at: user?.updated_at, + created_at: user?.created_at, + defaultVal: 0, + id: 1, + nickName: "foo", + password: "BAR", + phoneNumber: "08135539123", + }); +}); + +clientTestPostgres(async function testFindOneByWhere() { + await userModel.insertRowsAffected( + { nickName: "foo", phoneNumber: "08135539123" }, + ); + await topicModel.insertRowsAffected({ title: "foo", userId: 1 }); + const user = await userModel.findOne( + Where.and( + Where.field("id").eq(1), + Where.field("password").isNull(), + Where.field("default_val").lt(10), + ), + ); + const topic = await topicModel.findById(1); + assertEquals(user, { + id: 1, + nickName: "foo", + password: null, + defaultVal: 0, + phoneNumber: "08135539123", + updated_at: user?.updated_at, + created_at: user?.created_at, + }); + assert(!!topic?.created_at); + assertEquals(topic, { + updated_at: topic?.updated_at, + created_at: topic?.created_at, + id: 1, + title: "foo", + userId: 1, + }); +}); + +clientTestPostgres(async function testDelete() { + await userModel.insertRowsAffected({ nickName: "foo" }); + await userModel.insertRowsAffected({ nickName: "bar" }); + await userModel.insertRowsAffected({ nickName: "noo" }); + const count = await userModel.delete( + Where.or(Where.field("id").eq(1), Where.field("nick_name").eq("noo")), + ); + + assertEquals(count, 2); +}); + +clientTestPostgres(async function testFindOneByOptions() { + await userModel.insertRowsAffected({ nickName: "foo" }); + await topicModel.insertRowsAffected({ title: "foo", userId: 1 }); + const user = await userModel.findOne({ + where: Where.and( + Where.field("id").eq(1), + Where.field("password").isNull(), + Where.field("default_val").lt(10), + ), + }); + const topic = await topicModel.findOne({ + where: Where.field("topics.id").eq(1), + fields: ["topics.*", "users.nick_name as userNickName"], + join: [Join.left("users").on("users.id", "topics.user_id")], + }); + + assert(!!topic?.created_at); + assert(!!topic?.updated_at); + assertEquals(topic, { + updated_at: topic?.updated_at, + created_at: topic?.created_at, + id: 1, + userId: 1, + title: "foo", + usernickname: "foo", // Postgres changes alia to small letters + }); +}); +clientTestPostgres(async function testTransactionFail() { + let userId: number | undefined; + let topicId: number | undefined; + await assertThrowsAsync(async () => { + await dso.transaction(async (trans) => { + const userModel = trans.getModel(UserModel); + const topicModel = trans.getModel(TopicModel); + userId = await userModel.insertRowsAffected( + { nickName: "foo", password: "bar" }, + ); + topicId = await topicModel.insertRowsAffected({ title: "zoo", userId }); + let user = await userModel.findById(userId!); + assert(!!user); + await userModel.query(new Query().table("notexists").select("*")); + return true; + }, "POSTGRES"); + }); + const user = await userModel.findById(userId!); + const topic = await topicModel.findById(topicId!); + assert(!user); + assert(!topic); +}); + +clientTestPostgres(async function testTransactionSuccess() { + let topicId: number | undefined; + let userId: number | undefined; + const result = await dso.transaction(async (trans) => { + const userModel = trans.getModel(UserModel); + const topicModel = trans.getModel(TopicModel); + userId = await userModel.insertRowsAffected( + { nickName: "foo", password: "bar" }, + ); + topicId = await topicModel.insertRowsAffected({ title: "zoo", userId }); + let user = await userModel.findById(userId!); + assert(!!user); + return true; + }, "POSTGRES"); const user = await userModel.findById(userId!); const topic = await userModel.findById(topicId!); assertEquals(result, true); diff --git a/util.ts b/util.ts index aa68843..5d8b52e 100644 --- a/util.ts +++ b/util.ts @@ -9,3 +9,26 @@ export function line2camel(key: string) { return letter.toUpperCase(); }); } +export function replaceBackTick(sql: string) { + return sql.replace(/`/g, " ").replace(/"/g, "'"); +} + +export function rowsPostgres(queryResult: any) { + let returnedResult: any = []; + + const rows = queryResult.rows; + const columns = queryResult.rowDescription.columns; + const columnNames: string[] = columns.map((column: any) => { + return column.name; + }); + rows.forEach((row: any, rowIndex: any) => { + let rowData: any = {}; + row.forEach((rVal: any, rIndex: any) => { + const columnName: string = columnNames[rIndex]; + rowData[columnName] = row[rIndex]; + }); + returnedResult.push(rowData); + }); + + return returnedResult; +} From 184a01955776b1f4280e48c7fc63f1b932a8424a Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 14:33:39 +0100 Subject: [PATCH 02/24] feat:databaseDriverAdaptations - addedd Postgres|Sqlite --- .gitignore | 2 ++ test.db | Bin 16384 -> 0 bytes 2 files changed, 2 insertions(+) delete mode 100644 test.db diff --git a/.gitignore b/.gitignore index 61ef1a6..6508dd0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ mysql.log package-lock.json .vscode node_modules +test.db +*.code-workspace diff --git a/test.db b/test.db deleted file mode 100644 index 0063b8e26da17a81c24756eeb3e4e71fb739f4da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI%!E4h{90%~1rfyVdQxFY1`2#Nvw3;RjY=fb(MlH5=%hJIP60#=4VB6FrRq}&65WYrYWs0j4>}B;x~|&yx)5X`F;4k!y|_WZ9n$tGzf=IOe!lgj$;pq z7-Q$jnNQBe@j1C!?Q@HPORnea?)|St;TGeIXRLTu`X=1IWHyE%009U<00Izz00bZa zf&WiH_?bHrMUnedkDZf&H;KG3N}F8MG<3@#OMlolC=F4CCS%u$J=ab)FjlK$99nv3 zpB@>H^&|QzZ-l;Pg#P%C!uzbbx%jD##m%+%hN0Z@+7cQPGedlVMSIho`G%a0mY8Yh> zs-n!`_sv#EH;?I=aZDB8O(MN+>!4>)WffNe)&vCp4kvHdRZUZO>RN+e>nn|1N%+OC@p+zcIi6>Q z#s7cdOV%Ke4+J0p0SG_<0uX=z1Rwwb2tXjS0{6HLRb_=6imb>rdAlYzsIKlR@~)y> J5PBa3zX4p3CMN&@ From 638ce2c5f993f0d431128e6fc1379f0ea4041208 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 14:41:01 +0100 Subject: [PATCH 03/24] feat:databaseDriverAdaptations - addedd Postgres|Sqlite --- src/Reflect.ts | 64 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Reflect.ts b/src/Reflect.ts index 2f77c2b..b47fd0f 100644 --- a/src/Reflect.ts +++ b/src/Reflect.ts @@ -53,7 +53,7 @@ namespace Reflect { } interface MapConstructor { - new (): Map; + new(): Map; new (): Map; prototype: Map; } @@ -70,7 +70,7 @@ namespace Reflect { } interface SetConstructor { - new (): Set; + new(): Set; new (): Set; prototype: Set; } @@ -84,7 +84,7 @@ namespace Reflect { } interface WeakMapConstructor { - new (): WeakMap; + new(): WeakMap; new (): WeakMap; prototype: WeakMap; } @@ -702,10 +702,10 @@ namespace Reflect { const root = typeof global === "object" ? global : typeof self === "object" - ? self - : typeof this === "object" - ? this - : Function("return this;")(); + ? self + : typeof this === "object" + ? this + : Function("return this;")(); let exporter = makeExporter(Reflect); if (typeof root.Reflect === "undefined") { @@ -759,8 +759,8 @@ namespace Reflect { create: supportsCreate ? () => MakeDictionary(Object.create(null) as HashMap) : supportsProto - ? () => MakeDictionary({ __proto__: null as any } as HashMap) - : () => MakeDictionary({} as HashMap), + ? () => MakeDictionary({ __proto__: null as any } as HashMap) + : () => MakeDictionary({} as HashMap), has: downLevel ? (map: HashMap, key: string | number | symbol) => @@ -780,13 +780,13 @@ namespace Reflect { process.env && process.env["REFLECT_METADATA_USE_MAP_POLYFILL"] === "true"; const _Map: typeof Map = !usePolyfill && - typeof Map === "function" && - typeof Map.prototype.entries === "function" + typeof Map === "function" && + typeof Map.prototype.entries === "function" ? Map : CreateMapPolyfill(); const _Set: typeof Set = !usePolyfill && - typeof Set === "function" && - typeof Set.prototype.entries === "function" + typeof Set === "function" && + typeof Set.prototype.entries === "function" ? Set : CreateSetPolyfill(); const _WeakMap: typeof WeakMap = @@ -873,7 +873,7 @@ namespace Reflect { if (IsNull(attributes)) attributes = undefined; propertyKey = ToPropertyKey(propertyKey); return DecorateProperty( - decorators, + decorators, target, propertyKey, attributes, @@ -882,8 +882,8 @@ namespace Reflect { if (!IsArray(decorators)) throw new TypeError(); if (!IsConstructor(target)) throw new TypeError(); return DecorateConstructor( - decorators, - target, + decorators, + target, ); } } @@ -1429,7 +1429,7 @@ namespace Reflect { const decorated = decorator(target); if (!IsUndefined(decorated) && !IsNull(decorated)) { if (!IsConstructor(decorated)) throw new TypeError(); - target = decorated; + target = decorated; } } return target; @@ -1446,7 +1446,7 @@ namespace Reflect { const decorated = decorator(target, propertyKey, descriptor); if (!IsUndefined(decorated) && !IsNull(decorated)) { if (!IsObject(decorated)) throw new TypeError(); - descriptor = decorated; + descriptor = decorated; } } return descriptor; @@ -1667,7 +1667,7 @@ namespace Reflect { // https://tc39.github.io/ecma262/#sec-object-type function IsObject< T, - >(x: T | undefined | null | boolean | string | symbol | number): x is T { + >(x: T | undefined | null | boolean | string | symbol | number): x is T { return typeof x === "object" ? x !== null : typeof x === "function"; } @@ -1697,8 +1697,8 @@ namespace Reflect { const hint: "string" | "number" | "default" = PreferredType === Tag.String ? "string" : PreferredType === Tag.Number - ? "number" - : "default"; + ? "number" + : "default"; const exoticToPrim = GetMethod(input, toPrimitiveSymbol); if (exoticToPrim !== undefined) { const result = exoticToPrim.call(input, hint); @@ -1769,8 +1769,8 @@ namespace Reflect { return Array.isArray ? Array.isArray(argument) : argument instanceof Object - ? argument instanceof Array - : Object.prototype.toString.call(argument) === "[object Array]"; + ? argument instanceof Array + : Object.prototype.toString.call(argument) === "[object Array]"; } // 7.2.3 IsCallable(argument) @@ -1889,7 +1889,7 @@ namespace Reflect { K, V, R extends K | V | [K, V], - > implements IterableIterator { + > implements IterableIterator { private _keys: K[]; private _values: V[]; private _index = 0; @@ -1921,7 +1921,7 @@ namespace Reflect { } return { value: result, done: false }; } - return { value: undefined, done: true }; + return { value: undefined, done: true }; } throw(error: any): IteratorResult { if (this._index >= 0) { @@ -1937,7 +1937,7 @@ namespace Reflect { this._keys = arraySentinel; this._values = arraySentinel; } - return { value: value, done: true }; + return { value: value, done: true }; } } @@ -2104,20 +2104,20 @@ namespace Reflect { function GetOrCreateWeakMapTable< K, - >(target: K, create: true): HashMap; + >(target: K, create: true): HashMap; function GetOrCreateWeakMapTable< K, - >(target: K, create: false): HashMap | undefined; + >(target: K, create: false): HashMap | undefined; function GetOrCreateWeakMapTable< K, - >(target: K, create: boolean): HashMap | undefined { + >(target: K, create: boolean): HashMap | undefined { if (!hasOwn.call(target, rootKey)) { if (!create) return undefined; Object.defineProperty(target, rootKey, { value: HashMap.create(), }); } - return ( target)[rootKey]; + return (target)[rootKey]; } function FillRandomBytes(buffer: BufferLike, size: number): BufferLike { @@ -2156,8 +2156,8 @@ namespace Reflect { // uses a heuristic used by v8 and chakra to force an object into dictionary mode. function MakeDictionary(obj: T): T { - ( obj).__ = undefined; - delete ( obj).__; + (obj).__ = undefined; + delete (obj).__; return obj; } }); From b181a858a31dfb728827829c53e7bda8338f7a89 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 14:45:09 +0100 Subject: [PATCH 04/24] feat:databaseDriverAdaptations - addedd Postgres|Sqlite --- src/Reflect.ts | 64 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Reflect.ts b/src/Reflect.ts index b47fd0f..2f77c2b 100644 --- a/src/Reflect.ts +++ b/src/Reflect.ts @@ -53,7 +53,7 @@ namespace Reflect { } interface MapConstructor { - new(): Map; + new (): Map; new (): Map; prototype: Map; } @@ -70,7 +70,7 @@ namespace Reflect { } interface SetConstructor { - new(): Set; + new (): Set; new (): Set; prototype: Set; } @@ -84,7 +84,7 @@ namespace Reflect { } interface WeakMapConstructor { - new(): WeakMap; + new (): WeakMap; new (): WeakMap; prototype: WeakMap; } @@ -702,10 +702,10 @@ namespace Reflect { const root = typeof global === "object" ? global : typeof self === "object" - ? self - : typeof this === "object" - ? this - : Function("return this;")(); + ? self + : typeof this === "object" + ? this + : Function("return this;")(); let exporter = makeExporter(Reflect); if (typeof root.Reflect === "undefined") { @@ -759,8 +759,8 @@ namespace Reflect { create: supportsCreate ? () => MakeDictionary(Object.create(null) as HashMap) : supportsProto - ? () => MakeDictionary({ __proto__: null as any } as HashMap) - : () => MakeDictionary({} as HashMap), + ? () => MakeDictionary({ __proto__: null as any } as HashMap) + : () => MakeDictionary({} as HashMap), has: downLevel ? (map: HashMap, key: string | number | symbol) => @@ -780,13 +780,13 @@ namespace Reflect { process.env && process.env["REFLECT_METADATA_USE_MAP_POLYFILL"] === "true"; const _Map: typeof Map = !usePolyfill && - typeof Map === "function" && - typeof Map.prototype.entries === "function" + typeof Map === "function" && + typeof Map.prototype.entries === "function" ? Map : CreateMapPolyfill(); const _Set: typeof Set = !usePolyfill && - typeof Set === "function" && - typeof Set.prototype.entries === "function" + typeof Set === "function" && + typeof Set.prototype.entries === "function" ? Set : CreateSetPolyfill(); const _WeakMap: typeof WeakMap = @@ -873,7 +873,7 @@ namespace Reflect { if (IsNull(attributes)) attributes = undefined; propertyKey = ToPropertyKey(propertyKey); return DecorateProperty( - decorators, + decorators, target, propertyKey, attributes, @@ -882,8 +882,8 @@ namespace Reflect { if (!IsArray(decorators)) throw new TypeError(); if (!IsConstructor(target)) throw new TypeError(); return DecorateConstructor( - decorators, - target, + decorators, + target, ); } } @@ -1429,7 +1429,7 @@ namespace Reflect { const decorated = decorator(target); if (!IsUndefined(decorated) && !IsNull(decorated)) { if (!IsConstructor(decorated)) throw new TypeError(); - target = decorated; + target = decorated; } } return target; @@ -1446,7 +1446,7 @@ namespace Reflect { const decorated = decorator(target, propertyKey, descriptor); if (!IsUndefined(decorated) && !IsNull(decorated)) { if (!IsObject(decorated)) throw new TypeError(); - descriptor = decorated; + descriptor = decorated; } } return descriptor; @@ -1667,7 +1667,7 @@ namespace Reflect { // https://tc39.github.io/ecma262/#sec-object-type function IsObject< T, - >(x: T | undefined | null | boolean | string | symbol | number): x is T { + >(x: T | undefined | null | boolean | string | symbol | number): x is T { return typeof x === "object" ? x !== null : typeof x === "function"; } @@ -1697,8 +1697,8 @@ namespace Reflect { const hint: "string" | "number" | "default" = PreferredType === Tag.String ? "string" : PreferredType === Tag.Number - ? "number" - : "default"; + ? "number" + : "default"; const exoticToPrim = GetMethod(input, toPrimitiveSymbol); if (exoticToPrim !== undefined) { const result = exoticToPrim.call(input, hint); @@ -1769,8 +1769,8 @@ namespace Reflect { return Array.isArray ? Array.isArray(argument) : argument instanceof Object - ? argument instanceof Array - : Object.prototype.toString.call(argument) === "[object Array]"; + ? argument instanceof Array + : Object.prototype.toString.call(argument) === "[object Array]"; } // 7.2.3 IsCallable(argument) @@ -1889,7 +1889,7 @@ namespace Reflect { K, V, R extends K | V | [K, V], - > implements IterableIterator { + > implements IterableIterator { private _keys: K[]; private _values: V[]; private _index = 0; @@ -1921,7 +1921,7 @@ namespace Reflect { } return { value: result, done: false }; } - return { value: undefined, done: true }; + return { value: undefined, done: true }; } throw(error: any): IteratorResult { if (this._index >= 0) { @@ -1937,7 +1937,7 @@ namespace Reflect { this._keys = arraySentinel; this._values = arraySentinel; } - return { value: value, done: true }; + return { value: value, done: true }; } } @@ -2104,20 +2104,20 @@ namespace Reflect { function GetOrCreateWeakMapTable< K, - >(target: K, create: true): HashMap; + >(target: K, create: true): HashMap; function GetOrCreateWeakMapTable< K, - >(target: K, create: false): HashMap | undefined; + >(target: K, create: false): HashMap | undefined; function GetOrCreateWeakMapTable< K, - >(target: K, create: boolean): HashMap | undefined { + >(target: K, create: boolean): HashMap | undefined { if (!hasOwn.call(target, rootKey)) { if (!create) return undefined; Object.defineProperty(target, rootKey, { value: HashMap.create(), }); } - return (target)[rootKey]; + return ( target)[rootKey]; } function FillRandomBytes(buffer: BufferLike, size: number): BufferLike { @@ -2156,8 +2156,8 @@ namespace Reflect { // uses a heuristic used by v8 and chakra to force an object into dictionary mode. function MakeDictionary(obj: T): T { - (obj).__ = undefined; - delete (obj).__; + ( obj).__ = undefined; + delete ( obj).__; return obj; } }); From d5b500a3330997d3270fbb7595a67726e1cc7afd Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 15:30:52 +0100 Subject: [PATCH 05/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- src/Reflect.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Reflect.ts b/src/Reflect.ts index 2f77c2b..4933709 100644 --- a/src/Reflect.ts +++ b/src/Reflect.ts @@ -1,3 +1,4 @@ +// deno-fmt-ignore-file // @ts-nocheck /*! ***************************************************************************** Copyright (C) Microsoft. All rights reserved. From 69a0bd9b4953cc51398532c7373eacbb61ee2400 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 18:32:06 +0100 Subject: [PATCH 06/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- .github/workflows/ci.yml | 10 ++++++++++ docker-compose.yml | 10 +++++++++- test.ts | 3 ++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e950cfa..5ea91aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,16 @@ jobs: MYSQL_ROOT_PASSWORD: "" options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + postgres: + image: postgres:10.8 + ports: + - 5432:5432 + env: + POSTGRES_USER: "thankgodukachukwu" + POSTGRES_PASSWORD: "test_orm" + POSTGRES_DB: "test_orm" + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: - uses: actions/checkout@v2 - name: Setup Deno diff --git a/docker-compose.yml b/docker-compose.yml index 682dddf..6489cbe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,4 +7,12 @@ services: - 3306:3306 environment: MYSQL_ALLOW_EMPTY_PASSWORD: "true" - MYSQL_ROOT_PASSWORD: "" \ No newline at end of file + MYSQL_ROOT_PASSWORD: "" + postgres: + image: postgres:10.8 + ports: + - 5432:5432 + environment: + POSTGRES_USER: "thankgodukachukwu" + POSTGRES_PASSWORD: "test_orm" + POSTGRES_DB: "test_orm" \ No newline at end of file diff --git a/test.ts b/test.ts index 8be1365..3bebe65 100644 --- a/test.ts +++ b/test.ts @@ -46,7 +46,8 @@ const config2 = { user: "thankgodukachukwu", database: "test_orm", hostname: "127.0.0.1", - password: "", + //password: "", + password: "test_orm", port: 5432, }; From e004f0584c7a0f8445693e5fd3ab498afacaa773 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 18:38:08 +0100 Subject: [PATCH 07/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ea91aa..39be3c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 postgres: - image: postgres:10.8 + image: postgres:11.5 ports: - 5432:5432 env: @@ -29,6 +29,7 @@ jobs: POSTGRES_PASSWORD: "test_orm" POSTGRES_DB: "test_orm" options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + steps: - uses: actions/checkout@v2 From 37aa379a7a2de9796cbe2cbd36861fbac51c6c2f Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 18:41:35 +0100 Subject: [PATCH 08/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39be3c8..cc636f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,11 @@ jobs: POSTGRES_USER: "thankgodukachukwu" POSTGRES_PASSWORD: "test_orm" POSTGRES_DB: "test_orm" - options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: From a819428e65f6ebffdfd749233b673ca62729dc38 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 18:47:59 +0100 Subject: [PATCH 09/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc636f7..e4ce521 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,6 @@ jobs: POSTGRES_PASSWORD: "test_orm" POSTGRES_DB: "test_orm" options: >- - --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 From ecbf4f041767248bc58f42affdd3b577af9e7294 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 18:50:09 +0100 Subject: [PATCH 10/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4ce521..e8bebcf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,10 +28,6 @@ jobs: POSTGRES_USER: "thankgodukachukwu" POSTGRES_PASSWORD: "test_orm" POSTGRES_DB: "test_orm" - options: >- - --health-interval 10s - --health-timeout 5s - --health-retries 5 steps: From fcfca4febdd77165b6de7943263abcd9c7ea43c9 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 18:58:01 +0100 Subject: [PATCH 11/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- .github/workflows/ci.yml | 4 +++- docker-compose.yml | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8bebcf..096e7d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,8 @@ jobs: POSTGRES_USER: "thankgodukachukwu" POSTGRES_PASSWORD: "test_orm" POSTGRES_DB: "test_orm" + sqlite: + image: sqlite3 steps: @@ -41,4 +43,4 @@ jobs: deno fmt --check - name: Test run: | - deno test -c tsconfig.json --allow-net + deno test --allow-net --allow-read --allow-write -c tsconfig.json diff --git a/docker-compose.yml b/docker-compose.yml index 6489cbe..4113429 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,4 +15,7 @@ services: environment: POSTGRES_USER: "thankgodukachukwu" POSTGRES_PASSWORD: "test_orm" - POSTGRES_DB: "test_orm" \ No newline at end of file + POSTGRES_DB: "test_orm" + sqlite: + image: sqlite3 + \ No newline at end of file From c022ef4aee6e8d40c43759f3aecb136422249fa1 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 19:00:39 +0100 Subject: [PATCH 12/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- .github/workflows/ci.yml | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 096e7d3..6303251 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: POSTGRES_PASSWORD: "test_orm" POSTGRES_DB: "test_orm" sqlite: - image: sqlite3 + image: keinos/sqlite3:latest steps: diff --git a/docker-compose.yml b/docker-compose.yml index 4113429..36520a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,5 +17,5 @@ services: POSTGRES_PASSWORD: "test_orm" POSTGRES_DB: "test_orm" sqlite: - image: sqlite3 + image: keinos/sqlite3:latest \ No newline at end of file From d566ab539fc693fdde1af0ed1457fb3318af8f54 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 19:08:22 +0100 Subject: [PATCH 13/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e6a6c98..47231bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,13 @@ services: - mysql + - postgresql + - SQLITE3 + + before_install: - curl -fsSL https://deno.land/x/install/install.sh | sh - export PATH="/home/travis/.deno/bin:$PATH" script: - - deno test --unstable --allow-net -c tsconfig.json test.ts + - deno test --allow-net --allow-read --allow-write -c tsconfig.json test.ts From 882a794541bb268682ccdbcaa4d27c1cbfe0cc7a Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 19:22:26 +0100 Subject: [PATCH 14/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- .github/workflows/ci.yml | 4 ++-- .travis.yml | 3 +++ docker-compose.yml | 4 ++-- test.ts | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6303251..98f0302 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,8 +25,8 @@ jobs: ports: - 5432:5432 env: - POSTGRES_USER: "thankgodukachukwu" - POSTGRES_PASSWORD: "test_orm" + POSTGRES_USER: "postgres" + POSTGRES_PASSWORD: "" POSTGRES_DB: "test_orm" sqlite: image: keinos/sqlite3:latest diff --git a/.travis.yml b/.travis.yml index 47231bb..e7c4f29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,5 +9,8 @@ before_install: - curl -fsSL https://deno.land/x/install/install.sh | sh - export PATH="/home/travis/.deno/bin:$PATH" +before_script: + - psql -c 'create database test_orm;' -U postgres + script: - deno test --allow-net --allow-read --allow-write -c tsconfig.json test.ts diff --git a/docker-compose.yml b/docker-compose.yml index 36520a7..4307b2d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,8 +13,8 @@ services: ports: - 5432:5432 environment: - POSTGRES_USER: "thankgodukachukwu" - POSTGRES_PASSWORD: "test_orm" + POSTGRES_USER: "postgres" + POSTGRES_PASSWORD: "" POSTGRES_DB: "test_orm" sqlite: image: keinos/sqlite3:latest diff --git a/test.ts b/test.ts index 3bebe65..6702729 100644 --- a/test.ts +++ b/test.ts @@ -43,11 +43,11 @@ async function main() { await main(); const config2 = { - user: "thankgodukachukwu", + user: "postgres", database: "test_orm", hostname: "127.0.0.1", //password: "", - password: "test_orm", + password: "", port: 5432, }; From 4c5f1c8f6e86c46999bf55d787cc26988443af18 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 19:46:47 +0100 Subject: [PATCH 15/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index e096a2a..b071891 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,40 @@ export interface Config extends Base { */ +const config: ClientConfig = { + hostname: "127.0.0.1", + port: 3306, + poolSize: 3, // optional + debug: false, + username: "root", + password: "", + db: "", +}; + +const mysqlConfig = { + type: "MYSQL", + clientConfig: { ...config, db: "dbname" }, +}; + +const configPostgres = { + user: "username", + database: "dbname", + hostname: "127.0.0.1", + password: "", + port: 5432, +}; + +const postgresConfig = { + type: "POSTGRES", + clientConfig: configPostgres, +}; + +const sqliteConfig = { + type: "SQLITE", + clientConfig: { database: "test.db" }, +}; + + async function main() { // The database must be created before linking await dso.connect(mysqlConfig); From 135b742fafcd7ca9731f242bc9443e03a1f12ab4 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 19:50:17 +0100 Subject: [PATCH 16/24] feat:databaseDriverAdaptations - Postgres|Sqlite --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b071891..9e15e18 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ ![GitHub release](https://img.shields.io/github/release/manyuanrong/dso.svg) ![(Deno)](https://img.shields.io/badge/deno-1.0.0-green.svg) -`dso` is a simple ORM Library based on [deno_mysql](https://github.com/manyuanrong/deno_mysql) + +`dso` is a simple ORM Library based on [deno_mysql](https://github.com/manyuanrong/deno_mysql), [Deno-Postgres](https://github.com/deno-postgres/deno-postgres) and [Deno-Sqlite](https://github.com/dyedgreen/deno-sqlite). + ### Example @@ -96,7 +98,7 @@ const sqliteConfig = { async function main() { - // The database must be created before linking + // The database must be created before linking with the configuration object await dso.connect(mysqlConfig); await dso.connect(postgresConfig); await dso.connect(sqliteConfig); From 1b1326bd4c138ad271b82c6f23d4c727d0d24d18 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Wed, 4 Nov 2020 13:54:35 +0100 Subject: [PATCH 17/24] feat:databaseDriverAdaptations - MYSQL --- mod.ts | 1 + src/DsoClient.ts | 67 +++++++ src/MysqlClient.ts | 103 +++++++++++ src/dso.ts | 13 +- src/model.ts | 87 +++------ src/model_2.ts | 401 ++++++++++++++++++++++++++++++++++++++++ src/sync.ts | 29 +-- src/transaction.ts | 10 +- src/transactionMysql.ts | 27 +++ test.ts | 33 ++-- test/model.ts | 45 +++-- 11 files changed, 692 insertions(+), 124 deletions(-) create mode 100644 src/DsoClient.ts create mode 100644 src/MysqlClient.ts create mode 100644 src/model_2.ts create mode 100644 src/transactionMysql.ts diff --git a/mod.ts b/mod.ts index 35adb08..9fbc2ad 100644 --- a/mod.ts +++ b/mod.ts @@ -15,6 +15,7 @@ export { SqliteClient, } from "./SqliteClient.ts"; export { dso } from "./src/dso.ts"; +export { MysqlClient} from "./src/MysqlClient.ts"; export * from "./src/field.ts"; export * from "./src/index.ts"; export * from "./src/model.ts"; diff --git a/src/DsoClient.ts b/src/DsoClient.ts new file mode 100644 index 0000000..dd75f48 --- /dev/null +++ b/src/DsoClient.ts @@ -0,0 +1,67 @@ +import { Connection } from "../deps.ts"; +import { MysqlClient } from "./MysqlClient.ts"; + +export enum ClientType { + MYSQL = "MYSQL", + POSTGRESS = "POSTGRESS", + SQLITE = "SQLITE", + } + + export interface DsoConnection { + query(sql: string, params?: any[]): Promise; + execute(sql: string, params?: any[]): Promise; + } + + /** Transaction processor */ + export interface TransactionProcessor { + (connection: MysqlClient): Promise; + } + + export interface PoolInfo { + size: number| undefined; + maxSize: number |undefined; + available: number | undefined; + } + + /** + * DSO client + */ + export abstract class DsoClient { + /** get pool info */ + abstract get pool(): PoolInfo| undefined; + + /** + * connect to database + * @param config config for client + * @returns Clinet instance + */ + abstract connect(config: T): Promise; + + /** + * excute query sql + * @param sql query sql string + * @param params query params + */ + abstract async query(sql: string, params?: any[]): Promise; + + /** + * excute sql + * @param sql sql string + * @param params query params + */ + abstract async execute(sql: string, params?: any[]): Promise; + + /** + * Execute a transaction process, and the transaction successfully + * returns the return value of the transaction process + * @param processor transation processor + + abstract async transaction( + processor: TransactionProcessor + ): Promise; + + /** + * close connection + */ + abstract async close(): Promise; + } \ No newline at end of file diff --git a/src/MysqlClient.ts b/src/MysqlClient.ts new file mode 100644 index 0000000..1ed3455 --- /dev/null +++ b/src/MysqlClient.ts @@ -0,0 +1,103 @@ +import { Connection } from "../deps.ts"; +import { Client } from "../deps.ts"; +import { TransactionProcessor } from "./DsoClient.ts"; + +import { DsoClient, PoolInfo } from "./DsoClient.ts"; +import { BaseModel } from "./model.ts"; +import { sync } from "./sync.ts"; +import { TransactionMysql } from "./transactionMysql.ts"; + + +export class MysqlClient extends DsoClient { + + #client: Client = new Client(); + + /** @ignore */ + #models: BaseModel[] = []; + + + /** + * @param showQuerylog + * set true will show execute/query sql + * + */ + constructor(private _conn?: Connection) { + super(); + + } + + + + /** + * Sync model to database table + * @param force set true, will drop table before create table + */ + async sync(force: boolean = false): Promise { + + for (const model of this.#models) { + await sync(this, model, force); + } + + } + + /** + * MySQL Database client + */ + get client(): Client { + return this.#client; + } + + /** + * all models + */ + get models() { + return this.#models; + } + + /** + * add model + * @param model + */ + define(ModelClass: { new(connection: Connection| MysqlClient): T }): T { + const model = new ModelClass(this); + this.#models.push(model); + return model; + } + + async close(): Promise { + this.#client.close(); + } + + async connect(config: ClientConfig): Promise { + await this.#client.connect(config); + return this; + } + + + get pool(): PoolInfo { + + const poolInfo = { + size: this.#client.pool?.size , + maxSize: this.#client.pool?.maxSize , + available: this.#client.pool?.available, + + } + return poolInfo; + } + async query(sql: string, params?: any[]): Promise { + return await this.#client.query(sql, params); + + } + async execute(sql: string, params?: any[]): Promise { + + return await this.#client.execute(sql, params); + + } + transaction = TransactionMysql.transaction; + + static showQueryLog: boolean = false; + + + + +} \ No newline at end of file diff --git a/src/dso.ts b/src/dso.ts index ec98df9..cc2ad0e 100644 --- a/src/dso.ts +++ b/src/dso.ts @@ -41,7 +41,7 @@ export const dso = { /** * Sync model to database table * @param force set true, will drop table before create table - */ + async sync(force: boolean = false): Promise { if (_configClientReturn["mysql"]) { for (const model of _models) { @@ -56,7 +56,7 @@ export const dso = { await sync(_clientSqlite, model, force); } } - }, + },*/ /** * MySQL Database client @@ -86,12 +86,7 @@ export const dso = { return _configClientReturn; }, - /** - * all models - */ - get models() { - return _models; - }, + /** * add model @@ -106,7 +101,7 @@ export const dso = { /* * Transaction object selected for each driver */ - transaction: Transaction.transaction, + //transaction: Transaction.transaction, /** * connect to database mysql | postgres | Sqlite diff --git a/src/model.ts b/src/model.ts index 0917d51..bbc1235 100644 --- a/src/model.ts +++ b/src/model.ts @@ -1,11 +1,10 @@ import { assert, Join, Order, Query, Where } from "../deps.ts"; -import { dso } from "./dso.ts"; import { Defaults, FieldOptions, FieldType } from "./field.ts"; import { Index, IndexType } from "./index.ts"; import { replaceBackTick, rowsPostgres } from "../util.ts"; -import { PostgresClient } from "../PostgresClient.ts"; -import { SqliteClient } from "../SqliteClient.ts"; import { Connection } from "../deps.ts"; +import { dso } from "./dso.ts"; +import { MysqlClient } from "./MysqlClient.ts"; export interface QueryOptions { fields?: string[]; @@ -35,7 +34,7 @@ export class BaseModel { created_at?: Date; updated_at?: Date; - constructor(public connection?: Connection | PostgresClient | SqliteClient) {} + constructor(public connection?: Connection| MysqlClient) {} /** get model name */ get modelName(): string { @@ -169,19 +168,11 @@ export class BaseModel { let resultSqlite: any; let resultPostgres: any; let converted: ModelFields | undefined; - if (dso.configClientReturn.sqlite != null) { - resultSqlite = await this.querySqlite(this.optionsToQuery(options)); - const resultArray = [...resultSqlite]; - converted = this.convertModel(resultArray[0]); - } else if (dso.configClientReturn.postgres != null) { - resultPostgres = await this.queryPostgres(this.optionsToQuery(options)); - - converted = this.convertModel(rowsPostgres(resultPostgres)[0]); - } else { + result = await this.query(this.optionsToQuery(options).limit(0, 1)); converted = this.convertModel(result[0]); - } + return converted; } @@ -196,16 +187,10 @@ export class BaseModel { let deleteCounts: number | undefined; let resultPostgres: any; - if (dso.configClientReturn.sqlite != null) { - await this.executeQuerySqlite(query); - deleteCounts = dso.clientSqlite.changes; - } else if (dso.configClientReturn.postgres != null) { - resultPostgres = await this.executeQueryPostGres(query); - deleteCounts = parseInt(resultPostgres.rowCount); - } else { + result = await this.execute(query); deleteCounts = result.affectedRows; - } + return deleteCounts ?? 0; } @@ -234,13 +219,10 @@ export class BaseModel { let result: any; let idReturn: number; - if (dso.configClientReturn.sqlite != null) { - await this.executeQuerySqlite(query); - idReturn = dso.clientSqlite.lastInsertRowId; - } else { + result = await this.execute(query); idReturn = result.lastInsertId; - } + return idReturn; } @@ -254,16 +236,9 @@ export class BaseModel { let updateCounts; - if (dso.configClientReturn.sqlite != null) { - await this.executeQuerySqlite(query); - updateCounts = dso.clientSqlite.changes; - } else if (dso.configClientReturn.postgres != null) { - resultPostgres = await this.executeQueryPostGres(query); - updateCounts = parseInt(resultPostgres.rowCount); - } else { result = await this.execute(query); updateCounts = result.affectedRows; - } + return updateCounts; } @@ -291,16 +266,10 @@ export class BaseModel { let updateCounts; - if (dso.configClientReturn.sqlite != null) { - await this.executeQuerySqlite(query); - updateCounts = dso.clientSqlite.changes; - } else if (dso.configClientReturn.postgres != null) { - resultPostgres = await this.executeQueryPostGres(query); - updateCounts = parseInt(resultPostgres.rowCount); - } else { + result = await this.execute(query); updateCounts = result.affectedRows; - } + return updateCounts; } @@ -312,10 +281,8 @@ export class BaseModel { async query(query: Query): Promise { const sql = query.build(); dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); - const result = this.connection - ? await this.connection.query(sql) - : await dso.client.query(sql); - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + const result = await this.connection?.query(sql); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; } @@ -327,9 +294,8 @@ export class BaseModel { const sql = query.build(); console.log(replaceBackTick(sql)); dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); - const result: any = this.connection - ? await this.connection.query(replaceBackTick(sql)) - : await dso.clientSqlite.query(replaceBackTick(sql)).asObjects(); + const result: any = await this.connection?.query(replaceBackTick(sql)); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; } @@ -341,9 +307,8 @@ export class BaseModel { async queryPostgres(query: Query): Promise { const sql = query.build(); dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); - const result: any = this.connection - ? await this.connection.query(replaceBackTick(sql)) - : await dso.clientPostgres.query(replaceBackTick(sql)); + const result: any = await this.connection?.query(replaceBackTick(sql)) + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; } @@ -355,9 +320,9 @@ export class BaseModel { async execute(query: Query) { const sql = query.build(); dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - const result = this.connection - ? await this.connection.execute(sql) - : await dso.client.execute(sql); + const result = await this.connection?.execute(sql) + + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; @@ -372,9 +337,8 @@ export class BaseModel { dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - const result = this.connection - ? await this.connection.query(replaceBackTick(sql)) - : await dso.clientPostgres.query(replaceBackTick(sql)); + const result = await this.connection?.query(replaceBackTick(sql)) + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; @@ -389,9 +353,8 @@ export class BaseModel { console.log(replaceBackTick(sql)); dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - const result = this.connection - ? await this.connection.query(replaceBackTick(sql)) - : await dso.clientSqlite.query(replaceBackTick(sql)); + const result = await this.connection?.query(replaceBackTick(sql)) + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; diff --git a/src/model_2.ts b/src/model_2.ts new file mode 100644 index 0000000..c8d1f0a --- /dev/null +++ b/src/model_2.ts @@ -0,0 +1,401 @@ +import { assert, Join, Order, Query, Where } from "../deps.ts"; +import { dso } from "./dso.ts"; +import { Defaults, FieldOptions, FieldType } from "./field.ts"; +import { Index, IndexType } from "./index.ts"; +import { replaceBackTick, rowsPostgres } from "../util.ts"; +import { PostgresClient } from "../PostgresClient.ts"; +import { SqliteClient } from "../SqliteClient.ts"; +import { Connection } from "../deps.ts"; +import { MysqlClient } from "./MysqlClient.ts"; + +export interface QueryOptions { + fields?: string[]; + where?: Where; + order?: Order[]; + group?: string[]; + join?: Join[]; + limit?: [number, number]; + having?: Where; +} + +/** Model Decorator */ +export function Model(name: string) { + return (target: { new (): T }) => { + Reflect.defineMetadata("model:name", name, target.prototype); + }; +} + +/** Model Fields list */ +export type ModelFields = Partial> & { + created_at?: Date; + updated_at?: Date; +}; + +/** Model base class */ +export class BaseModel { + created_at?: Date; + updated_at?: Date; + + constructor(public connection?: Connection | MysqlClient| PostgresClient | SqliteClient) {} + + /** get model name */ + get modelName(): string { + return Reflect.getMetadata("model:name", this); + } + + /** get primary key */ + get primaryKey(): FieldOptions | undefined { + return this.modelFields.find((field) => field.primary); + } + + /** Returns array of all available without primary key */ + get columnIndexes(): { [key: number]: Array } { + return { + [IndexType.INDEX]: this.modelFields.filter((field) => field.index) || [], + [IndexType.UNIQUE]: this.modelFields.filter((field) => field.unique) || + [], + [IndexType.SPATIAL]: this.modelFields.filter((field) => field.spatial) || + [], + [IndexType.FULLTEXT]: + this.modelFields.filter((field) => field.fullText) || [], + }; + } + + /** get defined fields list */ + get modelFields(): FieldOptions[] { + return ( + Reflect.getMetadata("model:fields", this) || [ + { + type: FieldType.DATE, + default: Defaults.CURRENT_TIMESTAMP, + autoUpdate: true, + name: "updated_at", + property: "updated_at", + }, + { + type: FieldType.DATE, + default: Defaults.CURRENT_TIMESTAMP, + name: "created_at", + property: "created_at", + }, + ] + ); + } + + /** get defined index list */ + get indexes(): Index[] { + return Reflect.getMetadata("model:indexes", this) || []; + } + + /** return a new Query instance with table name */ + builder(): Query { + const builder = new Query(); + return builder.table(this.modelName); + } + + /** + * Convert data object to model + * @param data + */ + private convertModel(data: { + [key: string]: any; + }): ModelFields | undefined { + if (!data) return; + const model: any = {}; + const fieldsMapping: any = {}; + this.modelFields.map( + (field) => (fieldsMapping[field.name] = field.property), + ); + this.indexes.map( + (index) => { + if (index.property) model[index.property] = index; + }, + ); + Object.keys(data).forEach((key) => { + const propertyName = fieldsMapping[key]; + model[propertyName || key] = data[key]; + }); + return model; + } + + /** + * Convert model object to db object + * @param model + */ + private convertObject(model: ModelFields): { [key: string]: any } { + const data: any = {}; + const fieldsMapping: any = {}; + this.modelFields.map( + (field) => (fieldsMapping[field.property!] = field.name), + ); + Object.keys(model).forEach((key) => { + const name = fieldsMapping[key]; + data[name || key] = model[key as keyof ModelFields]; + }); + return data; + } + + private optionsToQuery(options: QueryOptions) { + const query = this.builder(); + if (options.fields) { + query.select(...options.fields); + } else { + query.select(`${this.modelName}.*`); + } + + if (options.where) query.where(options.where); + if (options.group) query.groupBy(...options.group); + if (options.having) query.having(options.having); + if (options.join) { + options.join.forEach((join) => query.join(join)); + } + if (options.limit) query.limit(...options.limit); + if (options.order) options.order.forEach((order) => query.order(order)); + return query; + } + + /** + * find one record + * @param where conditions + */ + async findOne( + options: Where | QueryOptions, + ): Promise | undefined> { + if (options instanceof Where) { + options = { + where: options, + }; + } + let result; + let resultSqlite: any; + let resultPostgres: any; + let converted: ModelFields | undefined; + if (dso.configClientReturn.sqlite != null) { + resultSqlite = await this.querySqlite(this.optionsToQuery(options)); + const resultArray = [...resultSqlite]; + converted = this.convertModel(resultArray[0]); + } else if (dso.configClientReturn.postgres != null) { + resultPostgres = await this.queryPostgres(this.optionsToQuery(options)); + + converted = this.convertModel(rowsPostgres(resultPostgres)[0]); + } else {*/ + result = await this.query(this.optionsToQuery(options).limit(0, 1)); + + converted = this.convertModel(result[0]); + } + + return converted; + } + + /** + * delete by conditions + * @param where + */ + async delete(where: Where): Promise { + const query = this.builder().delete().where(where); + let result: any; + let deleteCounts: number | undefined; + let resultPostgres: any; + + if (dso.configClientReturn.sqlite != null) { + await this.executeQuerySqlite(query); + deleteCounts = dso.clientSqlite.changes; + } else if (dso.configClientReturn.postgres != null) { + resultPostgres = await this.executeQueryPostGres(query); + deleteCounts = parseInt(resultPostgres.rowCount); + } else { + result = await this.execute(query); + deleteCounts = result.affectedRows; + } + + return deleteCounts ?? 0; + } + + /** find all records by given conditions */ + async findAll(options: Where | QueryOptions): Promise[]> { + if (options instanceof Where) { + options = { + where: options, + }; + } + const result = await this.query(this.optionsToQuery(options)); + return result.map((record) => this.convertModel(record)!); + } + + /** find one record by primary key */ + async findById(id: string | number): Promise | undefined> { + assert(!!this.primaryKey); + return await this.findOne(Where.field(this.primaryKey.name).eq(id)); + } + + /** insert record */ + async insert(fields: Partial): Promise { + const query = this.builder().insert(this.convertObject(fields)); + + let result: any; + let idReturn: number; + + // if (dso.configClientReturn.sqlite != null) { + //await this.executeQuerySqlite(query); + //idReturn = dso.clientSqlite.lastInsertRowId; + //} else { + result = await this.execute(query); + idReturn = result.lastInsertId; + //} + + return idReturn; + } + + /** insert record */ + async insertRowsAffected(fields: Partial): Promise { + const query = this.builder().insert(this.convertObject(fields)); + + let resultPostgres: any; + let result: any; + + let updateCounts; + + if (dso.configClientReturn.sqlite != null) { + await this.executeQuerySqlite(query); + updateCounts = dso.clientSqlite.changes; + } else if (dso.configClientReturn.postgres != null) { + resultPostgres = await this.executeQueryPostGres(query); + updateCounts = parseInt(resultPostgres.rowCount); + } else { + result = await this.execute(query); + updateCounts = result.affectedRows; + } + + return updateCounts; + } + + /** update records by given conditions */ + async update( + data: Partial, + where?: Where, + ): Promise { + if ( + !where && + this.primaryKey && + data[this.primaryKey.property as keyof this] + ) { + where = Where.field(this.primaryKey.name).eq( + data[this.primaryKey.property as keyof this], + ); + } + const query = this.builder() + .update(this.convertObject(data)) + .where(where ?? ""); + + let result: any; + let resultPostgres: any; + + let updateCounts; + + if (dso.configClientReturn.sqlite != null) { + await this.executeQuerySqlite(query); + updateCounts = dso.clientSqlite.changes; + } else if (dso.configClientReturn.postgres != null) { + resultPostgres = await this.executeQueryPostGres(query); + updateCounts = parseInt(resultPostgres.rowCount); + } else { + result = await this.execute(query); + updateCounts = result.affectedRows; + } + + return updateCounts; + } + + /** + * query custom + * @param query + */ + async query(query: Query): Promise { + const sql = query.build(); + dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); + const result = this.connection + ? await this.connection.query(sql) + : await dso.client.query(sql); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + + /** + * query custom + * @param query + */ + async querySqlite(query: Query): Promise { + const sql = query.build(); + console.log(replaceBackTick(sql)); + dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); + const result: any = this.connection + ? await this.connection.query(replaceBackTick(sql)) + : await dso.clientSqlite.query(replaceBackTick(sql)).asObjects(); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + + /** + * query custom + * @param query + */ + async queryPostgres(query: Query): Promise { + const sql = query.build(); + dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); + const result: any = this.connection + ? await this.connection.query(replaceBackTick(sql)) + : await dso.clientPostgres.query(replaceBackTick(sql)); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + + /** + * excute custom + * @param query + */ + async execute(query: Query) { + const sql = query.build(); + dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); + const result = this.connection + ? await this.connection.execute(sql) + : await dso.client.execute(sql); + + + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + + /** + * excute custom + * @param query + */ + async executeQueryPostGres(query: Query) { + const sql = query.build(); + + dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); + + const result = this.connection + ? await this.connection.query(replaceBackTick(sql)) + : await dso.clientPostgres.query(replaceBackTick(sql)); + + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + + /** + * excute custom + * @param query + */ + async executeQuerySqlite(query: Query) { + const sql = query.build(); + console.log(replaceBackTick(sql)); + dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); + + const result = this.connection + ? await this.connection.query(replaceBackTick(sql)) + : await dso.clientSqlite.query(replaceBackTick(sql)); + + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } +} diff --git a/src/sync.ts b/src/sync.ts index 10d2298..a2eac10 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -6,14 +6,15 @@ import { columnIndexesList, Index } from "./index.ts"; import { replaceBackTick } from "../util.ts"; import { SqliteClient } from "../SqliteClient.ts"; import { PostgresClient } from "../PostgresClient.ts"; +import { MysqlClient } from "./MysqlClient.ts"; export async function sync( - client: Client | PostgresClient | SqliteClient, + client: MysqlClient | PostgresClient | SqliteClient, model: BaseModel, force: boolean, ) { if (force) { - if (client instanceof Client) { + if (client instanceof MysqlClient) { await client.execute(`DROP TABLE IF EXISTS ${model.modelName}`); } else { await client.query(`DROP TABLE IF EXISTS ${model.modelName}`); @@ -29,7 +30,7 @@ export async function sync( type = `VARCHAR(${field.length || 255})`; break; case FieldType.INT: - if (client instanceof Client) { + if (client instanceof MysqlClient) { type = `INT(${field.length || 11})`; } else if (client instanceof SqliteClient) { type = `INTEGER `; @@ -69,7 +70,7 @@ export async function sync( def += ` DEFAULT ${field.default}`; } } - if (client instanceof Client) { + if (client instanceof MysqlClient) { if (field.autoIncrement) def += " AUTO_INCREMENT"; } @@ -108,7 +109,7 @@ export async function sync( let sql; - if (client instanceof Client) { + if (client instanceof MysqlClient) { sql = [ "CREATE TABLE IF NOT EXISTS", model.modelName, @@ -125,17 +126,19 @@ export async function sync( defs, ");", ].join(" "); - console.log(sql); + // console.log(sql); } - console.log(sql); - - dso.showQueryLog && console.log(`\n[ DSO:SYNC ]\nSQL:\t ${sql}\n`); + // console.log(sql); + if (client instanceof MysqlClient) { + MysqlClient.showQueryLog && console.log(`\n[ DSO:SYNC ]\nSQL:\t ${sql}\n`); + } let result; - if (client instanceof Client) { - result = await client.execute(sql); + if (client instanceof MysqlClient) { + result = await client.query(sql); } else { result = await client.query(replaceBackTick(sql)); } - - dso.showQueryLog && console.log(`REUSLT:\t`, result, `\n`); + if (client instanceof MysqlClient) { + MysqlClient.showQueryLog && console.log(`REUSLT:\t`, result, `\n`); + } } diff --git a/src/transaction.ts b/src/transaction.ts index a4c77fd..198ce8c 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -1,11 +1,13 @@ import { Connection } from "../deps.ts"; -import { dso } from "./dso.ts"; import { BaseModel } from "./model.ts"; import { PostgresClient } from "../PostgresClient.ts"; import { SqliteClient } from "../SqliteClient.ts"; +import { dso } from "./dso.ts"; +import { MysqlClient } from "./MysqlClient.ts"; export class Transaction { constructor(private _conn: Connection | PostgresClient | SqliteClient) {} + getModel( Model: { new (conn: Connection | PostgresClient | SqliteClient): T }, ): T { @@ -13,13 +15,13 @@ export class Transaction { return model; } - static async transaction( + /**static async transaction( processor: (transaction: Transaction) => Promise, driverType: string, ): Promise { if (driverType.toUpperCase() == "MYSQL") { return ( - await dso.client.transaction(async (conn) => { + await new MysqlClient().client.transaction(async (conn) => { const trans = new Transaction(conn); return await processor(trans); }) @@ -39,5 +41,5 @@ export class Transaction { }) ) as T; } - } + }*/ } diff --git a/src/transactionMysql.ts b/src/transactionMysql.ts new file mode 100644 index 0000000..e39f23d --- /dev/null +++ b/src/transactionMysql.ts @@ -0,0 +1,27 @@ +import { Connection } from "../deps.ts"; +import { BaseModel } from "./model.ts"; +import { MysqlClient } from "./MysqlClient.ts"; + +export class TransactionMysql { + constructor(private _conn: Connection) { } + + getModel( + Model: { new(conn: Connection): T }, + ): T { + const model = new Model(this._conn); + return model; + } + + static async transaction( + processor: (transaction: TransactionMysql) => Promise, + client: MysqlClient + ): Promise { + + return ( + await client.client.transaction(async (conn) => { + const trans = new TransactionMysql(conn); + return await processor(trans); + }) + ) as T; + } +} diff --git a/test.ts b/test.ts index 6702729..d179950 100644 --- a/test.ts +++ b/test.ts @@ -1,47 +1,44 @@ -import { Client } from "./deps.ts"; import { dso } from "./mod.ts"; import "./test/model.ts"; import { ClientConfig } from "./deps.ts"; +import { MysqlClient } from "./src/MysqlClient.ts"; const config: ClientConfig = { hostname: "127.0.0.1", port: 3306, - poolSize: 3, - debug: false, + poolSize: 6, + debug: true, username: "root", password: "", - db: "", + db: "test1_orm", }; -const client = new Client(); -dso.showQueryLog = false; -const mysqlConfig = { - type: "MYSQL", - clientConfig: { ...config, db: "test_orm" }, -}; - -export async function clientTest(fn: Function) { +export async function clientTest(fn: Function, clientiele: MysqlClient) { Deno.test({ name: fn.name, fn: async () => { - await dso.connect(mysqlConfig); - await dso.sync(true); + await clientiele.connect(config); + await clientiele.sync(true); await fn(); - dso.close(); + clientiele.close(); }, }); } +const client: MysqlClient = new MysqlClient(); async function main() { + await client.connect(config); - await client.execute(`CREATE DATABASE IF NOT EXISTS test_orm`); - await client.execute(`USE test_orm`); + await client.query(`CREATE DATABASE IF NOT EXISTS test1_orm`); + await client.query(`USE test1_orm`); await client.close(); + } await main(); +/*** const config2 = { user: "postgres", database: "test_orm", @@ -83,4 +80,4 @@ export async function clientTestSQLITE(fn: Function) { dso.close(); }, }); -} +}**/ diff --git a/test/model.ts b/test/model.ts index 1b9908d..9ad4f2b 100644 --- a/test/model.ts +++ b/test/model.ts @@ -1,7 +1,8 @@ +import { ClientConfig } from "../deps.ts"; import { assert, assertEquals, assertThrowsAsync } from "../deps.ts"; import { BaseModel, - dso, + MysqlClient, Field, FieldType, Join, @@ -9,7 +10,8 @@ import { Query, Where, } from "../mod.ts"; -import { clientTest, clientTestPostgres, clientTestSQLITE } from "../test.ts"; +import { dso } from "../src/dso.ts"; +import { clientTest} from "../test.ts"; /** deno test --allow-net --allow-read --allow-write -c tsconfig.json */ @@ -51,9 +53,10 @@ class TopicModel extends BaseModel { @Field({ type: FieldType.STRING }) title?: string; } - -let userModel = dso.define(UserModel); -let topicModel = dso.define(TopicModel); +let client: MysqlClient = new MysqlClient(); +dso.showQueryLog = true; +let userModel = client.define(UserModel); +let topicModel = client.define(TopicModel); clientTest(async function testInsert() { assertEquals( @@ -80,7 +83,7 @@ clientTest(async function testInsert() { }), 3, ); -}); +}, client); clientTest(async function testInsertRowsAffected() { assertEquals( @@ -107,7 +110,7 @@ clientTest(async function testInsertRowsAffected() { }), 1, ); -}); +}, client); clientTest(async function testUpdate() { const id: number | undefined = await userModel.insert( @@ -130,7 +133,7 @@ clientTest(async function testUpdate() { password: "BAR", phoneNumber: "08135539123", }); -}); +}, client); clientTest(async function testFindOneByWhere() { await userModel.insert({ nickName: "foo", phoneNumber: "08135539123" }); @@ -160,7 +163,7 @@ clientTest(async function testFindOneByWhere() { title: "foo", userId: 1, }); -}); +}, client); clientTest(async function testDelete() { await userModel.insert({ nickName: "foo" }); @@ -171,7 +174,7 @@ clientTest(async function testDelete() { ); assertEquals(count, 2); -}); +}, client); clientTest(async function testFindOneByOptions() { await userModel.insert({ nickName: "foo" }); @@ -198,13 +201,17 @@ clientTest(async function testFindOneByOptions() { updated_at: topic?.updated_at, created_at: topic?.created_at, }); -}); +}, client); + + + + clientTest(async function testTransactionFail() { let userId: number | undefined; let topicId: number | undefined; await assertThrowsAsync(async () => { - await dso.transaction(async (trans) => { + await client.transaction(async (trans) => { const userModel = trans.getModel(UserModel); const topicModel = trans.getModel(TopicModel); userId = await userModel.insert({ nickName: "foo", password: "bar" }); @@ -213,18 +220,18 @@ clientTest(async function testTransactionFail() { assert(!!user); await userModel.query(new Query().table("notexists").select("*")); return true; - }, "MYSQL"); + }, client); }); const user = await userModel.findById(userId!); const topic = await topicModel.findById(topicId!); assert(!user); assert(!topic); -}); +}, client); clientTest(async function testTransactionSuccess() { let topicId: number | undefined; let userId: number | undefined; - const result = await dso.transaction(async (trans) => { + const result = await client.transaction(async (trans) => { const userModel = trans.getModel(UserModel); const topicModel = trans.getModel(TopicModel); userId = await userModel.insert({ nickName: "foo", password: "bar" }); @@ -232,14 +239,16 @@ clientTest(async function testTransactionSuccess() { let user = await userModel.findById(userId!); assert(!!user); return true; - }, "Mysql"); + }, client); const user = await userModel.findById(userId!); const topic = await userModel.findById(topicId!); assertEquals(result, true); assert(!!topic); assert(!!user); -}); +}, client); + +/** userModel = dso.define(UserModel); topicModel = dso.define(TopicModel); @@ -597,4 +606,4 @@ clientTestPostgres(async function testTransactionSuccess() { assertEquals(result, true); assert(!!topic); assert(!!user); -}); +}); */ \ No newline at end of file From 31e8dccf4dce90be3a07f19e226602607ddb22ef Mon Sep 17 00:00:00 2001 From: tksilicon Date: Wed, 4 Nov 2020 18:36:23 +0100 Subject: [PATCH 18/24] feat:databaseDriverAdaptations - MySQL --- mod.ts | 2 +- src/DsoClient.ts | 86 ++++----- src/MysqlClient.ts | 131 ++++++------- src/drivers/base.ts | 3 - src/drivers/mysql.ts | 7 - src/drivers/postgres.ts | 7 - src/drivers/sqlite.ts | 7 - src/dso.ts | 142 -------------- src/model.ts | 109 ++--------- src/model_2.ts | 401 ---------------------------------------- src/sync.ts | 13 +- src/transaction.ts | 45 ----- src/transactionMysql.ts | 9 +- test.ts | 53 +----- test/model.ts | 375 +------------------------------------ 15 files changed, 124 insertions(+), 1266 deletions(-) delete mode 100644 src/drivers/base.ts delete mode 100644 src/drivers/mysql.ts delete mode 100644 src/drivers/postgres.ts delete mode 100644 src/drivers/sqlite.ts delete mode 100644 src/model_2.ts delete mode 100644 src/transaction.ts diff --git a/mod.ts b/mod.ts index 9fbc2ad..050ee84 100644 --- a/mod.ts +++ b/mod.ts @@ -15,7 +15,7 @@ export { SqliteClient, } from "./SqliteClient.ts"; export { dso } from "./src/dso.ts"; -export { MysqlClient} from "./src/MysqlClient.ts"; +export { MysqlClient } from "./src/MysqlClient.ts"; export * from "./src/field.ts"; export * from "./src/index.ts"; export * from "./src/model.ts"; diff --git a/src/DsoClient.ts b/src/DsoClient.ts index dd75f48..d25b8b6 100644 --- a/src/DsoClient.ts +++ b/src/DsoClient.ts @@ -1,67 +1,57 @@ -import { Connection } from "../deps.ts"; import { MysqlClient } from "./MysqlClient.ts"; export enum ClientType { - MYSQL = "MYSQL", - POSTGRESS = "POSTGRESS", - SQLITE = "SQLITE", - } - - export interface DsoConnection { - query(sql: string, params?: any[]): Promise; - execute(sql: string, params?: any[]): Promise; - } - - /** Transaction processor */ - export interface TransactionProcessor { - (connection: MysqlClient): Promise; - } - - export interface PoolInfo { - size: number| undefined; - maxSize: number |undefined; - available: number | undefined; - } - - /** + MYSQL = "MYSQL", + POSTGRESS = "POSTGRESS", + SQLITE = "SQLITE", +} + +export interface DsoConnection { + query(sql: string, params?: any[]): Promise; + execute(sql: string, params?: any[]): Promise; +} + +/** Transaction processor */ +export interface TransactionProcessor { + (connection: MysqlClient): Promise; +} + +export interface PoolInfo { + size: number | undefined; + maxSize: number | undefined; + available: number | undefined; +} + +/** * DSO client */ - export abstract class DsoClient { - /** get pool info */ - abstract get pool(): PoolInfo| undefined; - - /** +export abstract class DsoClient { + /** get pool info */ + abstract get pool(): PoolInfo | undefined; + + /** * connect to database * @param config config for client * @returns Clinet instance */ - abstract connect(config: T): Promise; - - /** + abstract connect(config: T): Promise; + + /** * excute query sql * @param sql query sql string * @param params query params */ - abstract async query(sql: string, params?: any[]): Promise; - - /** + abstract async query(sql: string, params?: any[]): Promise; + + /** * excute sql * @param sql sql string * @param params query params */ - abstract async execute(sql: string, params?: any[]): Promise; - - /** - * Execute a transaction process, and the transaction successfully - * returns the return value of the transaction process - * @param processor transation processor - - abstract async transaction( - processor: TransactionProcessor - ): Promise; - - /** + abstract async execute(sql: string, params?: any[]): Promise; + + /** * close connection */ - abstract async close(): Promise; - } \ No newline at end of file + abstract async close(): Promise; +} diff --git a/src/MysqlClient.ts b/src/MysqlClient.ts index 1ed3455..007bfac 100644 --- a/src/MysqlClient.ts +++ b/src/MysqlClient.ts @@ -1,103 +1,82 @@ import { Connection } from "../deps.ts"; import { Client } from "../deps.ts"; -import { TransactionProcessor } from "./DsoClient.ts"; - import { DsoClient, PoolInfo } from "./DsoClient.ts"; import { BaseModel } from "./model.ts"; import { sync } from "./sync.ts"; import { TransactionMysql } from "./transactionMysql.ts"; - export class MysqlClient extends DsoClient { + #client: Client = new Client(); - #client: Client = new Client(); + /** @ignore */ + #models: BaseModel[] = []; - /** @ignore */ - #models: BaseModel[] = []; - - - /** - * @param showQuerylog - * set true will show execute/query sql - * + /** + * transaction processor */ - constructor(private _conn?: Connection) { - super(); - - } - + transaction = TransactionMysql.transaction; + constructor(private _conn?: Connection) { + super(); + } - /** + /** * Sync model to database table * @param force set true, will drop table before create table */ - async sync(force: boolean = false): Promise { - - for (const model of this.#models) { - await sync(this, model, force); - } - + async sync(force: boolean = false): Promise { + for (const model of this.#models) { + await sync(this, model, force); } + } - /** + /** * MySQL Database client */ - get client(): Client { - return this.#client; - } + get client(): Client { + return this.#client; + } - /** + /** * all models */ - get models() { - return this.#models; - } + get models() { + return this.#models; + } - /** + /** * add model * @param model */ - define(ModelClass: { new(connection: Connection| MysqlClient): T }): T { - const model = new ModelClass(this); - this.#models.push(model); - return model; - } - - async close(): Promise { - this.#client.close(); - } - - async connect(config: ClientConfig): Promise { - await this.#client.connect(config); - return this; - } - - - get pool(): PoolInfo { - - const poolInfo = { - size: this.#client.pool?.size , - maxSize: this.#client.pool?.maxSize , - available: this.#client.pool?.available, - - } - return poolInfo; - } - async query(sql: string, params?: any[]): Promise { - return await this.#client.query(sql, params); - - } - async execute(sql: string, params?: any[]): Promise { - - return await this.#client.execute(sql, params); - - } - transaction = TransactionMysql.transaction; - - static showQueryLog: boolean = false; - - - - -} \ No newline at end of file + define( + ModelClass: { new (connection: Connection | MysqlClient): T }, + ): T { + const model = new ModelClass(this); + this.#models.push(model); + return model; + } + + async close(): Promise { + this.#client.close(); + } + + async connect(config: ClientConfig): Promise { + await this.#client.connect(config); + return this; + } + + get pool(): PoolInfo { + const poolInfo = { + size: this.#client.pool?.size, + maxSize: this.#client.pool?.maxSize, + available: this.#client.pool?.available, + }; + return poolInfo; + } + async query(sql: string, params?: any[]): Promise { + return await this.#client.query(sql, params); + } + async execute(sql: string, params?: any[]): Promise { + return await this.#client.execute(sql, params); + } +} diff --git a/src/drivers/base.ts b/src/drivers/base.ts deleted file mode 100644 index 486bf8b..0000000 --- a/src/drivers/base.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface Base { - type: string; // MySQl|Postgres|Sqlite -} diff --git a/src/drivers/mysql.ts b/src/drivers/mysql.ts deleted file mode 100644 index 82e720e..0000000 --- a/src/drivers/mysql.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Base } from "./base.ts"; -import { ClientConfig, Client } from "../../deps.ts"; - -export interface MysqlConfig extends Base { - clientConfig: ClientConfig; - client?: Client; -} diff --git a/src/drivers/postgres.ts b/src/drivers/postgres.ts deleted file mode 100644 index c9706e9..0000000 --- a/src/drivers/postgres.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Base } from "./base.ts"; -import { PostgresClient } from "../../PostgresClient.ts"; - -export interface PostgresConfig extends Base { - clientConfig: object; - client?: PostgresClient; -} diff --git a/src/drivers/sqlite.ts b/src/drivers/sqlite.ts deleted file mode 100644 index 572103b..0000000 --- a/src/drivers/sqlite.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Base } from "./base.ts"; -import { SqliteClient } from "../../SqliteClient.ts"; - -export interface SqliteConfig extends Base { - clientConfig: object; - client?: SqliteClient; -} diff --git a/src/dso.ts b/src/dso.ts index cc2ad0e..aaa52c8 100644 --- a/src/dso.ts +++ b/src/dso.ts @@ -1,34 +1,3 @@ -import { Client } from "../deps.ts"; -import { BaseModel } from "./model.ts"; -import { sync } from "./sync.ts"; -import { Transaction } from "./transaction.ts"; -import { PostgresConfig } from "./drivers/postgres.ts"; -import { MysqlConfig } from "./drivers/mysql.ts"; -import { SqliteConfig } from "./drivers/sqlite.ts"; -import { SqliteClient } from "../SqliteClient.ts"; -import { PostgresClient } from "../PostgresClient.ts"; - -/** @ignore */ -let _client: Client; - -/** @ignore */ -let _clientPostgres: PostgresClient; - -/** @ignore */ -let _clientSqlite: SqliteClient; - -/** @ignore */ -let _models: BaseModel[] = []; - -/** @ignore */ -export type _ClientType = { - mysql?: Client; - postgres?: PostgresClient; - sqlite?: SqliteClient; -}; -/** @ignore */ -let _configClientReturn: _ClientType; - /** * Global dso instance */ @@ -37,115 +6,4 @@ export const dso = { * set true will show execute/query sql */ showQueryLog: false, - - /** - * Sync model to database table - * @param force set true, will drop table before create table - - async sync(force: boolean = false): Promise { - if (_configClientReturn["mysql"]) { - for (const model of _models) { - await sync(_client, model, force); - } - } else if (_configClientReturn["postgres"]) { - for (const model of _models) { - await sync(_clientPostgres, model, force); - } - } else if (_configClientReturn["sqlite"]) { - for (const model of _models) { - await sync(_clientSqlite, model, force); - } - } - },*/ - - /** - * MySQL Database client - */ - get client(): Client { - return _client; - }, - - /** - * Postgres Database client - */ - get clientPostgres(): PostgresClient { - return _clientPostgres; - }, - - /** - * Sqlite Database client - */ - get clientSqlite(): SqliteClient { - return _clientSqlite; - }, - - /** - * Current driver client - */ - get configClientReturn(): _ClientType { - return _configClientReturn; - }, - - - - /** - * add model - * @param model - */ - define(ModelClass: { new (): T }): T { - const model = new ModelClass(); - _models.push(model); - return model; - }, - - /* - * Transaction object selected for each driver - */ - //transaction: Transaction.transaction, - - /** - * connect to database mysql | postgres | Sqlite - * @param config client config - */ - async connect( - config: T, - ): Promise<_ClientType | undefined> { - if (config["type"].toUpperCase() === "POSTGRES") { - _clientPostgres = new PostgresClient(config["clientConfig"]); - await _clientPostgres.connect(); - - return _configClientReturn = { - postgres: _clientPostgres, - }; - } else if (config["type"].toUpperCase() === "MYSQL") { - if (config["client"] && config["client"] instanceof Client) { - _client = config["client"]; - } else { - _client = new Client(); - await _client.connect(config["clientConfig"]); - } - return _configClientReturn = { - mysql: _client, - }; - } else if (config["type"].toUpperCase() === "SQLITE") { - const configgy: any = config["clientConfig"]; - - _clientSqlite = new SqliteClient(configgy["database"]); - - return _configClientReturn = { - sqlite: _clientSqlite, - }; - } - return undefined; - }, - - close(): void { - if (_configClientReturn["mysql"]) { - _client.close(); - } else if (_configClientReturn["postgres"]) { - _clientPostgres.end(); - } else if (_configClientReturn["sqlite"]) { - _clientSqlite.close(); - } - }, }; diff --git a/src/model.ts b/src/model.ts index bbc1235..1ffe711 100644 --- a/src/model.ts +++ b/src/model.ts @@ -1,7 +1,6 @@ import { assert, Join, Order, Query, Where } from "../deps.ts"; import { Defaults, FieldOptions, FieldType } from "./field.ts"; import { Index, IndexType } from "./index.ts"; -import { replaceBackTick, rowsPostgres } from "../util.ts"; import { Connection } from "../deps.ts"; import { dso } from "./dso.ts"; import { MysqlClient } from "./MysqlClient.ts"; @@ -34,7 +33,7 @@ export class BaseModel { created_at?: Date; updated_at?: Date; - constructor(public connection?: Connection| MysqlClient) {} + constructor(public connection?: Connection | MysqlClient) {} /** get model name */ get modelName(): string { @@ -168,11 +167,10 @@ export class BaseModel { let resultSqlite: any; let resultPostgres: any; let converted: ModelFields | undefined; - - result = await this.query(this.optionsToQuery(options).limit(0, 1)); - converted = this.convertModel(result[0]); - + result = await this.query(this.optionsToQuery(options).limit(0, 1)); + + converted = this.convertModel(result[0]); return converted; } @@ -187,10 +185,8 @@ export class BaseModel { let deleteCounts: number | undefined; let resultPostgres: any; - - result = await this.execute(query); - deleteCounts = result.affectedRows; - + result = await this.execute(query); + deleteCounts = result.affectedRows; return deleteCounts ?? 0; } @@ -216,13 +212,8 @@ export class BaseModel { async insert(fields: Partial): Promise { const query = this.builder().insert(this.convertObject(fields)); - let result: any; - let idReturn: number; - - - result = await this.execute(query); - idReturn = result.lastInsertId; - + let result: any = await this.execute(query); + let idReturn: number = result.lastInsertId; return idReturn; } @@ -231,14 +222,8 @@ export class BaseModel { async insertRowsAffected(fields: Partial): Promise { const query = this.builder().insert(this.convertObject(fields)); - let resultPostgres: any; - let result: any; - - let updateCounts; - - result = await this.execute(query); - updateCounts = result.affectedRows; - + let result: any = await this.execute(query); + let updateCounts = result.affectedRows; return updateCounts; } @@ -261,15 +246,8 @@ export class BaseModel { .update(this.convertObject(data)) .where(where ?? ""); - let result: any; - let resultPostgres: any; - - let updateCounts; - - - result = await this.execute(query); - updateCounts = result.affectedRows; - + let result: any = await this.execute(query); + let updateCounts = result.affectedRows; return updateCounts; } @@ -282,33 +260,6 @@ export class BaseModel { const sql = query.build(); dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); const result = await this.connection?.query(sql); - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } - - /** - * query custom - * @param query - */ - async querySqlite(query: Query): Promise { - const sql = query.build(); - console.log(replaceBackTick(sql)); - dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); - const result: any = await this.connection?.query(replaceBackTick(sql)); - - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } - - /** - * query custom - * @param query - */ - async queryPostgres(query: Query): Promise { - const sql = query.build(); - dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); - const result: any = await this.connection?.query(replaceBackTick(sql)) - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; } @@ -320,41 +271,7 @@ export class BaseModel { async execute(query: Query) { const sql = query.build(); dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - const result = await this.connection?.execute(sql) - - - - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } - - /** - * excute custom - * @param query - */ - async executeQueryPostGres(query: Query) { - const sql = query.build(); - - dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - - const result = await this.connection?.query(replaceBackTick(sql)) - - - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } - - /** - * excute custom - * @param query - */ - async executeQuerySqlite(query: Query) { - const sql = query.build(); - console.log(replaceBackTick(sql)); - dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - - const result = await this.connection?.query(replaceBackTick(sql)) - + const result = await this.connection?.execute(sql); dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; diff --git a/src/model_2.ts b/src/model_2.ts deleted file mode 100644 index c8d1f0a..0000000 --- a/src/model_2.ts +++ /dev/null @@ -1,401 +0,0 @@ -import { assert, Join, Order, Query, Where } from "../deps.ts"; -import { dso } from "./dso.ts"; -import { Defaults, FieldOptions, FieldType } from "./field.ts"; -import { Index, IndexType } from "./index.ts"; -import { replaceBackTick, rowsPostgres } from "../util.ts"; -import { PostgresClient } from "../PostgresClient.ts"; -import { SqliteClient } from "../SqliteClient.ts"; -import { Connection } from "../deps.ts"; -import { MysqlClient } from "./MysqlClient.ts"; - -export interface QueryOptions { - fields?: string[]; - where?: Where; - order?: Order[]; - group?: string[]; - join?: Join[]; - limit?: [number, number]; - having?: Where; -} - -/** Model Decorator */ -export function Model(name: string) { - return (target: { new (): T }) => { - Reflect.defineMetadata("model:name", name, target.prototype); - }; -} - -/** Model Fields list */ -export type ModelFields = Partial> & { - created_at?: Date; - updated_at?: Date; -}; - -/** Model base class */ -export class BaseModel { - created_at?: Date; - updated_at?: Date; - - constructor(public connection?: Connection | MysqlClient| PostgresClient | SqliteClient) {} - - /** get model name */ - get modelName(): string { - return Reflect.getMetadata("model:name", this); - } - - /** get primary key */ - get primaryKey(): FieldOptions | undefined { - return this.modelFields.find((field) => field.primary); - } - - /** Returns array of all available without primary key */ - get columnIndexes(): { [key: number]: Array } { - return { - [IndexType.INDEX]: this.modelFields.filter((field) => field.index) || [], - [IndexType.UNIQUE]: this.modelFields.filter((field) => field.unique) || - [], - [IndexType.SPATIAL]: this.modelFields.filter((field) => field.spatial) || - [], - [IndexType.FULLTEXT]: - this.modelFields.filter((field) => field.fullText) || [], - }; - } - - /** get defined fields list */ - get modelFields(): FieldOptions[] { - return ( - Reflect.getMetadata("model:fields", this) || [ - { - type: FieldType.DATE, - default: Defaults.CURRENT_TIMESTAMP, - autoUpdate: true, - name: "updated_at", - property: "updated_at", - }, - { - type: FieldType.DATE, - default: Defaults.CURRENT_TIMESTAMP, - name: "created_at", - property: "created_at", - }, - ] - ); - } - - /** get defined index list */ - get indexes(): Index[] { - return Reflect.getMetadata("model:indexes", this) || []; - } - - /** return a new Query instance with table name */ - builder(): Query { - const builder = new Query(); - return builder.table(this.modelName); - } - - /** - * Convert data object to model - * @param data - */ - private convertModel(data: { - [key: string]: any; - }): ModelFields | undefined { - if (!data) return; - const model: any = {}; - const fieldsMapping: any = {}; - this.modelFields.map( - (field) => (fieldsMapping[field.name] = field.property), - ); - this.indexes.map( - (index) => { - if (index.property) model[index.property] = index; - }, - ); - Object.keys(data).forEach((key) => { - const propertyName = fieldsMapping[key]; - model[propertyName || key] = data[key]; - }); - return model; - } - - /** - * Convert model object to db object - * @param model - */ - private convertObject(model: ModelFields): { [key: string]: any } { - const data: any = {}; - const fieldsMapping: any = {}; - this.modelFields.map( - (field) => (fieldsMapping[field.property!] = field.name), - ); - Object.keys(model).forEach((key) => { - const name = fieldsMapping[key]; - data[name || key] = model[key as keyof ModelFields]; - }); - return data; - } - - private optionsToQuery(options: QueryOptions) { - const query = this.builder(); - if (options.fields) { - query.select(...options.fields); - } else { - query.select(`${this.modelName}.*`); - } - - if (options.where) query.where(options.where); - if (options.group) query.groupBy(...options.group); - if (options.having) query.having(options.having); - if (options.join) { - options.join.forEach((join) => query.join(join)); - } - if (options.limit) query.limit(...options.limit); - if (options.order) options.order.forEach((order) => query.order(order)); - return query; - } - - /** - * find one record - * @param where conditions - */ - async findOne( - options: Where | QueryOptions, - ): Promise | undefined> { - if (options instanceof Where) { - options = { - where: options, - }; - } - let result; - let resultSqlite: any; - let resultPostgres: any; - let converted: ModelFields | undefined; - if (dso.configClientReturn.sqlite != null) { - resultSqlite = await this.querySqlite(this.optionsToQuery(options)); - const resultArray = [...resultSqlite]; - converted = this.convertModel(resultArray[0]); - } else if (dso.configClientReturn.postgres != null) { - resultPostgres = await this.queryPostgres(this.optionsToQuery(options)); - - converted = this.convertModel(rowsPostgres(resultPostgres)[0]); - } else {*/ - result = await this.query(this.optionsToQuery(options).limit(0, 1)); - - converted = this.convertModel(result[0]); - } - - return converted; - } - - /** - * delete by conditions - * @param where - */ - async delete(where: Where): Promise { - const query = this.builder().delete().where(where); - let result: any; - let deleteCounts: number | undefined; - let resultPostgres: any; - - if (dso.configClientReturn.sqlite != null) { - await this.executeQuerySqlite(query); - deleteCounts = dso.clientSqlite.changes; - } else if (dso.configClientReturn.postgres != null) { - resultPostgres = await this.executeQueryPostGres(query); - deleteCounts = parseInt(resultPostgres.rowCount); - } else { - result = await this.execute(query); - deleteCounts = result.affectedRows; - } - - return deleteCounts ?? 0; - } - - /** find all records by given conditions */ - async findAll(options: Where | QueryOptions): Promise[]> { - if (options instanceof Where) { - options = { - where: options, - }; - } - const result = await this.query(this.optionsToQuery(options)); - return result.map((record) => this.convertModel(record)!); - } - - /** find one record by primary key */ - async findById(id: string | number): Promise | undefined> { - assert(!!this.primaryKey); - return await this.findOne(Where.field(this.primaryKey.name).eq(id)); - } - - /** insert record */ - async insert(fields: Partial): Promise { - const query = this.builder().insert(this.convertObject(fields)); - - let result: any; - let idReturn: number; - - // if (dso.configClientReturn.sqlite != null) { - //await this.executeQuerySqlite(query); - //idReturn = dso.clientSqlite.lastInsertRowId; - //} else { - result = await this.execute(query); - idReturn = result.lastInsertId; - //} - - return idReturn; - } - - /** insert record */ - async insertRowsAffected(fields: Partial): Promise { - const query = this.builder().insert(this.convertObject(fields)); - - let resultPostgres: any; - let result: any; - - let updateCounts; - - if (dso.configClientReturn.sqlite != null) { - await this.executeQuerySqlite(query); - updateCounts = dso.clientSqlite.changes; - } else if (dso.configClientReturn.postgres != null) { - resultPostgres = await this.executeQueryPostGres(query); - updateCounts = parseInt(resultPostgres.rowCount); - } else { - result = await this.execute(query); - updateCounts = result.affectedRows; - } - - return updateCounts; - } - - /** update records by given conditions */ - async update( - data: Partial, - where?: Where, - ): Promise { - if ( - !where && - this.primaryKey && - data[this.primaryKey.property as keyof this] - ) { - where = Where.field(this.primaryKey.name).eq( - data[this.primaryKey.property as keyof this], - ); - } - const query = this.builder() - .update(this.convertObject(data)) - .where(where ?? ""); - - let result: any; - let resultPostgres: any; - - let updateCounts; - - if (dso.configClientReturn.sqlite != null) { - await this.executeQuerySqlite(query); - updateCounts = dso.clientSqlite.changes; - } else if (dso.configClientReturn.postgres != null) { - resultPostgres = await this.executeQueryPostGres(query); - updateCounts = parseInt(resultPostgres.rowCount); - } else { - result = await this.execute(query); - updateCounts = result.affectedRows; - } - - return updateCounts; - } - - /** - * query custom - * @param query - */ - async query(query: Query): Promise { - const sql = query.build(); - dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); - const result = this.connection - ? await this.connection.query(sql) - : await dso.client.query(sql); - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } - - /** - * query custom - * @param query - */ - async querySqlite(query: Query): Promise { - const sql = query.build(); - console.log(replaceBackTick(sql)); - dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); - const result: any = this.connection - ? await this.connection.query(replaceBackTick(sql)) - : await dso.clientSqlite.query(replaceBackTick(sql)).asObjects(); - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } - - /** - * query custom - * @param query - */ - async queryPostgres(query: Query): Promise { - const sql = query.build(); - dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); - const result: any = this.connection - ? await this.connection.query(replaceBackTick(sql)) - : await dso.clientPostgres.query(replaceBackTick(sql)); - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } - - /** - * excute custom - * @param query - */ - async execute(query: Query) { - const sql = query.build(); - dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - const result = this.connection - ? await this.connection.execute(sql) - : await dso.client.execute(sql); - - - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } - - /** - * excute custom - * @param query - */ - async executeQueryPostGres(query: Query) { - const sql = query.build(); - - dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - - const result = this.connection - ? await this.connection.query(replaceBackTick(sql)) - : await dso.clientPostgres.query(replaceBackTick(sql)); - - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } - - /** - * excute custom - * @param query - */ - async executeQuerySqlite(query: Query) { - const sql = query.build(); - console.log(replaceBackTick(sql)); - dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - - const result = this.connection - ? await this.connection.query(replaceBackTick(sql)) - : await dso.clientSqlite.query(replaceBackTick(sql)); - - dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); - return result; - } -} diff --git a/src/sync.ts b/src/sync.ts index a2eac10..d91bf62 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -126,19 +126,16 @@ export async function sync( defs, ");", ].join(" "); - // console.log(sql); } - // console.log(sql); - if (client instanceof MysqlClient) { - MysqlClient.showQueryLog && console.log(`\n[ DSO:SYNC ]\nSQL:\t ${sql}\n`); - } + + dso.showQueryLog && console.log(`\n[ DSO:SYNC ]\nSQL:\t ${sql}\n`); + let result; if (client instanceof MysqlClient) { result = await client.query(sql); } else { result = await client.query(replaceBackTick(sql)); } - if (client instanceof MysqlClient) { - MysqlClient.showQueryLog && console.log(`REUSLT:\t`, result, `\n`); - } + + dso.showQueryLog && console.log(`REUSLT:\t`, result, `\n`); } diff --git a/src/transaction.ts b/src/transaction.ts deleted file mode 100644 index 198ce8c..0000000 --- a/src/transaction.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Connection } from "../deps.ts"; -import { BaseModel } from "./model.ts"; -import { PostgresClient } from "../PostgresClient.ts"; -import { SqliteClient } from "../SqliteClient.ts"; -import { dso } from "./dso.ts"; -import { MysqlClient } from "./MysqlClient.ts"; - -export class Transaction { - constructor(private _conn: Connection | PostgresClient | SqliteClient) {} - - getModel( - Model: { new (conn: Connection | PostgresClient | SqliteClient): T }, - ): T { - const model = new Model(this._conn); - return model; - } - - /**static async transaction( - processor: (transaction: Transaction) => Promise, - driverType: string, - ): Promise { - if (driverType.toUpperCase() == "MYSQL") { - return ( - await new MysqlClient().client.transaction(async (conn) => { - const trans = new Transaction(conn); - return await processor(trans); - }) - ) as T; - } else if (driverType.toUpperCase() == "POSTGRES") { - return ( - await dso.clientPostgres.transaction(async (conn) => { - const trans = new Transaction(conn); - return await processor(trans); - }) - ) as T; - } else { - return ( - await dso.clientSqlite.transaction(async (conn) => { - const trans = new Transaction(conn); - return await processor(trans); - }) - ) as T; - } - }*/ -} diff --git a/src/transactionMysql.ts b/src/transactionMysql.ts index e39f23d..dbdc20d 100644 --- a/src/transactionMysql.ts +++ b/src/transactionMysql.ts @@ -3,20 +3,19 @@ import { BaseModel } from "./model.ts"; import { MysqlClient } from "./MysqlClient.ts"; export class TransactionMysql { - constructor(private _conn: Connection) { } + constructor(private _conn: Connection) {} getModel( - Model: { new(conn: Connection): T }, + Model: { new (conn: Connection): T }, ): T { const model = new Model(this._conn); return model; } static async transaction( - processor: (transaction: TransactionMysql) => Promise, - client: MysqlClient + processor: (transaction: TransactionMysql) => Promise, + client: MysqlClient, ): Promise { - return ( await client.client.transaction(async (conn) => { const trans = new TransactionMysql(conn); diff --git a/test.ts b/test.ts index d179950..dd616ee 100644 --- a/test.ts +++ b/test.ts @@ -10,10 +10,9 @@ const config: ClientConfig = { debug: true, username: "root", password: "", - db: "test1_orm", + db: "test_orm", }; - export async function clientTest(fn: Function, clientiele: MysqlClient) { Deno.test({ name: fn.name, @@ -28,56 +27,10 @@ export async function clientTest(fn: Function, clientiele: MysqlClient) { const client: MysqlClient = new MysqlClient(); async function main() { - await client.connect(config); - await client.query(`CREATE DATABASE IF NOT EXISTS test1_orm`); - await client.query(`USE test1_orm`); + await client.query(`CREATE DATABASE IF NOT EXISTS test_orm`); + await client.query(`USE test_orm`); await client.close(); - } await main(); - -/*** -const config2 = { - user: "postgres", - database: "test_orm", - hostname: "127.0.0.1", - //password: "", - password: "", - port: 5432, -}; - -const postgresConfig = { - type: "POSTGRES", - clientConfig: config2, -}; - -export async function clientTestPostgres(fn: Function) { - Deno.test({ - name: fn.name, - fn: async () => { - await dso.connect(postgresConfig); - await dso.sync(true); - await fn(); - dso.close(); - }, - }); -} - -const sqliteConfig = { - type: "SQLITE", - clientConfig: { database: "test.db" }, -}; - -export async function clientTestSQLITE(fn: Function) { - Deno.test({ - name: fn.name, - fn: async () => { - await dso.connect(sqliteConfig); - await dso.sync(true); - await fn(); - dso.close(); - }, - }); -}**/ diff --git a/test/model.ts b/test/model.ts index 9ad4f2b..9afe717 100644 --- a/test/model.ts +++ b/test/model.ts @@ -1,4 +1,3 @@ -import { ClientConfig } from "../deps.ts"; import { assert, assertEquals, assertThrowsAsync } from "../deps.ts"; import { BaseModel, @@ -11,7 +10,7 @@ import { Where, } from "../mod.ts"; import { dso } from "../src/dso.ts"; -import { clientTest} from "../test.ts"; +import { clientTest } from "../test.ts"; /** deno test --allow-net --allow-read --allow-write -c tsconfig.json */ @@ -53,10 +52,11 @@ class TopicModel extends BaseModel { @Field({ type: FieldType.STRING }) title?: string; } -let client: MysqlClient = new MysqlClient(); + +const client: MysqlClient = new MysqlClient(); dso.showQueryLog = true; -let userModel = client.define(UserModel); -let topicModel = client.define(TopicModel); +const userModel = client.define(UserModel); +const topicModel = client.define(TopicModel); clientTest(async function testInsert() { assertEquals( @@ -203,10 +203,6 @@ clientTest(async function testFindOneByOptions() { }); }, client); - - - - clientTest(async function testTransactionFail() { let userId: number | undefined; let topicId: number | undefined; @@ -246,364 +242,3 @@ clientTest(async function testTransactionSuccess() { assert(!!topic); assert(!!user); }, client); - - -/** -userModel = dso.define(UserModel); -topicModel = dso.define(TopicModel); - -clientTestSQLITE(async function testInsert() { - assertEquals( - await userModel.insert({ - nickName: "foo", - password: "bar", - phoneNumber: "08135539123", - }), - 1, - ); - assertEquals( - await userModel.insert({ - nickName: "foo", - password: "bar", - phoneNumber: "08135539124", - }), - 2, - ); - assertEquals( - await userModel.insert({ - nickName: "foo", - password: "bar", - phoneNumber: "08135539154", - }), - 3, - ); -}); - -clientTestSQLITE(async function testInsertRowsAffected() { - assertEquals( - await userModel.insertRowsAffected({ - nickName: "foo", - password: "bar", - phoneNumber: "08135539123", - }), - 1, - ); - assertEquals( - await userModel.insertRowsAffected({ - nickName: "foo", - password: "bar", - phoneNumber: "08135539124", - }), - 1, - ); - assertEquals( - await userModel.insertRowsAffected({ - nickName: "foo", - password: "bar", - phoneNumber: "08135539154", - }), - 1, - ); -}); - -clientTestSQLITE(async function testUpdate() { - const id: number | undefined = await userModel.insert( - { nickName: "foo", phoneNumber: "08135539123" }, - ); - console.log(id); - assertEquals( - await userModel.update({ - id, - password: "BAR", - }), - 1, - ); - const user = await userModel.findById(id!); - - assertEquals(user, { - updated_at: user?.updated_at, - created_at: user?.created_at, - defaultVal: 0, - id: 1, - nickName: "foo", - password: "BAR", - phoneNumber: "08135539123", - }); -}); - -clientTestSQLITE(async function testFindOneByWhere() { - await userModel.insert({ nickName: "foo", phoneNumber: "08135539123" }); - await topicModel.insert({ title: "foo", userId: 1 }); - const user = await userModel.findOne( - Where.and( - Where.field("id").eq(1), - Where.field("password").isNull(), - Where.field("default_val").lt(10), - ), - ); - const topic = await topicModel.findById(1); - assertEquals(user, { - id: 1, - nickName: "foo", - password: null, - defaultVal: 0, - phoneNumber: "08135539123", - updated_at: user?.updated_at, - created_at: user?.created_at, - }); - assert(!!topic?.created_at); - assertEquals(topic, { - updated_at: topic?.updated_at, - created_at: topic?.created_at, - id: 1, - title: "foo", - userId: 1, - }); -}); - -clientTestSQLITE(async function testDelete() { - await userModel.insert({ nickName: "foo" }); - await userModel.insert({ nickName: "bar" }); - await userModel.insert({ nickName: "noo" }); - const count = await userModel.delete( - Where.or(Where.field("id").eq(1), Where.field("nick_name").eq("noo")), - ); - - assertEquals(count, 2); -}); - -clientTestSQLITE(async function testFindOneByOptions() { - await userModel.insert({ nickName: "foo" }); - await topicModel.insert({ title: "foo", userId: 1 }); - const user = await userModel.findOne({ - where: Where.and( - Where.field("id").eq(1), - Where.field("password").isNull(), - Where.field("default_val").lt(10), - ), - }); - const topic = await topicModel.findOne({ - where: Where.field("topics.id").eq(1), - fields: ["topics.*", "users.nick_name as userNickName"], - join: [Join.left("users").on("users.id", "topics.user_id")], - }); - assert(!!topic?.created_at); - assert(!!topic?.updated_at); - assertEquals(topic, { - id: 1, - title: "foo", - userId: 1, - userNickName: "foo", - updated_at: topic?.updated_at, - created_at: topic?.created_at, - }); -}); - -clientTestSQLITE(async function testTransactionSuccess() { - let topicId: number | undefined; - let userId: number | undefined; - const result = await dso.transaction(async (trans) => { - const userModel = trans.getModel(UserModel); - const topicModel = trans.getModel(TopicModel); - userId = await userModel.insert( - { nickName: "foo", password: "bar" }, - ); - topicId = await topicModel.insert({ title: "zoo", userId }); - let user = await userModel.findById(userId!); - assert(!!user); - return true; - }, "SQLITE"); - const user = await userModel.findById(userId!); - const topic = await userModel.findById(topicId!); - assertEquals(result, true); - assert(!!topic); - assert(!!user); -}); - -clientTestSQLITE(async function testTransactionFail() { - let userId: number | undefined; - let topicId: number | undefined; - await assertThrowsAsync(async () => { - await dso.transaction(async (trans) => { - const userModel = trans.getModel(UserModel); - const topicModel = trans.getModel(TopicModel); - userId = await userModel.insert({ nickName: "foo", password: "bar" }); - topicId = await topicModel.insert({ title: "zoo", userId }); - let user = await userModel.findById(userId!); - assert(!!user); - await userModel.query(new Query().table("notexists").select("*")); - return true; - }, "SQLITE"); - }); - const user = await userModel.findById(userId!); - const topic = await topicModel.findById(topicId!); - assert(!user); - assert(!topic); -}); - -userModel = dso.define(UserModel); -topicModel = dso.define(TopicModel); - -clientTestPostgres(async function testInsert() { - assertEquals( - await userModel.insertRowsAffected({ - nickName: "foo", - password: "bar", - phoneNumber: "08135539123", - }), - 1, - ); - assertEquals( - await userModel.insertRowsAffected({ - nickName: "foo", - password: "bar", - phoneNumber: "08135539124", - }), - 1, - ); - assertEquals( - await userModel.insertRowsAffected({ - nickName: "foo", - password: "bar", - phoneNumber: "08135539154", - }), - 1, - ); -}); - -clientTestPostgres(async function testUpdate() { - const id: number | undefined = await userModel.insertRowsAffected( - { nickName: "foo", phoneNumber: "08135539123" }, - ); - assertEquals( - await userModel.update({ - id, - password: "BAR", - }), - 1, - ); - const user = await userModel.findById(id!); - assertEquals(user, { - updated_at: user?.updated_at, - created_at: user?.created_at, - defaultVal: 0, - id: 1, - nickName: "foo", - password: "BAR", - phoneNumber: "08135539123", - }); -}); - -clientTestPostgres(async function testFindOneByWhere() { - await userModel.insertRowsAffected( - { nickName: "foo", phoneNumber: "08135539123" }, - ); - await topicModel.insertRowsAffected({ title: "foo", userId: 1 }); - const user = await userModel.findOne( - Where.and( - Where.field("id").eq(1), - Where.field("password").isNull(), - Where.field("default_val").lt(10), - ), - ); - const topic = await topicModel.findById(1); - assertEquals(user, { - id: 1, - nickName: "foo", - password: null, - defaultVal: 0, - phoneNumber: "08135539123", - updated_at: user?.updated_at, - created_at: user?.created_at, - }); - assert(!!topic?.created_at); - assertEquals(topic, { - updated_at: topic?.updated_at, - created_at: topic?.created_at, - id: 1, - title: "foo", - userId: 1, - }); -}); - -clientTestPostgres(async function testDelete() { - await userModel.insertRowsAffected({ nickName: "foo" }); - await userModel.insertRowsAffected({ nickName: "bar" }); - await userModel.insertRowsAffected({ nickName: "noo" }); - const count = await userModel.delete( - Where.or(Where.field("id").eq(1), Where.field("nick_name").eq("noo")), - ); - - assertEquals(count, 2); -}); - -clientTestPostgres(async function testFindOneByOptions() { - await userModel.insertRowsAffected({ nickName: "foo" }); - await topicModel.insertRowsAffected({ title: "foo", userId: 1 }); - const user = await userModel.findOne({ - where: Where.and( - Where.field("id").eq(1), - Where.field("password").isNull(), - Where.field("default_val").lt(10), - ), - }); - const topic = await topicModel.findOne({ - where: Where.field("topics.id").eq(1), - fields: ["topics.*", "users.nick_name as userNickName"], - join: [Join.left("users").on("users.id", "topics.user_id")], - }); - - assert(!!topic?.created_at); - assert(!!topic?.updated_at); - assertEquals(topic, { - updated_at: topic?.updated_at, - created_at: topic?.created_at, - id: 1, - userId: 1, - title: "foo", - usernickname: "foo", // Postgres changes alia to small letters - }); -}); -clientTestPostgres(async function testTransactionFail() { - let userId: number | undefined; - let topicId: number | undefined; - await assertThrowsAsync(async () => { - await dso.transaction(async (trans) => { - const userModel = trans.getModel(UserModel); - const topicModel = trans.getModel(TopicModel); - userId = await userModel.insertRowsAffected( - { nickName: "foo", password: "bar" }, - ); - topicId = await topicModel.insertRowsAffected({ title: "zoo", userId }); - let user = await userModel.findById(userId!); - assert(!!user); - await userModel.query(new Query().table("notexists").select("*")); - return true; - }, "POSTGRES"); - }); - const user = await userModel.findById(userId!); - const topic = await topicModel.findById(topicId!); - assert(!user); - assert(!topic); -}); - -clientTestPostgres(async function testTransactionSuccess() { - let topicId: number | undefined; - let userId: number | undefined; - const result = await dso.transaction(async (trans) => { - const userModel = trans.getModel(UserModel); - const topicModel = trans.getModel(TopicModel); - userId = await userModel.insertRowsAffected( - { nickName: "foo", password: "bar" }, - ); - topicId = await topicModel.insertRowsAffected({ title: "zoo", userId }); - let user = await userModel.findById(userId!); - assert(!!user); - return true; - }, "POSTGRES"); - const user = await userModel.findById(userId!); - const topic = await userModel.findById(topicId!); - assertEquals(result, true); - assert(!!topic); - assert(!!user); -}); */ \ No newline at end of file From f20b1fad017c73f2a408bd5e80db6b2446e442bf Mon Sep 17 00:00:00 2001 From: tksilicon Date: Wed, 4 Nov 2020 19:09:38 +0100 Subject: [PATCH 19/24] feat:databaseDriverAdaptations - MySQL --- src/model.ts | 3 +++ src/sync.ts | 19 ++++++------------- test/model.ts | 12 +++++++----- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/model.ts b/src/model.ts index 4627c84..a79e751 100644 --- a/src/model.ts +++ b/src/model.ts @@ -2,6 +2,9 @@ import { assert, Join, Order, Query, Where } from "../deps.ts"; import { Defaults, FieldOptions, FieldType } from "./field.ts"; import { Index, IndexType } from "./index.ts"; import { CharsetType } from "./charset.ts"; +import { dso } from "./dso.ts"; +import { Connection } from "../deps.ts"; +import { MysqlClient } from "./MysqlClient.ts"; export interface QueryOptions { fields?: string[]; diff --git a/src/sync.ts b/src/sync.ts index bf80686..5d80bb4 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -4,6 +4,11 @@ import { FieldType, Defaults } from "./field.ts"; import { BaseModel } from "./model.ts"; import { columnIndexesList, Index } from "./index.ts"; import { charsetList } from "./charset.ts"; +import { replaceBackTick } from "../util.ts"; +import { SqliteClient } from "../SqliteClient.ts"; +import { PostgresClient } from "../PostgresClient.ts"; +import { MysqlClient } from "./MysqlClient.ts"; + export async function sync( client: MysqlClient | PostgresClient | SqliteClient, @@ -107,7 +112,6 @@ export async function sync( defs += `, ${columnIndexesList[index.type]} (${index.columns.join(", ")})`; }); -<<<<<<< HEAD let sql; if (client instanceof MysqlClient) { @@ -117,7 +121,7 @@ export async function sync( "(", defs, ")", - "ENGINE=InnoDB DEFAULT CHARSET=utf8;", + `ENGINE=InnoDB DEFAULT CHARSET=${charsetList[model.charset]};`, ].join(" "); } else { sql = [ @@ -128,17 +132,6 @@ export async function sync( ");", ].join(" "); } -======= - const sql = [ - "CREATE TABLE IF NOT EXISTS", - model.modelName, - "(", - defs, - ")", - `ENGINE=InnoDB DEFAULT CHARSET=${charsetList[model.charset]};`, - ].join(" "); - console.log(sql); ->>>>>>> master dso.showQueryLog && console.log(`\n[ DSO:SYNC ]\nSQL:\t ${sql}\n`); diff --git a/test/model.ts b/test/model.ts index a28be09..bb48b65 100644 --- a/test/model.ts +++ b/test/model.ts @@ -67,9 +67,11 @@ class CharsetsModel extends BaseModel { chineseName?: string; } -const userModel = dso.define(UserModel); -const topicModel = dso.define(TopicModel); -const charsetsModel = dso.define(CharsetsModel); +const client: MysqlClient = new MysqlClient(); +dso.showQueryLog = true; +const userModel = client.define(UserModel); +const topicModel = client.define(TopicModel); +const charsetsModel = client.define(CharsetsModel); clientTest(async function testInsert() { assertEquals( @@ -223,14 +225,14 @@ clientTest(async function testCharsetsFail() { { czechName: "独角兽", chineseName: "jednorožec" }, ) as Promise, ); -}); +}, client); clientTest(async function testCharsetsSuccess() { const id = await charsetsModel.insert( { czechName: "jednorožec", chineseName: "独角兽" }, ); assertEquals(id, 1); -}); +}, client); clientTest(async function testTransactionFail() { let userId: number | undefined; From ed867108fbcc1eb52ab1b5910594c77456f26c97 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Wed, 4 Nov 2020 19:39:23 +0100 Subject: [PATCH 20/24] feat:databaseDriverAdaptations - MySQL --- PostgresClient.ts | 89 ------------------------------------ SqliteClient.ts | 112 ---------------------------------------------- mod.ts | 7 --- postgres_deps.ts | 24 ---------- sqlite_deps.ts | 16 ------- src/dso.ts | 9 +++- src/sync.ts | 21 ++------- test.ts | 17 ++++--- test/model.ts | 36 +++++++-------- 9 files changed, 37 insertions(+), 294 deletions(-) delete mode 100644 PostgresClient.ts delete mode 100644 SqliteClient.ts delete mode 100644 postgres_deps.ts delete mode 100644 sqlite_deps.ts diff --git a/PostgresClient.ts b/PostgresClient.ts deleted file mode 100644 index 294f2b6..0000000 --- a/PostgresClient.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - Client, - PostgresError, - log, - QueryResult, - QueryConfig, -} from "./postgres_deps.ts"; - -/** Transaction processor */ -export interface TransactionProcessor { - (connection: PostgresClient): Promise; -} - -export class PostgresClient { - protected client: Client; - - constructor(config: object) { - this.client = new Client(config); - } - - async connect(): Promise { - return this.client.connect(); - } - - // TODO: can we use more specific type for args? - async query(text: string): Promise { - return this.client.query(text); - } - - // TODO: can we use more specific type for args? - async execute(text: string): Promise { - return this.client.query(text); - } - - async multiQuery(queries: QueryConfig[]): Promise { - const result: QueryResult[] = []; - - for (const query of queries) { - result.push(await this.client.query(query)); - } - - return result; - } - - async end(): Promise { - await this.client.end(); - } - - /** - * Use a connection for transaction processor - * - * @param fn transation processor - */ - async useConnection(fn: (conn: PostgresClient) => Promise) { - if (!this.client) { - throw new Error("Unconnected"); - } - try { - const result = await fn(this); - return result; - } catch (error) { - throw new PostgresError( - { severity: "high", code: "TA", message: "transactions" }, - ); - } - } - - /** - * Execute a transaction process, and the transaction successfully - * returns the return value of the transaction process - * @param processor transation processor - */ - async transaction(processor: TransactionProcessor): Promise { - return await this.useConnection(async (connection) => { - try { - await connection.query("BEGIN"); - const result = await processor(connection); - await connection.query("COMMIT"); - return result; - } catch (error) { - log.info(`ROLLBACK: ${error.message}`); - await connection.query("ROLLBACK"); - throw new PostgresError( - { severity: "high", code: "TA", message: "transactions" }, - ); - } - }); - } -} diff --git a/SqliteClient.ts b/SqliteClient.ts deleted file mode 100644 index d516e78..0000000 --- a/SqliteClient.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { DB } from "https://deno.land/x/sqlite/mod.ts"; -import { log } from "./sqlite_deps.ts"; -import SqliteError from "https://deno.land/x/sqlite/src/error.ts"; -import { Rows } from "./sqlite_deps.ts"; - -/** Transaction processor */ -export interface TransactionProcessor { - (connection: SqliteClient): Promise; -} - -export class SqliteClient { - protected db: DB; - constructor(path: string) { - this.db = new DB(path); - } - - query(sql: string): Rows { - return this.db.query(sql); - } - - execute(sql: string): Rows { - return this.db.query(sql); - } - - /** - * DB.close - * - * Close database handle. This must be called if - * DB is no longer used, to avoid leaking file - * resources. - * - * If force is specified, any on-going transactions - * will be closed. - */ - close(force: boolean = false) { - this.db.close(force); - } - - /** - * DB.lastInsertRowId - * - * Get last inserted row id. This corresponds to - * the SQLite function `sqlite3_last_insert_rowid`. - * - * By default, it will return 0 if there is no row - * inserted yet. - */ - get lastInsertRowId(): number { - return this.db.lastInsertRowId; - } - - /** - * DB.changes - * - * Return the number of rows modified, inserted or - * deleted by the most recently completed query. - * This corresponds to the SQLite function - * `sqlite3_changes`. - */ - get changes(): number { - return this.db.changes; - } - - /** - * DB.totalChanges - * - * Return the number of rows modified, inserted or - * deleted since the database was opened. - * This corresponds to the SQLite function - * `sqlite3_total_changes`. - */ - get totalChanges(): number { - return this.db.totalChanges; - } - - /** - * Use a connection for transaction processor - * - * @param fn transation processor - */ - async useConnection(fn: (conn: this) => Promise) { - if (!this.db) { - throw new Error("Unconnected"); - } - try { - const result = await fn(this); - return result; - } catch (error) { - throw new SqliteError("connection", 2); - } - } - - /** - * Execute a transaction process, and the transaction successfully - * returns the return value of the transaction process - * @param processor transation processor - */ - async transaction(processor: TransactionProcessor): Promise { - return await this.useConnection(async (connection) => { - try { - await connection.query("BEGIN"); - const result = await processor(connection); - await connection.query("COMMIT"); - return result; - } catch (error) { - log.info(`ROLLBACK: ${error.message}`); - await connection.query("ROLLBACK"); - throw new SqliteError("transaction", 1); - } - }); - } -} diff --git a/mod.ts b/mod.ts index 8695a2d..2e336cd 100644 --- a/mod.ts +++ b/mod.ts @@ -8,14 +8,7 @@ export { Where, } from "./deps.ts"; -export { - PostgresClient, -} from "./PostgresClient.ts"; -export { - SqliteClient, -} from "./SqliteClient.ts"; export { dso } from "./src/dso.ts"; -export { MysqlClient } from "./src/MysqlClient.ts"; export * from "./src/field.ts"; export * from "./src/index.ts"; export * from "./src/charset.ts"; diff --git a/postgres_deps.ts b/postgres_deps.ts deleted file mode 100644 index 06b518e..0000000 --- a/postgres_deps.ts +++ /dev/null @@ -1,24 +0,0 @@ -export { - Pool, - PostgresError, - Client, -} from "https://deno.land/x/postgres/mod.ts"; - -export { - Connection, -} from "https://deno.land/x/postgres/connection.ts"; - -export { - Query as QueryPostgres, - QueryConfig, - QueryResult, -} from "https://deno.land/x/postgres/query.ts"; - -export { - ConnectionOptions, - createParams, -} from "https://deno.land/x/postgres/connection_params.ts"; - -export { - log, -} from "https://deno.land/x/mysql/src/logger.ts"; diff --git a/sqlite_deps.ts b/sqlite_deps.ts deleted file mode 100644 index dbfb750..0000000 --- a/sqlite_deps.ts +++ /dev/null @@ -1,16 +0,0 @@ -export { - getStr, - setStr, - setArr, -} from "https://deno.land/x/sqlite/src/wasm.ts"; - -export { Status, Values } from "https://deno.land/x/sqlite/src/constants.ts"; - -export { - Rows, - Empty, -} from "https://deno.land/x/sqlite/src/rows.ts"; - -export { - log, -} from "https://deno.land/x/mysql/src/logger.ts"; diff --git a/src/dso.ts b/src/dso.ts index aaa52c8..e1b4d33 100644 --- a/src/dso.ts +++ b/src/dso.ts @@ -1,9 +1,16 @@ +import { MysqlClient } from "./MysqlClient.ts"; + /** - * Global dso instance + * Global dso instance containing all clients */ export const dso = { /** * set true will show execute/query sql */ showQueryLog: false, + + /** + * MYSQL CLIENT + */ + mysqlClient: new MysqlClient(), }; diff --git a/src/sync.ts b/src/sync.ts index 5d80bb4..a80bf86 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -4,23 +4,15 @@ import { FieldType, Defaults } from "./field.ts"; import { BaseModel } from "./model.ts"; import { columnIndexesList, Index } from "./index.ts"; import { charsetList } from "./charset.ts"; -import { replaceBackTick } from "../util.ts"; -import { SqliteClient } from "../SqliteClient.ts"; -import { PostgresClient } from "../PostgresClient.ts"; import { MysqlClient } from "./MysqlClient.ts"; - export async function sync( - client: MysqlClient | PostgresClient | SqliteClient, + client: MysqlClient, model: BaseModel, force: boolean, ) { if (force) { - if (client instanceof MysqlClient) { - await client.execute(`DROP TABLE IF EXISTS ${model.modelName}`); - } else { - await client.query(`DROP TABLE IF EXISTS ${model.modelName}`); - } + await client.execute(`DROP TABLE IF EXISTS ${model.modelName}`); } let defs = model.modelFields @@ -34,8 +26,6 @@ export async function sync( case FieldType.INT: if (client instanceof MysqlClient) { type = `INT(${field.length || 11})`; - } else if (client instanceof SqliteClient) { - type = `INTEGER `; } else { if (field.autoIncrement) { type = `SERIAL`; @@ -136,11 +126,8 @@ export async function sync( dso.showQueryLog && console.log(`\n[ DSO:SYNC ]\nSQL:\t ${sql}\n`); let result; - if (client instanceof MysqlClient) { - result = await client.query(sql); - } else { - result = await client.query(replaceBackTick(sql)); - } + + result = await client.query(sql); dso.showQueryLog && console.log(`REUSLT:\t`, result, `\n`); } diff --git a/test.ts b/test.ts index dd616ee..25539ff 100644 --- a/test.ts +++ b/test.ts @@ -13,24 +13,23 @@ const config: ClientConfig = { db: "test_orm", }; -export async function clientTest(fn: Function, clientiele: MysqlClient) { +export async function clientTest(fn: Function) { Deno.test({ name: fn.name, fn: async () => { - await clientiele.connect(config); - await clientiele.sync(true); + await dso.mysqlClient.connect(config); + await dso.mysqlClient.sync(true); await fn(); - clientiele.close(); + dso.mysqlClient.close(); }, }); } -const client: MysqlClient = new MysqlClient(); async function main() { - await client.connect(config); - await client.query(`CREATE DATABASE IF NOT EXISTS test_orm`); - await client.query(`USE test_orm`); - await client.close(); + await dso.mysqlClient.connect(config); + await dso.mysqlClient.query(`CREATE DATABASE IF NOT EXISTS test_orm`); + await dso.mysqlClient.query(`USE test_orm`); + await dso.mysqlClient.close(); } await main(); diff --git a/test/model.ts b/test/model.ts index bb48b65..6fcc42f 100644 --- a/test/model.ts +++ b/test/model.ts @@ -1,7 +1,6 @@ import { assert, assertEquals, assertThrowsAsync } from "../deps.ts"; import { BaseModel, - MysqlClient, Field, FieldType, CharsetType, @@ -67,11 +66,10 @@ class CharsetsModel extends BaseModel { chineseName?: string; } -const client: MysqlClient = new MysqlClient(); dso.showQueryLog = true; -const userModel = client.define(UserModel); -const topicModel = client.define(TopicModel); -const charsetsModel = client.define(CharsetsModel); +const userModel = dso.mysqlClient.define(UserModel); +const topicModel = dso.mysqlClient.define(TopicModel); +const charsetsModel = dso.mysqlClient.define(CharsetsModel); clientTest(async function testInsert() { assertEquals( @@ -98,7 +96,7 @@ clientTest(async function testInsert() { }), 3, ); -}, client); +}); clientTest(async function testInsertRowsAffected() { assertEquals( @@ -125,7 +123,7 @@ clientTest(async function testInsertRowsAffected() { }), 1, ); -}, client); +}); clientTest(async function testUpdate() { const id: number | undefined = await userModel.insert( @@ -148,7 +146,7 @@ clientTest(async function testUpdate() { password: "BAR", phoneNumber: "08135539123", }); -}, client); +}); clientTest(async function testFindOneByWhere() { await userModel.insert({ nickName: "foo", phoneNumber: "08135539123" }); @@ -178,7 +176,7 @@ clientTest(async function testFindOneByWhere() { title: "foo", userId: 1, }); -}, client); +}); clientTest(async function testDelete() { await userModel.insert({ nickName: "foo" }); @@ -189,7 +187,7 @@ clientTest(async function testDelete() { ); assertEquals(count, 2); -}, client); +}); clientTest(async function testFindOneByOptions() { await userModel.insert({ nickName: "foo" }); @@ -216,7 +214,7 @@ clientTest(async function testFindOneByOptions() { updated_at: topic?.updated_at, created_at: topic?.created_at, }); -}, client); +}); clientTest(async function testCharsetsFail() { await assertThrowsAsync( @@ -225,20 +223,20 @@ clientTest(async function testCharsetsFail() { { czechName: "独角兽", chineseName: "jednorožec" }, ) as Promise, ); -}, client); +}); clientTest(async function testCharsetsSuccess() { const id = await charsetsModel.insert( { czechName: "jednorožec", chineseName: "独角兽" }, ); assertEquals(id, 1); -}, client); +}); clientTest(async function testTransactionFail() { let userId: number | undefined; let topicId: number | undefined; await assertThrowsAsync(async () => { - await client.transaction(async (trans) => { + await dso.mysqlClient.transaction(async (trans) => { const userModel = trans.getModel(UserModel); const topicModel = trans.getModel(TopicModel); userId = await userModel.insert({ nickName: "foo", password: "bar" }); @@ -247,18 +245,18 @@ clientTest(async function testTransactionFail() { assert(!!user); await userModel.query(new Query().table("notexists").select("*")); return true; - }, client); + }, dso.mysqlClient); }); const user = await userModel.findById(userId!); const topic = await topicModel.findById(topicId!); assert(!user); assert(!topic); -}, client); +}); clientTest(async function testTransactionSuccess() { let topicId: number | undefined; let userId: number | undefined; - const result = await client.transaction(async (trans) => { + const result = await dso.mysqlClient.transaction(async (trans) => { const userModel = trans.getModel(UserModel); const topicModel = trans.getModel(TopicModel); userId = await userModel.insert({ nickName: "foo", password: "bar" }); @@ -266,10 +264,10 @@ clientTest(async function testTransactionSuccess() { let user = await userModel.findById(userId!); assert(!!user); return true; - }, client); + }, dso.mysqlClient); const user = await userModel.findById(userId!); const topic = await userModel.findById(topicId!); assertEquals(result, true); assert(!!topic); assert(!!user); -}, client); +}); From ba33409ddaebffb7d4616de6bcd076d83ad17fdc Mon Sep 17 00:00:00 2001 From: tksilicon Date: Wed, 4 Nov 2020 19:41:57 +0100 Subject: [PATCH 21/24] feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL --- .github/workflows/ci.yml | 13 +------------ deps.ts | 1 - mod.ts | 1 - src/DsoClient.ts | 11 ----------- test.ts | 13 +++++-------- 5 files changed, 6 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98f0302..9505666 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,17 +19,6 @@ jobs: MYSQL_ALLOW_EMPTY_PASSWORD: "true" MYSQL_ROOT_PASSWORD: "" options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - - postgres: - image: postgres:11.5 - ports: - - 5432:5432 - env: - POSTGRES_USER: "postgres" - POSTGRES_PASSWORD: "" - POSTGRES_DB: "test_orm" - sqlite: - image: keinos/sqlite3:latest steps: @@ -43,4 +32,4 @@ jobs: deno fmt --check - name: Test run: | - deno test --allow-net --allow-read --allow-write -c tsconfig.json + deno test --allow-net -c tsconfig.json diff --git a/deps.ts b/deps.ts index fcc1773..ce35e37 100644 --- a/deps.ts +++ b/deps.ts @@ -5,7 +5,6 @@ export { } from "https://deno.land/std@v0.51.0/testing/asserts.ts"; export { Client, - ClientConfig, Connection, } from "https://deno.land/x/mysql@2.1.0/mod.ts"; export { diff --git a/mod.ts b/mod.ts index 2e336cd..ec22f8f 100644 --- a/mod.ts +++ b/mod.ts @@ -1,6 +1,5 @@ export { Client, - ClientConfig, Join, Order, Query, diff --git a/src/DsoClient.ts b/src/DsoClient.ts index d25b8b6..9be68a5 100644 --- a/src/DsoClient.ts +++ b/src/DsoClient.ts @@ -1,21 +1,10 @@ import { MysqlClient } from "./MysqlClient.ts"; -export enum ClientType { - MYSQL = "MYSQL", - POSTGRESS = "POSTGRESS", - SQLITE = "SQLITE", -} - export interface DsoConnection { query(sql: string, params?: any[]): Promise; execute(sql: string, params?: any[]): Promise; } -/** Transaction processor */ -export interface TransactionProcessor { - (connection: MysqlClient): Promise; -} - export interface PoolInfo { size: number | undefined; maxSize: number | undefined; diff --git a/test.ts b/test.ts index 25539ff..46511b8 100644 --- a/test.ts +++ b/test.ts @@ -1,23 +1,20 @@ import { dso } from "./mod.ts"; import "./test/model.ts"; -import { ClientConfig } from "./deps.ts"; -import { MysqlClient } from "./src/MysqlClient.ts"; -const config: ClientConfig = { +const config = { hostname: "127.0.0.1", port: 3306, - poolSize: 6, - debug: true, + poolSize: 3, + debug: false, username: "root", password: "", - db: "test_orm", + db: "", }; - export async function clientTest(fn: Function) { Deno.test({ name: fn.name, fn: async () => { - await dso.mysqlClient.connect(config); + await dso.mysqlClient.connect({ ...config, db: "test_orm" }); await dso.mysqlClient.sync(true); await fn(); dso.mysqlClient.close(); From 6631d3b0daf298492767ac17586da06065218b12 Mon Sep 17 00:00:00 2001 From: vmasdani Date: Thu, 5 Nov 2020 00:07:46 +0700 Subject: [PATCH 22/24] add isolatedModules:false to tsconfig & readme for Deno 1.6.1 --- README.md | 5 +++-- tsconfig.json | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fe53e7b..2913c82 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,8 @@ Since this library needs to use Typescript Decorators and other features, a cust "allowJs": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "module": "esnext" + "module": "esnext", + "isolatedModules": false } } ``` @@ -316,4 +317,4 @@ Following types of an index are available | fulltext | true #### Character sets -list of valid character sets is based on https://dev.mysql.com/doc/refman/8.0/en/charset-charsets.html \ No newline at end of file +list of valid character sets is based on https://dev.mysql.com/doc/refman/8.0/en/charset-charsets.html diff --git a/tsconfig.json b/tsconfig.json index 4931dc4..6728492 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "allowJs": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "module": "esnext" + "module": "esnext", + "isolatedModules": false } } From 6e90cabb296c9f260d7fe0654cf769125ece65c0 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Thu, 5 Nov 2020 08:56:53 +0100 Subject: [PATCH 23/24] feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL feat:databaseDriverAdaptations - MySQL --- README.md | 60 ++++++++++++++----------------------------------------- deps.ts | 1 + mod.ts | 1 + 3 files changed, 17 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 2913c82..0279475 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ ![(Deno)](https://img.shields.io/badge/deno-1.0.0-green.svg) -`dso` is a simple ORM Library based on [deno_mysql](https://github.com/manyuanrong/deno_mysql), [Deno-Postgres](https://github.com/deno-postgres/deno-postgres) and [Deno-Sqlite](https://github.com/dyedgreen/deno-sqlite). +`dso` is a simple ORM Library based on [deno_mysql](https://github.com/manyuanrong/deno_mysql) + +Other databases to be supported include - [Deno-Postgres](https://github.com/deno-postgres/deno-postgres) and [Deno-Sqlite](https://github.com/dyedgreen/deno-sqlite). ### Example @@ -60,16 +62,7 @@ class UserModel extends BaseModel { public myUniqueIndex!: Index; } -const userModel = dso.define(UserModel); -/* - -export interface Config extends Base { - type: type: string; // MySQl|Postgres|Sqlite - clientConfig?: ClientConfig | object; MySQL client Config or an object - client?: Client | PostgresClient | SqliteClient; -} - -*/ +const userModel = dso.mysqlClient.define(UserModel); const config: ClientConfig = { hostname: "127.0.0.1", @@ -81,35 +74,13 @@ const config: ClientConfig = { db: "", }; -const mysqlConfig = { - type: "MYSQL", - clientConfig: { ...config, db: "dbname" }, -}; - -const configPostgres = { - user: "username", - database: "dbname", - hostname: "127.0.0.1", - password: "", - port: 5432, -}; - -const postgresConfig = { - type: "POSTGRES", - clientConfig: configPostgres, -}; -const sqliteConfig = { - type: "SQLITE", - clientConfig: { database: "test.db" }, -}; async function main() { // The database must be created before linking with the configuration object - await dso.connect(mysqlConfig); - await dso.connect(postgresConfig); - await dso.connect(sqliteConfig); + await dso.mysqlClient.connect(mysqlConfig); + /* When installing or initializing a database, @@ -121,7 +92,7 @@ async function main() { == WARNING! == Using true as the parameter will reset your whole database! Use with caution. */ - await dso.sync(false); + await dso.mysqlClient.sync(false); // You can add records using insert method const insertId = await userModel.insert({ @@ -193,13 +164,13 @@ Set whether query debugging information is displayed dso.showQueryLog = true; ``` -#### dso.connect +#### dso.mysqlClient.connect You need to use this method to link to the database before you can manipulate the database See the approprite configuration object specified earlier. Example below is for MySQL ```ts -await dso.connect({ +await dso.mysqlClient.connect({ hostname: "127.0.0.1", // database hostname port: 3306, // database port username: "root", // database username @@ -208,7 +179,7 @@ await dso.connect({ }); ``` -#### dso.define() +#### dso.mysqlClient.define() Add an annotated `Model` instance and return the instance. @@ -248,7 +219,7 @@ export default const userModel = dso.define(UserModel); // userModel.delete(...) ``` -#### dso.sync +#### dso.mysqlClient.sync When installing or initializing a database, you can use sync to synchronize the database model to the database. @@ -259,22 +230,21 @@ const force = true; // force await dso.sync(force); ``` -#### dso.transaction(processor: (transaction: Transaction) => Promise): Promise +#### dso.mysqlClient.transaction(processor: (transaction: Transaction) => Promise): Promise Create and start a transaction. -New `Model` instances must be obtained through `getModel(Model)`. Otherwise, it will not be controlled by transactions. Transaction takes a second `driverType:string` parameter which can be -"MYSQL"| "POSTGRES" | "SQLITE" (ignores capitalization). +New `Model` instances must be obtained through `getModel(Model)`. Otherwise, it will not be controlled by transactions. ```ts -const result = await dso.transaction(async trans => { +const result = await dso.mysqlClient.transaction(async trans => { const userModel = trans.getModel(UserModel); const topicModel = trans.getModel(TopicModel); userId = await userModel.insert({ nickName: "foo", password: "bar", phoneNumber: "08135539123" }); topicId = await topicModel.insert({ title: "zoo", userId }); return true; -}, "MYSQL"); +}); ``` ### Top Level Types diff --git a/deps.ts b/deps.ts index ce35e37..fcc1773 100644 --- a/deps.ts +++ b/deps.ts @@ -5,6 +5,7 @@ export { } from "https://deno.land/std@v0.51.0/testing/asserts.ts"; export { Client, + ClientConfig, Connection, } from "https://deno.land/x/mysql@2.1.0/mod.ts"; export { diff --git a/mod.ts b/mod.ts index ec22f8f..2e336cd 100644 --- a/mod.ts +++ b/mod.ts @@ -1,5 +1,6 @@ export { Client, + ClientConfig, Join, Order, Query, From 0d4ec1c298425b72242f9a57610ea15c0f141b38 Mon Sep 17 00:00:00 2001 From: manyuanrong <416828041@qq.com> Date: Fri, 6 Nov 2020 13:58:12 +0800 Subject: [PATCH 24/24] abstract base dso client --- mod.ts | 1 + src/DsoClient.ts | 46 --------------------- src/MysqlClient.ts | 82 ------------------------------------- src/drivers/base.ts | 90 +++++++++++++++++++++++++++++++++++++++++ src/drivers/mysql.ts | 68 +++++++++++++++++++++++++++++++ src/dso.ts | 53 ++++++++++++++++++++++-- src/model.ts | 22 +++++----- src/sync.ts | 17 ++++---- src/transactionMysql.ts | 26 ------------ src/types.ts | 32 +++++++++++++++ test.ts | 48 ++++++++++++++-------- test/model.ts | 16 ++++---- 12 files changed, 302 insertions(+), 199 deletions(-) delete mode 100644 src/DsoClient.ts delete mode 100644 src/MysqlClient.ts create mode 100644 src/drivers/base.ts create mode 100644 src/drivers/mysql.ts delete mode 100644 src/transactionMysql.ts create mode 100644 src/types.ts diff --git a/mod.ts b/mod.ts index 2e336cd..aa74519 100644 --- a/mod.ts +++ b/mod.ts @@ -14,3 +14,4 @@ export * from "./src/index.ts"; export * from "./src/charset.ts"; export * from "./src/model.ts"; export * from "./src/util.ts"; +export * from "./src/types.ts"; diff --git a/src/DsoClient.ts b/src/DsoClient.ts deleted file mode 100644 index 9be68a5..0000000 --- a/src/DsoClient.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { MysqlClient } from "./MysqlClient.ts"; - -export interface DsoConnection { - query(sql: string, params?: any[]): Promise; - execute(sql: string, params?: any[]): Promise; -} - -export interface PoolInfo { - size: number | undefined; - maxSize: number | undefined; - available: number | undefined; -} - -/** - * DSO client - */ -export abstract class DsoClient { - /** get pool info */ - abstract get pool(): PoolInfo | undefined; - - /** - * connect to database - * @param config config for client - * @returns Clinet instance - */ - abstract connect(config: T): Promise; - - /** - * excute query sql - * @param sql query sql string - * @param params query params - */ - abstract async query(sql: string, params?: any[]): Promise; - - /** - * excute sql - * @param sql sql string - * @param params query params - */ - abstract async execute(sql: string, params?: any[]): Promise; - - /** - * close connection - */ - abstract async close(): Promise; -} diff --git a/src/MysqlClient.ts b/src/MysqlClient.ts deleted file mode 100644 index 007bfac..0000000 --- a/src/MysqlClient.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Connection } from "../deps.ts"; -import { Client } from "../deps.ts"; -import { DsoClient, PoolInfo } from "./DsoClient.ts"; -import { BaseModel } from "./model.ts"; -import { sync } from "./sync.ts"; -import { TransactionMysql } from "./transactionMysql.ts"; - -export class MysqlClient extends DsoClient { - #client: Client = new Client(); - - /** @ignore */ - #models: BaseModel[] = []; - - /** - * transaction processor - */ - transaction = TransactionMysql.transaction; - - constructor(private _conn?: Connection) { - super(); - } - - /** - * Sync model to database table - * @param force set true, will drop table before create table - */ - async sync(force: boolean = false): Promise { - for (const model of this.#models) { - await sync(this, model, force); - } - } - - /** - * MySQL Database client - */ - get client(): Client { - return this.#client; - } - - /** - * all models - */ - get models() { - return this.#models; - } - - /** - * add model - * @param model - */ - define( - ModelClass: { new (connection: Connection | MysqlClient): T }, - ): T { - const model = new ModelClass(this); - this.#models.push(model); - return model; - } - - async close(): Promise { - this.#client.close(); - } - - async connect(config: ClientConfig): Promise { - await this.#client.connect(config); - return this; - } - - get pool(): PoolInfo { - const poolInfo = { - size: this.#client.pool?.size, - maxSize: this.#client.pool?.maxSize, - available: this.#client.pool?.available, - }; - return poolInfo; - } - async query(sql: string, params?: any[]): Promise { - return await this.#client.query(sql, params); - } - async execute(sql: string, params?: any[]): Promise { - return await this.#client.execute(sql, params); - } -} diff --git a/src/drivers/base.ts b/src/drivers/base.ts new file mode 100644 index 0000000..55e4730 --- /dev/null +++ b/src/drivers/base.ts @@ -0,0 +1,90 @@ +import { Query } from "../../deps.ts"; +import { dso } from "../dso.ts"; +import { BaseModel } from "../model.ts"; +import { DriverName } from "../types.ts"; + +export interface DsoConnection { + query(sql: string, params?: any[]): Promise; + execute(sql: string, params?: any[]): Promise; +} + +export interface PoolInfo { + size: number | undefined; + maxSize: number | undefined; + available: number | undefined; +} + +/** + * DSO client + */ +export abstract class DsoClient { + /** get driver name */ + abstract get driverName(): DriverName; + + /** get pool info */ + abstract get pool(): PoolInfo | undefined; + + /** + * connect to database + * @param config config for client + * @returns Clinet instance + */ + abstract connect(config: T): Promise; + + abstract async useConnection( + fn: (conn: DsoConnection) => Promise, + ): Promise; + + /** + * close connection + */ + abstract async close(): Promise; + + abstract async transaction( + processor: (transaction: DsoTransaction) => Promise, + client: DsoClient, + ): Promise; + + /** + * query custom + * @param query + */ + async query(query: Query | string): Promise { + const sql = typeof query === "string" ? query : query.build(); + dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); + const result = this.useConnection((conn) => { + return conn.query(sql); + }); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } + + /** + * excute custom + * @param query + */ + async execute(query: Query | string) { + const sql = typeof query === "string" ? query : query.build(); + dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); + const result = this.useConnection((conn) => { + return conn.execute(sql); + }); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); + return result; + } +} + +export class DsoTransaction { + #connection: DsoConnection; + + constructor(conn: DsoConnection) { + this.#connection = conn; + } + + getModel( + Model: { new (conn: DsoConnection): T }, + ): T { + const model = new Model(this.#connection); + return model; + } +} diff --git a/src/drivers/mysql.ts b/src/drivers/mysql.ts new file mode 100644 index 0000000..e73896f --- /dev/null +++ b/src/drivers/mysql.ts @@ -0,0 +1,68 @@ +import { + Client, + ClientConfig, + Connection, +} from "https://deno.land/x/mysql@2.1.0/mod.ts"; +import { DriverName } from "../types.ts"; +import { DsoClient, DsoConnection, DsoTransaction, PoolInfo } from "./base.ts"; + +class MySqlConnection implements DsoConnection { + #connection: Connection; + + constructor(conn: Connection) { + this.#connection = conn; + } + + async query(sql: string, params?: any[]): Promise { + const result = await this.#connection.query(sql, params); + return result as T; + } + + async execute(sql: string, params?: any[]): Promise { + const result = await this.#connection.execute(sql, params); + return result as T; + } +} + +export class MysqlClient extends DsoClient { + #client: Client = new Client(); + + get driverName(): DriverName { + return DriverName.MYSQL; + } + + async close(): Promise { + this.#client.close(); + } + + async connect(config: ClientConfig): Promise { + await this.#client.connect(config); + return this; + } + + get pool(): PoolInfo { + const poolInfo = { + size: this.#client.pool?.size, + maxSize: this.#client.pool?.maxSize, + available: this.#client.pool?.available, + }; + return poolInfo; + } + + useConnection(fn: (conn: DsoConnection) => Promise): Promise { + return this.#client.useConnection((mysqlConn) => { + return fn(new MySqlConnection(mysqlConn)); + }); + } + + async transaction( + processor: (transaction: DsoTransaction) => Promise, + ): Promise { + return ( + await this.#client.transaction(async (conn) => { + const trans = new DsoTransaction(new MySqlConnection(conn)); + return await processor(trans); + }) + ) as T; + } +} diff --git a/src/dso.ts b/src/dso.ts index e1b4d33..994c187 100644 --- a/src/dso.ts +++ b/src/dso.ts @@ -1,4 +1,14 @@ -import { MysqlClient } from "./MysqlClient.ts"; +import { DsoClient } from "./drivers/base.ts"; +import { MysqlClient } from "./drivers/mysql.ts"; +import { BaseModel } from "./model.ts"; +import { sync } from "./sync.ts"; +import { ConnectOptions } from "./types.ts"; + +/** @ignore */ +const models: BaseModel[] = []; + +/** @ignore */ +let client: DsoClient; /** * Global dso instance containing all clients @@ -9,8 +19,45 @@ export const dso = { */ showQueryLog: false, + get models(): BaseModel[] { + return models; + }, + + get client(): DsoClient { + return client; + }, + + /** + * add model + * @param model + */ + define(ModelClass: { new (): T }): T { + const model = new ModelClass(); + models.push(model); + return model; + }, + + /** + * Sync model to database table + * @param force set true, will drop table before create table + */ + async sync(force: boolean = false): Promise { + for (const model of models) { + await sync(client, model, force); + } + }, + /** - * MYSQL CLIENT + * connect to database + * @param config client config */ - mysqlClient: new MysqlClient(), + async connect(config: ConnectOptions) { + if (config instanceof DsoClient) { + client = config; + } else if (config.driver === "mysql") { + client = new MysqlClient(); + await client.connect(config.options); + } + return client; + }, }; diff --git a/src/model.ts b/src/model.ts index a79e751..1634233 100644 --- a/src/model.ts +++ b/src/model.ts @@ -1,10 +1,9 @@ import { assert, Join, Order, Query, Where } from "../deps.ts"; -import { Defaults, FieldOptions, FieldType } from "./field.ts"; -import { Index, IndexType } from "./index.ts"; import { CharsetType } from "./charset.ts"; +import { DsoConnection } from "./drivers/base.ts"; import { dso } from "./dso.ts"; -import { Connection } from "../deps.ts"; -import { MysqlClient } from "./MysqlClient.ts"; +import { Defaults, FieldOptions, FieldType } from "./field.ts"; +import { Index, IndexType } from "./index.ts"; export interface QueryOptions { fields?: string[]; @@ -35,7 +34,7 @@ export class BaseModel { updated_at?: Date; charset: CharsetType = CharsetType.utf8; - constructor(public connection?: Connection | MysqlClient) {} + constructor(public connection?: DsoConnection) {} /** get model name */ get modelName(): string { @@ -166,8 +165,6 @@ export class BaseModel { }; } let result; - let resultSqlite: any; - let resultPostgres: any; let converted: ModelFields | undefined; result = await this.query(this.optionsToQuery(options).limit(0, 1)); @@ -261,7 +258,11 @@ export class BaseModel { async query(query: Query): Promise { const sql = query.build(); dso.showQueryLog && console.log(`\n[ DSO:QUERY ]\nSQL:\t ${sql}\n`); - const result = await this.connection?.query(sql); + + const result = this.connection + ? await this.connection?.query(sql) + : await dso.client.useConnection((conn) => conn.query(sql)); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; } @@ -273,7 +274,10 @@ export class BaseModel { async execute(query: Query) { const sql = query.build(); dso.showQueryLog && console.log(`\n[ DSO:EXECUTE ]\nSQL:\t ${sql}\n`); - const result = await this.connection?.execute(sql); + + const result = this.connection + ? await this.connection?.execute(sql) + : await dso.client.useConnection((conn) => conn.execute(sql)); dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; diff --git a/src/sync.ts b/src/sync.ts index a80bf86..6671e16 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1,13 +1,14 @@ import { assert, Client } from "../deps.ts"; import { dso } from "../mod.ts"; -import { FieldType, Defaults } from "./field.ts"; -import { BaseModel } from "./model.ts"; -import { columnIndexesList, Index } from "./index.ts"; import { charsetList } from "./charset.ts"; -import { MysqlClient } from "./MysqlClient.ts"; +import { DsoClient } from "./drivers/base.ts"; +import { Defaults, FieldType } from "./field.ts"; +import { columnIndexesList, Index } from "./index.ts"; +import { BaseModel } from "./model.ts"; +import { DriverName } from "./types.ts"; export async function sync( - client: MysqlClient, + client: DsoClient, model: BaseModel, force: boolean, ) { @@ -24,7 +25,7 @@ export async function sync( type = `VARCHAR(${field.length || 255})`; break; case FieldType.INT: - if (client instanceof MysqlClient) { + if (client.driverName === DriverName.MYSQL) { type = `INT(${field.length || 11})`; } else { if (field.autoIncrement) { @@ -65,7 +66,7 @@ export async function sync( def += ` DEFAULT ${field.default}`; } } - if (client instanceof MysqlClient) { + if (client.driverName === DriverName.MYSQL) { if (field.autoIncrement) def += " AUTO_INCREMENT"; } @@ -104,7 +105,7 @@ export async function sync( let sql; - if (client instanceof MysqlClient) { + if (client.driverName === DriverName.MYSQL) { sql = [ "CREATE TABLE IF NOT EXISTS", model.modelName, diff --git a/src/transactionMysql.ts b/src/transactionMysql.ts deleted file mode 100644 index dbdc20d..0000000 --- a/src/transactionMysql.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Connection } from "../deps.ts"; -import { BaseModel } from "./model.ts"; -import { MysqlClient } from "./MysqlClient.ts"; - -export class TransactionMysql { - constructor(private _conn: Connection) {} - - getModel( - Model: { new (conn: Connection): T }, - ): T { - const model = new Model(this._conn); - return model; - } - - static async transaction( - processor: (transaction: TransactionMysql) => Promise, - client: MysqlClient, - ): Promise { - return ( - await client.client.transaction(async (conn) => { - const trans = new TransactionMysql(conn); - return await processor(trans); - }) - ) as T; - } -} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..3e41911 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,32 @@ +import { DsoClient } from "./drivers/base.ts"; + +export enum DriverName { + MYSQL = "mysql", + SQLITE = "sqlite", +} + +export interface MysqlOptions { + /** Database hostname */ + hostname?: string; + /** Database username */ + username?: string; + /** Database password */ + password?: string; + /** Database port */ + port?: number; + /** Database name */ + db?: string; + /** Whether to Display Packet Debugging Information */ + debug?: boolean; + /** Connect timeout */ + timeout?: number; + /** Connection pool size default 1 */ + poolSize?: number; + /** charset */ + charset?: string; +} + +export type ConnectOptions = DsoClient | { + driver: DriverName; + options?: MysqlOptions; +}; diff --git a/test.ts b/test.ts index 46511b8..5f2811e 100644 --- a/test.ts +++ b/test.ts @@ -1,32 +1,46 @@ import { dso } from "./mod.ts"; +import { DriverName } from "./src/types.ts"; import "./test/model.ts"; const config = { - hostname: "127.0.0.1", - port: 3306, - poolSize: 3, - debug: false, - username: "root", - password: "", - db: "", + driver: DriverName.MYSQL, + options: { + hostname: "127.0.0.1", + port: 3306, + poolSize: 3, + debug: false, + username: "root", + password: "", + db: "", + }, }; -export async function clientTest(fn: Function) { + +export async function clientTest(testFn: Function) { Deno.test({ - name: fn.name, + name: testFn.name, fn: async () => { - await dso.mysqlClient.connect({ ...config, db: "test_orm" }); - await dso.mysqlClient.sync(true); - await fn(); - dso.mysqlClient.close(); + await dso.connect( + { + ...config, + options: { + ...config.options, + db: "test_orm", + }, + }, + ); + await dso.sync(true); + await testFn(); + dso.client.close(); }, }); } async function main() { - await dso.mysqlClient.connect(config); - await dso.mysqlClient.query(`CREATE DATABASE IF NOT EXISTS test_orm`); - await dso.mysqlClient.query(`USE test_orm`); - await dso.mysqlClient.close(); + await dso.connect(config); + await dso.client.query(`DROP DATABASE IF EXISTS test_orm`); + await dso.client.query(`CREATE DATABASE IF NOT EXISTS test_orm`); + await dso.client.query(`USE test_orm`); + await dso.client.close(); } await main(); diff --git a/test/model.ts b/test/model.ts index 6fcc42f..558dd33 100644 --- a/test/model.ts +++ b/test/model.ts @@ -1,9 +1,9 @@ import { assert, assertEquals, assertThrowsAsync } from "../deps.ts"; import { BaseModel, + CharsetType, Field, FieldType, - CharsetType, Join, Model, Query, @@ -67,9 +67,9 @@ class CharsetsModel extends BaseModel { } dso.showQueryLog = true; -const userModel = dso.mysqlClient.define(UserModel); -const topicModel = dso.mysqlClient.define(TopicModel); -const charsetsModel = dso.mysqlClient.define(CharsetsModel); +const userModel = dso.define(UserModel); +const topicModel = dso.define(TopicModel); +const charsetsModel = dso.define(CharsetsModel); clientTest(async function testInsert() { assertEquals( @@ -236,7 +236,7 @@ clientTest(async function testTransactionFail() { let userId: number | undefined; let topicId: number | undefined; await assertThrowsAsync(async () => { - await dso.mysqlClient.transaction(async (trans) => { + await dso.client.transaction(async (trans) => { const userModel = trans.getModel(UserModel); const topicModel = trans.getModel(TopicModel); userId = await userModel.insert({ nickName: "foo", password: "bar" }); @@ -245,7 +245,7 @@ clientTest(async function testTransactionFail() { assert(!!user); await userModel.query(new Query().table("notexists").select("*")); return true; - }, dso.mysqlClient); + }, dso.client); }); const user = await userModel.findById(userId!); const topic = await topicModel.findById(topicId!); @@ -256,7 +256,7 @@ clientTest(async function testTransactionFail() { clientTest(async function testTransactionSuccess() { let topicId: number | undefined; let userId: number | undefined; - const result = await dso.mysqlClient.transaction(async (trans) => { + const result = await dso.client.transaction(async (trans) => { const userModel = trans.getModel(UserModel); const topicModel = trans.getModel(TopicModel); userId = await userModel.insert({ nickName: "foo", password: "bar" }); @@ -264,7 +264,7 @@ clientTest(async function testTransactionSuccess() { let user = await userModel.findById(userId!); assert(!!user); return true; - }, dso.mysqlClient); + }, dso.client); const user = await userModel.findById(userId!); const topic = await userModel.findById(topicId!); assertEquals(result, true);