diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e950cfa..9505666 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: MYSQL_ALLOW_EMPTY_PASSWORD: "true" MYSQL_ROOT_PASSWORD: "" options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + steps: - uses: actions/checkout@v2 @@ -31,4 +32,4 @@ jobs: deno fmt --check - name: Test run: | - deno test -c tsconfig.json --allow-net + deno test --allow-net -c tsconfig.json 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/.travis.yml b/.travis.yml index e6a6c98..e7c4f29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,16 @@ services: - mysql + - postgresql + - SQLITE3 + + 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 --unstable --allow-net -c tsconfig.json test.ts + - deno test --allow-net --allow-read --allow-write -c tsconfig.json test.ts diff --git a/README.md b/README.md index 340713d..0279475 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,12 @@ ![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) +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 ```ts @@ -58,17 +62,25 @@ class UserModel extends BaseModel { public myUniqueIndex!: Index; } -const userModel = dso.define(UserModel); +const userModel = dso.mysqlClient.define(UserModel); + +const config: ClientConfig = { + hostname: "127.0.0.1", + port: 3306, + poolSize: 3, // optional + debug: false, + username: "root", + password: "", + db: "", +}; + + + 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" - }); + // The database must be created before linking with the configuration object + await dso.mysqlClient.connect(mysqlConfig); + /* When installing or initializing a database, @@ -80,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({ @@ -88,6 +100,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); @@ -123,7 +143,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 } } ``` @@ -143,12 +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 @@ -157,7 +179,7 @@ await dso.connect({ }); ``` -#### dso.define() +#### dso.mysqlClient.define() Add an annotated `Model` instance and return the instance. @@ -191,12 +213,13 @@ 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(...) ``` -#### dso.sync +#### dso.mysqlClient.sync When installing or initializing a database, you can use sync to synchronize the database model to the database. @@ -207,14 +230,14 @@ 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. ```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); @@ -264,4 +287,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/docker-compose.yml b/docker-compose.yml index 682dddf..4307b2d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,4 +7,15 @@ 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: "postgres" + POSTGRES_PASSWORD: "" + POSTGRES_DB: "test_orm" + sqlite: + image: keinos/sqlite3:latest + \ No newline at end of file diff --git a/mod.ts b/mod.ts index bc8ea36..aa74519 100644 --- a/mod.ts +++ b/mod.ts @@ -7,9 +7,11 @@ export { replaceParams, Where, } from "./deps.ts"; + export { dso } from "./src/dso.ts"; export * from "./src/field.ts"; 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/Reflect.ts b/src/Reflect.ts index eab0600..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. @@ -780,13 +781,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..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 f2bbca1..994c187 100644 --- a/src/dso.ts +++ b/src/dso.ts @@ -1,45 +1,30 @@ -import { Client, ClientConfig } from "../deps.ts"; +import { DsoClient } from "./drivers/base.ts"; +import { MysqlClient } from "./drivers/mysql.ts"; import { BaseModel } from "./model.ts"; import { sync } from "./sync.ts"; -import { Transaction } from "./transaction.ts"; +import { ConnectOptions } from "./types.ts"; /** @ignore */ -let _client: Client; +const models: BaseModel[] = []; /** @ignore */ -let _models: BaseModel[] = []; +let client: DsoClient; /** - * Global dso instance + * Global dso instance containing all clients */ export const dso = { /** - * set true will show exucte/query sql + * 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 { - for (const model of _models) { - await sync(_client, model, force); - } + get models(): BaseModel[] { + return models; }, - /** - * Database client - */ - get client(): Client { - return _client; - }, - - /** - * all models - */ - get models() { - return _models; + get client(): DsoClient { + return client; }, /** @@ -48,27 +33,31 @@ export const dso = { */ define(ModelClass: { new (): T }): T { const model = new ModelClass(); - _models.push(model); + models.push(model); return model; }, - transaction: Transaction.transaction, + /** + * 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); + } + }, /** * connect to database * @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: ConnectOptions) { + if (config instanceof DsoClient) { + client = config; + } else if (config.driver === "mysql") { + client = new MysqlClient(); + await client.connect(config.options); } - return _client; - }, - - close(): void { - _client.close(); + return client; }, }; diff --git a/src/model.ts b/src/model.ts index fce6a4d..1634233 100644 --- a/src/model.ts +++ b/src/model.ts @@ -1,8 +1,9 @@ -import { assert, Connection, Join, Order, Query, Where } from "../deps.ts"; +import { assert, Join, Order, Query, Where } from "../deps.ts"; +import { CharsetType } from "./charset.ts"; +import { DsoConnection } from "./drivers/base.ts"; import { dso } from "./dso.ts"; import { Defaults, FieldOptions, FieldType } from "./field.ts"; import { Index, IndexType } from "./index.ts"; -import { CharsetType } from "./charset.ts"; export interface QueryOptions { fields?: string[]; @@ -33,7 +34,7 @@ export class BaseModel { updated_at?: Date; charset: CharsetType = CharsetType.utf8; - constructor(public connection?: Connection) {} + constructor(public connection?: DsoConnection) {} /** get model name */ get modelName(): string { @@ -163,8 +164,14 @@ export class BaseModel { where: options, }; } - const result = await this.query(this.optionsToQuery(options).limit(0, 1)); - return this.convertModel(result[0]); + let result; + let converted: ModelFields | undefined; + + result = await this.query(this.optionsToQuery(options).limit(0, 1)); + + converted = this.convertModel(result[0]); + + return converted; } /** @@ -172,12 +179,15 @@ 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; + + result = await this.execute(query); + deleteCounts = result.affectedRows; + + return deleteCounts ?? 0; } /** find all records by given conditions */ @@ -200,8 +210,21 @@ 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 = await this.execute(query); + let idReturn: number = result.lastInsertId; + + return idReturn; + } + + /** insert record */ + async insertRowsAffected(fields: Partial): Promise { + const query = this.builder().insert(this.convertObject(fields)); + + let result: any = await this.execute(query); + let updateCounts = result.affectedRows; + + return updateCounts; } /** update records by given conditions */ @@ -222,8 +245,10 @@ export class BaseModel { .update(this.convertObject(data)) .where(where ?? ""); - const result = await this.execute(query); - return result.affectedRows; + let result: any = await this.execute(query); + let updateCounts = result.affectedRows; + + return updateCounts; } /** @@ -233,9 +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 = this.connection - ? await this.connection.query(sql) - : await dso.client.query(sql); + ? await this.connection?.query(sql) + : await dso.client.useConnection((conn) => conn.query(sql)); + dso.showQueryLog && console.log(`RESULT:\t`, result, `\n`); return result; } @@ -247,9 +274,11 @@ 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); + ? 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 1ee0340..6671e16 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1,11 +1,17 @@ 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 { 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: Client, model: BaseModel, force: boolean) { +export async function sync( + client: DsoClient, + model: BaseModel, + force: boolean, +) { if (force) { await client.execute(`DROP TABLE IF EXISTS ${model.modelName}`); } @@ -19,7 +25,15 @@ 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.driverName === DriverName.MYSQL) { + type = `INT(${field.length || 11})`; + } else { + if (field.autoIncrement) { + type = `SERIAL`; + } else { + type = `INT`; + } + } break; case FieldType.DATE: type = `TIMESTAMP`; @@ -39,6 +53,7 @@ export async function sync(client: Client, model: BaseModel, force: boolean) { break; } } + def += ` ${type}`; if (field.charset) { def += ` CHARACTER SET ${charsetList[field.charset]}`; @@ -51,13 +66,21 @@ export async function sync(client: Client, model: BaseModel, force: boolean) { def += ` DEFAULT ${field.default}`; } } - if (field.autoIncrement) def += " AUTO_INCREMENT"; + if (client.driverName === DriverName.MYSQL) { + 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; }) @@ -80,17 +103,32 @@ 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=${charsetList[model.charset]};`, - ].join(" "); - console.log(sql); + let sql; + + if (client.driverName === DriverName.MYSQL) { + sql = [ + "CREATE TABLE IF NOT EXISTS", + model.modelName, + "(", + defs, + ")", + `ENGINE=InnoDB DEFAULT CHARSET=${charsetList[model.charset]};`, + ].join(" "); + } else { + sql = [ + "CREATE TABLE IF NOT EXISTS", + model.modelName, + "(", + defs, + ");", + ].join(" "); + } dso.showQueryLog && console.log(`\n[ DSO:SYNC ]\nSQL:\t ${sql}\n`); - const result = await client.execute(sql); + + let result; + + result = await client.query(sql); + dso.showQueryLog && console.log(`REUSLT:\t`, result, `\n`); } diff --git a/src/transaction.ts b/src/transaction.ts deleted file mode 100644 index d7819d1..0000000 --- a/src/transaction.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Connection } from "../deps.ts"; -import { dso } from "./dso.ts"; -import { BaseModel } from "./model.ts"; - -export class Transaction { - constructor(private _conn: Connection) {} - getModel(Model: { new (conn: Connection): T }): T { - const model = new Model(this._conn); - return model; - } - - static async transaction( - processor: (transaction: Transaction) => Promise, - ): Promise { - return (await dso.client.transaction(async (conn) => { - const trans = new Transaction(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 f26ffed..5f2811e 100644 --- a/test.ts +++ b/test.ts @@ -1,37 +1,46 @@ -import { Client } from "./deps.ts"; 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: "", + }, }; -const client = new Client(); -dso.showQueryLog = false; - -export async function clientTest(fn: Function) { +export async function clientTest(testFn: Function) { Deno.test({ - name: fn.name, + name: testFn.name, fn: async () => { - await dso.connect({ ...config, db: "test_orm" }); + await dso.connect( + { + ...config, + options: { + ...config.options, + db: "test_orm", + }, + }, + ); await dso.sync(true); - await fn(); - dso.close(); + await testFn(); + dso.client.close(); }, }); } 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.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 4b34dff..558dd33 100644 --- a/test/model.ts +++ b/test/model.ts @@ -1,17 +1,19 @@ import { assert, assertEquals, assertThrowsAsync } from "../deps.ts"; import { BaseModel, - dso, + CharsetType, Field, FieldType, - CharsetType, Join, Model, Query, Where, } from "../mod.ts"; +import { dso } from "../src/dso.ts"; import { clientTest } from "../test.ts"; +/** deno test --allow-net --allow-read --allow-write -c tsconfig.json */ + @Model("users") class UserModel extends BaseModel { @Field({ @@ -64,6 +66,7 @@ class CharsetsModel extends BaseModel { chineseName?: string; } +dso.showQueryLog = true; const userModel = dso.define(UserModel); const topicModel = dso.define(TopicModel); const charsetsModel = dso.define(CharsetsModel); @@ -85,6 +88,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() { @@ -198,16 +236,16 @@ clientTest(async function testTransactionFail() { let userId: number | undefined; let topicId: number | undefined; await assertThrowsAsync(async () => { - await dso.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" }); 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; - }); + }, dso.client); }); const user = await userModel.findById(userId!); const topic = await topicModel.findById(topicId!); @@ -218,7 +256,7 @@ clientTest(async function testTransactionFail() { clientTest(async function testTransactionSuccess() { let topicId: number | undefined; let userId: number | undefined; - const result = await dso.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" }); @@ -226,7 +264,7 @@ clientTest(async function testTransactionSuccess() { let user = await userModel.findById(userId!); assert(!!user); return true; - }); + }, dso.client); const user = await userModel.findById(userId!); const topic = await userModel.findById(topicId!); assertEquals(result, true); 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 } } 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; +}