Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Database driver adaptations #32

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ jobs:
MYSQL_ROOT_PASSWORD: ""
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3

postgres:
image: postgres:11.5
ports:
- 5432:5432
env:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: ""
POSTGRES_DB: "test_orm"
sqlite:
image: keinos/sqlite3:latest


steps:
- uses: actions/checkout@v2
- name: Setup Deno
Expand All @@ -31,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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ mysql.log
package-lock.json
.vscode
node_modules
test.db
*.code-workspace
9 changes: 8 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -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
89 changes: 89 additions & 0 deletions PostgresClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
Client,
PostgresError,
log,
QueryResult,
QueryConfig,
} from "./postgres_deps.ts";

/** Transaction processor */
export interface TransactionProcessor<T> {
(connection: PostgresClient): Promise<T>;
}

export class PostgresClient {
protected client: Client;

constructor(config: object) {
this.client = new Client(config);
}

async connect(): Promise<void> {
return this.client.connect();
}

// TODO: can we use more specific type for args?
async query(text: string): Promise<QueryResult> {
return this.client.query(text);
}

// TODO: can we use more specific type for args?
async execute(text: string): Promise<QueryResult> {
return this.client.query(text);
}

async multiQuery(queries: QueryConfig[]): Promise<QueryResult[]> {
const result: QueryResult[] = [];

for (const query of queries) {
result.push(await this.client.query(query));
}

return result;
}

async end(): Promise<void> {
await this.client.end();
}

/**
* Use a connection for transaction processor
*
* @param fn transation processor
*/
async useConnection<T>(fn: (conn: PostgresClient) => Promise<T>) {
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<T>(processor: TransactionProcessor<T>): Promise<T> {
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" },
);
}
});
}
}
76 changes: 64 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -51,16 +53,55 @@ 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;
}

*/

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({
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.connect(mysqlConfig);
await dso.connect(postgresConfig);
await dso.connect(sqliteConfig);

/*
When installing or initializing a database,
Expand All @@ -80,6 +121,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);

Expand Down Expand Up @@ -138,6 +187,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({
Expand Down Expand Up @@ -183,7 +233,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(...)
```
Expand All @@ -203,7 +254,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<boolean>(async trans => {
Expand All @@ -213,7 +265,7 @@ const result = await dso.transaction<boolean>(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
Expand Down
112 changes: 112 additions & 0 deletions SqliteClient.ts
Original file line number Diff line number Diff line change
@@ -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<T> {
(connection: SqliteClient): Promise<T>;
}

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<T>(fn: (conn: this) => Promise<T>) {
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<T>(processor: TransactionProcessor<T>): Promise<T> {
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);
}
});
}
}
13 changes: 12 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,15 @@ services:
- 3306:3306
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "true"
MYSQL_ROOT_PASSWORD: ""
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

Loading