diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index f062d044..437d4107 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -27,8 +27,8 @@ jobs: strategy: matrix: postgres: - - '14.2' - - '16' + - "14.2" + - "16" runs-on: ubuntu-latest services: postgres: @@ -105,14 +105,14 @@ jobs: run: | cd internal/integration yarn install - # - name: yarn tsc - # run: | - # cd internal/integration - # yarn tsc - name: yarn test run: | cd internal/integration yarn test + # - name: yarn tsc + # run: | + # cd internal/integration + # yarn tsc mysql-golang: needs: [build] runs-on: ubuntu-latest @@ -191,14 +191,14 @@ jobs: run: | cd internal/integration yarn install - # - name: yarn tsc - # run: | - # cd internal/integration - # yarn tsc - name: yarn test run: | cd internal/integration yarn test + # - name: yarn tsc + # run: | + # cd internal/integration + # yarn tsc sqlite-golang: needs: [build] runs-on: ubuntu-latest @@ -249,11 +249,11 @@ jobs: run: | cd internal/integration yarn install - # - name: yarn tsc - # run: | - # cd internal/integration - # yarn tsc - name: yarn test run: | cd internal/integration yarn test + # - name: yarn tsc + # run: | + # cd internal/integration + # yarn tsc diff --git a/Makefile b/Makefile index d8a20007..185ac0ee 100644 --- a/Makefile +++ b/Makefile @@ -33,9 +33,9 @@ test-mysql: install cd internal/integration && queryx db:migrate --schema mysql.hcl cd internal/integration && queryx db:migrate --schema mysql.hcl cd internal/integration && queryx generate --schema mysql.hcl - # cd internal/integration && yarn tsc - # cd internal/integration && yarn test - cd internal/integration && go test ./... + cd internal/integration && yarn tsc + cd internal/integration && yarn test + # cd internal/integration && go test ./... test-sqlite: install rm -rf internal/integration/db @@ -44,9 +44,9 @@ test-sqlite: install cd internal/integration && queryx db:migrate --schema sqlite.hcl cd internal/integration && queryx db:migrate --schema sqlite.hcl cd internal/integration && queryx generate --schema sqlite.hcl - # cd internal/integration && yarn tsc - # cd internal/integration && yarn test - cd internal/integration && go test ./... + cd internal/integration && yarn tsc + cd internal/integration && yarn test + # cd internal/integration && go test ./... test: test-postgresql test-sqlite test-mysql diff --git a/generator/client/typescript/templates/[model]/[model].tstmpl b/generator/client/typescript/templates/[model]/[model].tstmpl index 168f52c8..acb5b03e 100644 --- a/generator/client/typescript/templates/[model]/[model].tstmpl +++ b/generator/client/typescript/templates/[model]/[model].tstmpl @@ -107,11 +107,7 @@ export class {{ $.model.Name }} { toJSON() { return { {{- range $c := $.model.Columns }} - {{- if eq $c.Type "bigint"}} - {{ $c.Name | camel }}: Number(this.{{ $c.Name | camel }}), - {{- else }} {{ $c.Name | camel }}: this.{{ $c.Name | camel }}, - {{- end }} {{- end }} {{- range $b := $.model.BelongsTo }} {{ $b.Name | camel }}: this.{{ $b.Name | camel }}, diff --git a/generator/client/typescript/templates/queryx/adapter.mysql.ts b/generator/client/typescript/templates/queryx/adapter.mysql.ts index caf31ad1..22c21bfe 100644 --- a/generator/client/typescript/templates/queryx/adapter.mysql.ts +++ b/generator/client/typescript/templates/queryx/adapter.mysql.ts @@ -5,34 +5,46 @@ import { parse } from "date-fns"; import { Config } from "./config"; export class Adapter { + public config: Config; + public pool: mysql.Pool; public db: mysql.Pool; constructor(config: Config) { - this.db = mysql.createPool({ - uri: config.url, + this.config = config; + } + + connect() { + const pool = mysql.createPool({ + uri: this.config.url, }); + this.pool = pool; + this.db = pool; + } + + newClient() { + return this.pool.getConnection(); + } + + release() { + this.db.release(); } async query(query: string, ...args: any[]) { - console.log(query, args); let [rows] = await this.db.query(query, args); return rows; } async queryOne(query: string, ...args: any[]) { - console.log(query, args); let [rows] = await this.db.query(query, args); return rows[0] || null; } async exec(query: string, ...args: any[]) { - console.log(query, args); let [res] = await this.db.execute(query, args); return res.affectedRows; } async _exec(query: string, ...args: any[]) { - console.log(query, args); let [res] = await this.db.execute(query, args); return res; } @@ -50,8 +62,6 @@ export class Adapter { } } -export function rebind(query: string, args?: T) {} - export function adapterValue(type: string, value: any) { switch (type) { case "time": diff --git a/generator/client/typescript/templates/queryx/adapter.postgresql.ts b/generator/client/typescript/templates/queryx/adapter.postgresql.ts index 72a4aa33..d6010b81 100644 --- a/generator/client/typescript/templates/queryx/adapter.postgresql.ts +++ b/generator/client/typescript/templates/queryx/adapter.postgresql.ts @@ -7,20 +7,35 @@ import { Config } from "./config"; types.setTypeParser(types.builtins.INT8, (val) => parseInt(val, 10)); export class Adapter { - private db: Pool; + public config: Config; + public pool: Pool; + public db: Pool; constructor(config: Config) { - this.db = new Pool({ - connectionString: config.url, + this.config = config; + } + + connect() { + const pool = new Pool({ + connectionString: this.config.url, }); + this.pool = pool; + this.db = pool; + } + + newClient() { + return this.pool.connect(); + } + + release() { + this.db.release(); } private _query( query: string, - args?: I + args?: I, ) { let [query1, args1] = rebind(query, args); - console.log(query1, args1); return this.db.query(query1, args1); } diff --git a/generator/client/typescript/templates/queryx/adapter.sqlite.ts b/generator/client/typescript/templates/queryx/adapter.sqlite.ts index cf43ea53..3552a9ea 100644 --- a/generator/client/typescript/templates/queryx/adapter.sqlite.ts +++ b/generator/client/typescript/templates/queryx/adapter.sqlite.ts @@ -5,29 +5,37 @@ import { format, parse } from "date-fns"; import { Config } from "./config"; export class Adapter { + public config: Config; private db; constructor(config: Config) { - this.db = new Database(config.url); + this.config = config; } + connect() { + this.db = new Database(this.config.url); + } + + newClient() { + return new Database(this.config.url); + } + + release() {} + query(query: string, ...args: any[]): R[] { let [query1, args1] = rebind(query, args); - console.log(query1, args1); let stmt = this.db.prepare(query1); return stmt.all(...args1) as R[]; } queryOne(query: string, ...args: any[]): R { let [query1, args1] = rebind(query, args); - console.log(query1, args1); let stmt = this.db.prepare(query1); return stmt.get(...args1) as R; } async exec(query: string, ...args: any[]) { let [query1, args1] = rebind(query, args); - console.log(query1, args1); let stmt = this.db.prepare(query1); let res = stmt.run(...args1); return res.changes; diff --git a/generator/client/typescript/templates/queryx/client.tstmpl b/generator/client/typescript/templates/queryx/client.tstmpl index b7c752da..044477fa 100644 --- a/generator/client/typescript/templates/queryx/client.tstmpl +++ b/generator/client/typescript/templates/queryx/client.tstmpl @@ -2,21 +2,34 @@ import { Env, Config, newConfig, getenv } from "./config"; {{- range $m := $.client.Models }} -import { {{ $m.Name }}Query } from "../{{ $m.Name | snake }}" +import { {{ $m.Name }}Query } from "../{{ $m.Name | snake }}"; {{- end }} -import { Table, BigIntColumn, IntegerColumn, FloatColumn, BooleanColumn, StringColumn, TextColumn, DateColumn, TimeColumn, DatetimeColumn, UUIDColumn, JSONColumn } from "./table"; +import { + Table, + BigIntColumn, + IntegerColumn, + FloatColumn, + BooleanColumn, + StringColumn, + TextColumn, + DateColumn, + TimeColumn, + DatetimeColumn, + UUIDColumn, + JSONColumn, +} from "./table"; import { Adapter } from "./adapter"; import { Clause } from "./clause"; export class QXClient { public config: Config; public adapter: Adapter; - {{- range $m := $.client.Models }} - public {{ $m.Name | camel }}: Table; - {{- range $c := .Columns }} - public {{ $m.Name | camel }}{{ $c.Name | pascal }}: {{goType .Type}}Column - {{- end}} - {{- end}} + {{- range $m := $.client.Models }} + public {{ $m.Name | camel }}: Table; + {{- range $c := .Columns }} + public {{ $m.Name | camel }}{{ $c.Name | pascal }}: {{ goType .Type }}Column; + {{- end}} + {{- end}} constructor(config: Config) { this.config = config; @@ -49,23 +62,26 @@ export class QXClient { {{- end }} raw(fragment: string, ...args: any[]) { - return new Clause(fragment, args) + return new Clause(fragment, args); } async tx() { const tx = new Tx(this.config); + tx.adapter.db = await this.adapter.newClient(); await tx.adapter.beginTx(); return tx; } - async transaction(fn: (t: Tx) => void) { - const tx = await this.tx() + async transaction(fn: (t: Tx) => Promise) { + const tx = await this.tx(); try { await fn(tx); await tx.commit(); } catch (err) { await tx.rollback(); throw err; + } finally { + tx.adapter.release(); } } @@ -74,7 +90,7 @@ export class QXClient { } or(...clauses: Clause[]) { - return clauses[0].or(...clauses.slice(1)) + return clauses[0].or(...clauses.slice(1)); } } @@ -100,5 +116,6 @@ export const newClient = () => { export const newClientWithEnv = (env: Env) => { let config = newConfig(env); let client = new QXClient(config); + client.adapter.connect(); return client; }; diff --git a/internal/integration/client.test.ts b/internal/integration/client.test.ts index bb33339d..0f79614b 100644 --- a/internal/integration/client.test.ts +++ b/internal/integration/client.test.ts @@ -139,8 +139,8 @@ test("timestamps", async () => { expect(user.updatedAt).not.toBeNull(); expect(user.createdAt).toEqual(user.updatedAt); + await new Promise((resolve) => setTimeout(resolve, 10)); await user.update({ name: "new name" }); - console.log(user.createdAt, user.updatedAt); expect(user.updatedAt > user.createdAt).toBe(true); }); @@ -387,6 +387,23 @@ test("transactionBlock", async () => { expect(total).toEqual(2); }); +test("transaction with pool connection", async () => { + await c.queryTag().deleteAll(); + + let tx = await c.tx(); + let tag1 = await tx.queryTag().create({ name: "tag1" }); + + await Promise.all([ + tx.queryOne("select 1"), + tag1.update({ name: "tag1-updated" }), + ]); + + await tx.commit(); + + let tags = await c.query<{ name: string }>("select name from tags"); + expect(tags).toEqual([{ name: "tag1-updated" }]); +}); + test("changeJSON", async () => { let userChange = new UserChange({ name: "user name",