From 1d93b4d7fa177870add788450a4f4792948f41e7 Mon Sep 17 00:00:00 2001 From: Pro <1134781076@qq.com> Date: Mon, 22 Dec 2025 12:15:31 +0800 Subject: [PATCH 1/2] feat: add mysql2-pool and postgresql-pool connector --- src/_connectors.ts | 6 +++ src/connectors/mysql2-pool.ts | 76 +++++++++++++++++++++++++++++++ src/connectors/postgresql-pool.ts | 72 +++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 src/connectors/mysql2-pool.ts create mode 100644 src/connectors/postgresql-pool.ts diff --git a/src/_connectors.ts b/src/_connectors.ts index e0171c6..f442ac7 100644 --- a/src/_connectors.ts +++ b/src/_connectors.ts @@ -10,10 +10,12 @@ import type { ConnectorOptions as LibSQLHttpOptions } from "db0/connectors/libsq import type { ConnectorOptions as LibSQLNodeOptions } from "db0/connectors/libsql/node"; import type { ConnectorOptions as LibSQLWebOptions } from "db0/connectors/libsql/web"; import type { ConnectorOptions as MySQL2Options } from "db0/connectors/mysql2"; +import type { ConnectorOptions as MySQL2PoolOptions } from "db0/connectors/mysql2-pool"; import type { ConnectorOptions as NodeSQLiteOptions } from "db0/connectors/node-sqlite"; import type { ConnectorOptions as PgliteOptions } from "db0/connectors/pglite"; import type { ConnectorOptions as PlanetscaleOptions } from "db0/connectors/planetscale"; import type { ConnectorOptions as PostgreSQLOptions } from "db0/connectors/postgresql"; +import type { ConnectorOptions as PostgrePoolSQLOptions } from "db0/connectors/postgresql-pool"; import type { ConnectorOptions as SQLite3Options } from "db0/connectors/sqlite3"; export type ConnectorName = "better-sqlite3" | "bun-sqlite" | "bun" | "cloudflare-d1" | "cloudflare-hyperdrive-mysql" | "cloudflare-hyperdrive-postgresql" | "libsql-core" | "libsql-http" | "libsql-node" | "libsql" | "libsql-web" | "mysql2" | "node-sqlite" | "sqlite" | "pglite" | "planetscale" | "postgresql" | "sqlite3"; @@ -33,12 +35,14 @@ export type ConnectorOptions = { "libsql": LibSQLNodeOptions; "libsql-web": LibSQLWebOptions; "mysql2": MySQL2Options; + "mysql2-pool": MySQL2PoolOptions; "node-sqlite": NodeSQLiteOptions; /** alias of node-sqlite */ "sqlite": NodeSQLiteOptions; "pglite": PgliteOptions; "planetscale": PlanetscaleOptions; "postgresql": PostgreSQLOptions; + "postgresql-pool": PostgrePoolSQLOptions; "sqlite3": SQLite3Options; }; @@ -57,11 +61,13 @@ export const connectors: Record = Object.freeze({ "libsql": "db0/connectors/libsql/node", "libsql-web": "db0/connectors/libsql/web", "mysql2": "db0/connectors/mysql2", + "mysql2-pool": "db0/connectors/mysql2-pool", "node-sqlite": "db0/connectors/node-sqlite", /** alias of node-sqlite */ "sqlite": "db0/connectors/node-sqlite", "pglite": "db0/connectors/pglite", "planetscale": "db0/connectors/planetscale", "postgresql": "db0/connectors/postgresql", + "postgresql-pool": "db0/connectors/postgresql-pool", "sqlite3": "db0/connectors/sqlite3", } as const); diff --git a/src/connectors/mysql2-pool.ts b/src/connectors/mysql2-pool.ts new file mode 100644 index 0000000..d2c9dbe --- /dev/null +++ b/src/connectors/mysql2-pool.ts @@ -0,0 +1,76 @@ +import mysql from "mysql2/promise"; +import type { Connector, Primitive } from "db0"; +import { BoundableStatement } from "./_internal/statement.ts"; + +export type ConnectorOptions = mysql.PoolOptions; + +type InternalQuery = ( + sql: string, + params?: unknown[], +) => Promise; + +export default function mysqlConnector( + opts: ConnectorOptions, +): Connector { + let _pool: mysql.Pool | undefined; + const getConnection = async () => { + if (!_pool) { + _pool = mysql.createPool({ + ...opts, + }); + } + const _connection : mysql.PoolConnection = await _pool.getConnection(); + return _connection; + }; + + const query: InternalQuery = async (sql, params) => { + const connection = await getConnection(); + try { + const result = await connection.query(sql, params); + return result[0]; + } finally { + connection.release(); + } + } + + return { + name: "mysql-pool", + dialect: "mysql", + getInstance: () => getConnection(), + exec: (sql) => query(sql), + prepare: (sql) => new StatementWrapper(sql, query), + dispose: async () => { + await _pool?.end?.(); + _pool = undefined; + }, + }; +} + +class StatementWrapper extends BoundableStatement { + #query: InternalQuery; + #sql: string; + + constructor(sql: string, query: InternalQuery) { + super(); + this.#sql = sql; + this.#query = query; + } + + async all(...params: Primitive[]) { + const res = (await this.#query(this.#sql, params)) as mysql.RowDataPacket[]; + return res; + } + + async run(...params: Primitive[]) { + const res = (await this.#query(this.#sql, params)) as mysql.RowDataPacket[]; + return { + success: true, + ...res, + }; + } + + async get(...params: Primitive[]) { + const res = (await this.#query(this.#sql, params)) as mysql.RowDataPacket[]; + return res[0]; + } +} diff --git a/src/connectors/postgresql-pool.ts b/src/connectors/postgresql-pool.ts new file mode 100644 index 0000000..efe28ef --- /dev/null +++ b/src/connectors/postgresql-pool.ts @@ -0,0 +1,72 @@ +import pg from "pg"; +import type { Connector, Primitive } from "db0"; +import { BoundableStatement } from "./_internal/statement.ts"; + +export type ConnectorOptions = pg.PoolConfig; + +type InternalQuery = ( + sql: string, + params?: Primitive[], +) => Promise; + +export default function postgresqlConnector( + opts: ConnectorOptions, +): Connector { + let _pool: undefined | pg.Pool; + function getClient() { + if (!_pool) _pool = new pg.Pool(opts); + return _pool.connect(); + } + + const query: InternalQuery = async (sql, params) => { + const client = await getClient(); + return client.query(normalizeParams(sql), params); + }; + + return { + name: "postgresql-pool", + dialect: "postgresql", + getInstance: () => getClient(), + exec: (sql) => query(sql), + prepare: (sql) => new StatementWrapper(sql, query), + dispose: async () => { + await (await _pool)?.end?.(); + _pool = undefined; + }, + }; +} + +// https://www.postgresql.org/docs/9.3/sql-prepare.html +function normalizeParams(sql: string) { + let i = 0; + return sql.replace(/\?/g, () => `$${++i}`); +} + +class StatementWrapper extends BoundableStatement { + #query: InternalQuery; + #sql: string; + + constructor(sql: string, query: InternalQuery) { + super(); + this.#sql = sql; + this.#query = query; + } + + async all(...params: Primitive[]) { + const res = await this.#query(this.#sql, params); + return res.rows; + } + + async run(...params: Primitive[]) { + const res = await this.#query(this.#sql, params); + return { + success: true, + ...res, + }; + } + + async get(...params: Primitive[]) { + const res = await this.#query(this.#sql, params); + return res.rows[0]; + } +} From ce4b03348e90141a41445df81a977a940073558a Mon Sep 17 00:00:00 2001 From: Pro <1134781076@qq.com> Date: Mon, 22 Dec 2025 14:31:41 +0800 Subject: [PATCH 2/2] feat: apply database wrapper to pool-connection instance --- docs/2.connectors/mysql-pool.md | 73 +++++++++++++++++++++++++++ docs/2.connectors/postgresql-pool.md | 75 ++++++++++++++++++++++++++++ src/connectors/mysql2-pool.ts | 55 +++++++++++++++----- src/connectors/postgresql-pool.ts | 43 +++++++++++++--- 4 files changed, 228 insertions(+), 18 deletions(-) create mode 100644 docs/2.connectors/mysql-pool.md create mode 100644 docs/2.connectors/postgresql-pool.md diff --git a/docs/2.connectors/mysql-pool.md b/docs/2.connectors/mysql-pool.md new file mode 100644 index 0000000..3489685 --- /dev/null +++ b/docs/2.connectors/mysql-pool.md @@ -0,0 +1,73 @@ +--- +icon: simple-icons:mysql +--- +# MySQL (Pool) + +> Connect DB0 to Mysql Database using mysql2 pool connection + +## Usage + +For this connector, you need to install [`mysql2`](https://www.npmjs.com/package/mysql2) dependency: + +:pm-install{name="mysql2"} + +Use `mysql2-pool` connector: + +```js +import { createDatabase } from "db0"; +import mysqlPool from "db0/connectors/mysql2-pool"; + +const db = createDatabase( + mysqlPool({ + /* options */ + }), +); +``` + +### Pool Query + +```js +const {rows} = db.sql`SELECT * FROM tbl`; +``` + +### Transactions + +For pool connections, do not use transactions with the **pool.query** method. + +:read-more{title="node-postgres transactions" to="https://pg.nodejs.cn/features/transactions"} + + + +To use transactions, get an instance first and dispose after finished. + +```js +const c = await db.getInstance(); +await c.sql`BEGIN`; +await c.sql`insert into test (name) values ('TEST1')`; +await c.sql`COMMIT`; +await c.sql`BEGIN`; +await c.sql`insert into test (name) values ('TEST2')`; +await c.sql`ROLLBACK`; +c.dispose(); +``` + +The pid can test by: + +```js +// Pool query: different pids +new Array(10).fill(1).forEach(async () => { + const {rows} = await db.sql`SELECT pg_backend_pid()`; + console.log(rows[0]); +}); + +// PoolConnection query: the same pid +const c = await db.getInstance(); +new Array(10).fill(1).forEach(async () => { + const {rows} = await c.sql`SELECT pg_backend_pid()`; + console.log(rows[0]); +}); +``` + +Options + +:read-more{to="https://github.com/sidorares/node-mysql2/blob/master/typings/mysql/lib/Connection.d.ts#L82-L329"} diff --git a/docs/2.connectors/postgresql-pool.md b/docs/2.connectors/postgresql-pool.md new file mode 100644 index 0000000..0e5bfbd --- /dev/null +++ b/docs/2.connectors/postgresql-pool.md @@ -0,0 +1,75 @@ +--- +icon: simple-icons:postgresql +--- +# PostgreSQL (Pool) + +> Connect DB0 to PostgreSQL + +:read-more{to="https://www.postgresql.org"} + +## Usage + +For this connector, you need to install [`pg`](https://www.npmjs.com/package/pg) dependency: + +:pm-install{name="pg @types/pg"} + +Use `postgresql-pool` connector: + +```js +import { createDatabase } from "db0"; +import postgresqlPool from "db0/connectors/postgresql-pool"; + +const db = createDatabase( + postgresqlPool({ + /* options */ + }), +); +``` + +### Pool Query + +```js +const {rows} = db.sql`SELECT * FROM tbl`; +``` + +### Transactions + +For pool connections, do not use transactions with the **pool.query** method. + +:read-more{title="node-postgres transactions" to="https://pg.nodejs.cn/features/transactions"} + + + +To use transactions, get an instance first and dispose after finished. + +```js +const c = await db.getInstance(); +await c.sql`BEGIN`; +await c.sql`insert into test (name) values ('TEST1')`; +await c.sql`COMMIT`; +await c.sql`BEGIN`; +await c.sql`insert into test (name) values ('TEST2')`; +await c.sql`ROLLBACK`; +c.dispose(); +``` + +The pid can test by: + +```js +// Pool query: different pids +new Array(10).fill(1).forEach(async () => { + const {rows} = await db.sql`SELECT pg_backend_pid()`; + console.log(rows[0]); +}); + +// PoolConnection query: the same pid +const c = await db.getInstance(); +new Array(10).fill(1).forEach(async () => { + const {rows} = await c.sql`SELECT pg_backend_pid()`; + console.log(rows[0]); +}); +``` + +## Options + +:read-more{title="node-postgres client options" to="https://node-postgres.com/apis/client#new-client"} diff --git a/src/connectors/mysql2-pool.ts b/src/connectors/mysql2-pool.ts index d2c9dbe..12d69ca 100644 --- a/src/connectors/mysql2-pool.ts +++ b/src/connectors/mysql2-pool.ts @@ -1,4 +1,5 @@ import mysql from "mysql2/promise"; +import { createDatabase } from "../database.ts"; import type { Connector, Primitive } from "db0"; import { BoundableStatement } from "./_internal/statement.ts"; @@ -9,29 +10,59 @@ type InternalQuery = ( params?: unknown[], ) => Promise; -export default function mysqlConnector( +export default function mysqlPoolConnector( opts: ConnectorOptions, ): Connector { let _pool: mysql.Pool | undefined; - const getConnection = async () => { + const getPool = () => { if (!_pool) { _pool = mysql.createPool({ ...opts, }); } - const _connection : mysql.PoolConnection = await _pool.getConnection(); - return _connection; + return _pool; + }; + const getConnection = async () => { + let _connection: mysql.PoolConnection | undefined; + let _connectionPromise: Promise | undefined; + const _getConnection = async () => { + if (!_connection) { + if (!_connectionPromise) { + _connectionPromise = getPool().getConnection().then(connection => { + _connection = connection; + _connectionPromise = undefined; + return connection; + }); + } + return _connectionPromise; + } + return _connection; + }; + const _query: InternalQuery = (sql, params) => + _getConnection() + .then((c) => c.query(sql, params)) + .then((res) => res[0]); + return createDatabase({ + name: "mysql-pool-connection", + dialect: "mysql", + getInstance: () => _getConnection(), + exec: (sql) => _query(sql), + prepare: (sql) => new StatementWrapper(sql, _query), + dispose: async () => { + _connection?.release?.(); + _connection = undefined; + }, + }); }; const query: InternalQuery = async (sql, params) => { - const connection = await getConnection(); - try { - const result = await connection.query(sql, params); - return result[0]; - } finally { - connection.release(); - } - } + const pool = getPool(); + try { + const result = await pool.query(sql, params); + return result[0]; + } finally { + } + }; return { name: "mysql-pool", diff --git a/src/connectors/postgresql-pool.ts b/src/connectors/postgresql-pool.ts index efe28ef..7fef057 100644 --- a/src/connectors/postgresql-pool.ts +++ b/src/connectors/postgresql-pool.ts @@ -1,4 +1,5 @@ import pg from "pg"; +import { createDatabase } from "../database.ts"; import type { Connector, Primitive } from "db0"; import { BoundableStatement } from "./_internal/statement.ts"; @@ -9,18 +10,48 @@ type InternalQuery = ( params?: Primitive[], ) => Promise; -export default function postgresqlConnector( +export default function postgresqlPoolConnector( opts: ConnectorOptions, ): Connector { let _pool: undefined | pg.Pool; - function getClient() { + const getPool = () => { if (!_pool) _pool = new pg.Pool(opts); - return _pool.connect(); - } + return _pool; + }; + const getClient = async () => { + let _client: pg.PoolClient | undefined; + let _clientPromise: Promise | undefined; + const _getClient = async () => { + if (!_client) { + if (!_clientPromise) { + _clientPromise = getPool().connect().then(client => { + _client = client; + _clientPromise = undefined; + return client; + }); + } + return _clientPromise; + } + return _client; + }; + const _query: InternalQuery = (sql, params) => + _getClient().then((c) => c.query(normalizeParams(sql), params)); + return createDatabase({ + name: "postgresql-pool-client", + dialect: "postgresql", + getInstance: () => _getClient(), + exec: (sql) => _query(sql), + prepare: (sql) => new StatementWrapper(sql, _query), + dispose: async () => { + _client?.release?.(); + _client = undefined; + }, + }); + }; const query: InternalQuery = async (sql, params) => { - const client = await getClient(); - return client.query(normalizeParams(sql), params); + const pool = getPool(); + return pool.query(normalizeParams(sql), params); }; return {