From 3af23ab12077fdde8b3544a0fec7f3acbe862db5 Mon Sep 17 00:00:00 2001 From: tksilicon Date: Tue, 4 Aug 2020 14:29:19 +0100 Subject: [PATCH 01/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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);