drizzle-databend 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/dist/client.d.ts +22 -0
- package/dist/columns.d.ts +70 -0
- package/dist/dialect.d.ts +10 -0
- package/dist/driver.d.ts +50 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +887 -0
- package/dist/migrator.d.ts +4 -0
- package/dist/pool.d.ts +21 -0
- package/dist/session.d.ts +55 -0
- package/dist/sql/result-mapper.d.ts +7 -0
- package/dist/sql/selection.d.ts +2 -0
- package/package.json +53 -0
- package/src/client.ts +140 -0
- package/src/columns.ts +164 -0
- package/src/dialect.ts +109 -0
- package/src/driver.ts +268 -0
- package/src/index.ts +6 -0
- package/src/migrator.ts +22 -0
- package/src/pool.ts +233 -0
- package/src/session.ts +311 -0
- package/src/sql/result-mapper.ts +234 -0
- package/src/sql/selection.ts +60 -0
package/src/driver.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Client } from 'databend-driver';
|
|
2
|
+
import { entityKind } from 'drizzle-orm/entity';
|
|
3
|
+
import type { Logger } from 'drizzle-orm/logger';
|
|
4
|
+
import { DefaultLogger } from 'drizzle-orm/logger';
|
|
5
|
+
import { PgDatabase } from 'drizzle-orm/pg-core/db';
|
|
6
|
+
import {
|
|
7
|
+
createTableRelationsHelpers,
|
|
8
|
+
extractTablesRelationalConfig,
|
|
9
|
+
type ExtractTablesWithRelations,
|
|
10
|
+
type RelationalSchemaConfig,
|
|
11
|
+
type TablesRelationalConfig,
|
|
12
|
+
} from 'drizzle-orm/relations';
|
|
13
|
+
import { type DrizzleConfig } from 'drizzle-orm/utils';
|
|
14
|
+
import type {
|
|
15
|
+
DatabendClientLike,
|
|
16
|
+
DatabendQueryResultHKT,
|
|
17
|
+
DatabendTransaction,
|
|
18
|
+
} from './session.ts';
|
|
19
|
+
import { DatabendSession } from './session.ts';
|
|
20
|
+
import { DatabendDialect } from './dialect.ts';
|
|
21
|
+
import { isPool, closeClientConnection } from './client.ts';
|
|
22
|
+
import {
|
|
23
|
+
createDatabendConnectionPool,
|
|
24
|
+
type DatabendPoolConfig,
|
|
25
|
+
} from './pool.ts';
|
|
26
|
+
import type { Connection } from 'databend-driver';
|
|
27
|
+
|
|
28
|
+
export interface DatabendDriverOptions {
|
|
29
|
+
logger?: Logger;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class DatabendDriver {
|
|
33
|
+
static readonly [entityKind]: string = 'DatabendDriver';
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
private client: DatabendClientLike,
|
|
37
|
+
private dialect: DatabendDialect,
|
|
38
|
+
private options: DatabendDriverOptions = {}
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
createSession(
|
|
42
|
+
schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined
|
|
43
|
+
): DatabendSession<Record<string, unknown>, TablesRelationalConfig> {
|
|
44
|
+
return new DatabendSession(this.client, this.dialect, schema, {
|
|
45
|
+
logger: this.options.logger,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DatabendDrizzleConfig<
|
|
51
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
52
|
+
> extends DrizzleConfig<TSchema> {
|
|
53
|
+
/** Pool configuration. Use size config or false to disable. */
|
|
54
|
+
pool?: DatabendPoolConfig | false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface DatabendDrizzleConfigWithConnection<
|
|
58
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
59
|
+
> extends DatabendDrizzleConfig<TSchema> {
|
|
60
|
+
/** Connection DSN string */
|
|
61
|
+
connection: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface DatabendDrizzleConfigWithClient<
|
|
65
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
66
|
+
> extends DatabendDrizzleConfig<TSchema> {
|
|
67
|
+
/** Explicit client (connection or pool) */
|
|
68
|
+
client: DatabendClientLike;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isConfigObject(data: unknown): data is Record<string, unknown> {
|
|
72
|
+
if (typeof data !== 'object' || data === null) return false;
|
|
73
|
+
if (data.constructor?.name !== 'Object') return false;
|
|
74
|
+
return (
|
|
75
|
+
'connection' in data ||
|
|
76
|
+
'client' in data ||
|
|
77
|
+
'pool' in data ||
|
|
78
|
+
'schema' in data ||
|
|
79
|
+
'logger' in data
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function createFromClient<
|
|
84
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
85
|
+
>(
|
|
86
|
+
client: DatabendClientLike,
|
|
87
|
+
config: DatabendDrizzleConfig<TSchema> = {},
|
|
88
|
+
databendClient?: Client
|
|
89
|
+
): DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>> {
|
|
90
|
+
const dialect = new DatabendDialect();
|
|
91
|
+
|
|
92
|
+
const logger =
|
|
93
|
+
config.logger === true ? new DefaultLogger() : config.logger || undefined;
|
|
94
|
+
|
|
95
|
+
let schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined;
|
|
96
|
+
|
|
97
|
+
if (config.schema) {
|
|
98
|
+
const tablesConfig = extractTablesRelationalConfig(
|
|
99
|
+
config.schema,
|
|
100
|
+
createTableRelationsHelpers
|
|
101
|
+
);
|
|
102
|
+
schema = {
|
|
103
|
+
fullSchema: config.schema,
|
|
104
|
+
schema: tablesConfig.tables,
|
|
105
|
+
tableNamesMap: tablesConfig.tableNamesMap,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const driver = new DatabendDriver(client, dialect, { logger });
|
|
110
|
+
const session = driver.createSession(schema);
|
|
111
|
+
|
|
112
|
+
const db = new DatabendDatabase(
|
|
113
|
+
dialect,
|
|
114
|
+
session,
|
|
115
|
+
schema,
|
|
116
|
+
client,
|
|
117
|
+
databendClient
|
|
118
|
+
);
|
|
119
|
+
return db as DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function createFromDsn<
|
|
123
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
124
|
+
>(
|
|
125
|
+
dsn: string,
|
|
126
|
+
config: DatabendDrizzleConfig<TSchema> = {}
|
|
127
|
+
): Promise<DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>> {
|
|
128
|
+
const databendClient = new Client(dsn);
|
|
129
|
+
|
|
130
|
+
if (config.pool === false) {
|
|
131
|
+
const connection = await databendClient.getConn();
|
|
132
|
+
return createFromClient(connection, config, databendClient);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const poolSize = config.pool?.size ?? 4;
|
|
136
|
+
const pool = createDatabendConnectionPool(databendClient, { size: poolSize });
|
|
137
|
+
return createFromClient(pool, config, databendClient);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Overload 1: DSN string (async, auto-pools)
|
|
141
|
+
export function drizzle<
|
|
142
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
143
|
+
>(
|
|
144
|
+
dsn: string
|
|
145
|
+
): Promise<DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
146
|
+
|
|
147
|
+
// Overload 2: DSN string + config (async, auto-pools)
|
|
148
|
+
export function drizzle<
|
|
149
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
150
|
+
>(
|
|
151
|
+
dsn: string,
|
|
152
|
+
config: DatabendDrizzleConfig<TSchema>
|
|
153
|
+
): Promise<DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
154
|
+
|
|
155
|
+
// Overload 3: Config with connection (async, auto-pools)
|
|
156
|
+
export function drizzle<
|
|
157
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
158
|
+
>(
|
|
159
|
+
config: DatabendDrizzleConfigWithConnection<TSchema>
|
|
160
|
+
): Promise<DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>>;
|
|
161
|
+
|
|
162
|
+
// Overload 4: Config with explicit client (sync)
|
|
163
|
+
export function drizzle<
|
|
164
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
165
|
+
>(
|
|
166
|
+
config: DatabendDrizzleConfigWithClient<TSchema>
|
|
167
|
+
): DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
168
|
+
|
|
169
|
+
// Overload 5: Explicit client (sync, backward compatible)
|
|
170
|
+
export function drizzle<
|
|
171
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
172
|
+
>(
|
|
173
|
+
client: DatabendClientLike,
|
|
174
|
+
config?: DatabendDrizzleConfig<TSchema>
|
|
175
|
+
): DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>;
|
|
176
|
+
|
|
177
|
+
// Implementation
|
|
178
|
+
export function drizzle<
|
|
179
|
+
TSchema extends Record<string, unknown> = Record<string, never>,
|
|
180
|
+
>(
|
|
181
|
+
clientOrConfigOrDsn:
|
|
182
|
+
| string
|
|
183
|
+
| DatabendClientLike
|
|
184
|
+
| DatabendDrizzleConfigWithConnection<TSchema>
|
|
185
|
+
| DatabendDrizzleConfigWithClient<TSchema>,
|
|
186
|
+
config?: DatabendDrizzleConfig<TSchema>
|
|
187
|
+
):
|
|
188
|
+
| DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>
|
|
189
|
+
| Promise<DatabendDatabase<TSchema, ExtractTablesWithRelations<TSchema>>> {
|
|
190
|
+
// String DSN -> async with auto-pool
|
|
191
|
+
if (typeof clientOrConfigOrDsn === 'string') {
|
|
192
|
+
return createFromDsn(clientOrConfigOrDsn, config);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Config object with connection or client
|
|
196
|
+
if (isConfigObject(clientOrConfigOrDsn)) {
|
|
197
|
+
const configObj = clientOrConfigOrDsn as
|
|
198
|
+
| DatabendDrizzleConfigWithConnection<TSchema>
|
|
199
|
+
| DatabendDrizzleConfigWithClient<TSchema>;
|
|
200
|
+
|
|
201
|
+
if ('connection' in configObj) {
|
|
202
|
+
const connConfig =
|
|
203
|
+
configObj as DatabendDrizzleConfigWithConnection<TSchema>;
|
|
204
|
+
const { connection, ...restConfig } = connConfig;
|
|
205
|
+
return createFromDsn(
|
|
206
|
+
connection,
|
|
207
|
+
restConfig as DatabendDrizzleConfig<TSchema>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if ('client' in configObj) {
|
|
212
|
+
const clientConfig = configObj as DatabendDrizzleConfigWithClient<TSchema>;
|
|
213
|
+
const { client: clientValue, ...restConfig } = clientConfig;
|
|
214
|
+
return createFromClient(
|
|
215
|
+
clientValue,
|
|
216
|
+
restConfig as DatabendDrizzleConfig<TSchema>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
throw new Error(
|
|
221
|
+
'Invalid drizzle config: either connection or client must be provided'
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Direct client (backward compatible)
|
|
226
|
+
return createFromClient(clientOrConfigOrDsn as DatabendClientLike, config);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export class DatabendDatabase<
|
|
230
|
+
TFullSchema extends Record<string, unknown> = Record<string, never>,
|
|
231
|
+
TSchema extends TablesRelationalConfig =
|
|
232
|
+
ExtractTablesWithRelations<TFullSchema>,
|
|
233
|
+
> extends PgDatabase<DatabendQueryResultHKT, TFullSchema, TSchema> {
|
|
234
|
+
static readonly [entityKind]: string = 'DatabendDatabase';
|
|
235
|
+
|
|
236
|
+
/** The underlying connection or pool */
|
|
237
|
+
readonly $client: DatabendClientLike;
|
|
238
|
+
|
|
239
|
+
/** The Databend Client instance (when created from DSN) */
|
|
240
|
+
readonly $databendClient?: Client;
|
|
241
|
+
|
|
242
|
+
constructor(
|
|
243
|
+
readonly dialect: DatabendDialect,
|
|
244
|
+
readonly session: DatabendSession<TFullSchema, TSchema>,
|
|
245
|
+
schema: RelationalSchemaConfig<TSchema> | undefined,
|
|
246
|
+
client: DatabendClientLike,
|
|
247
|
+
databendClient?: Client
|
|
248
|
+
) {
|
|
249
|
+
super(dialect, session, schema);
|
|
250
|
+
this.$client = client;
|
|
251
|
+
this.$databendClient = databendClient;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async close(): Promise<void> {
|
|
255
|
+
if (isPool(this.$client) && this.$client.close) {
|
|
256
|
+
await this.$client.close();
|
|
257
|
+
}
|
|
258
|
+
if (!isPool(this.$client)) {
|
|
259
|
+
await closeClientConnection(this.$client as Connection);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
override async transaction<T>(
|
|
264
|
+
transaction: (tx: DatabendTransaction<TFullSchema, TSchema>) => Promise<T>
|
|
265
|
+
): Promise<T> {
|
|
266
|
+
return await this.session.transaction<T>(transaction);
|
|
267
|
+
}
|
|
268
|
+
}
|
package/src/index.ts
ADDED
package/src/migrator.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MigrationConfig } from 'drizzle-orm/migrator';
|
|
2
|
+
import { readMigrationFiles } from 'drizzle-orm/migrator';
|
|
3
|
+
import type { DatabendDatabase } from './driver.ts';
|
|
4
|
+
import type { PgSession } from 'drizzle-orm/pg-core/session';
|
|
5
|
+
|
|
6
|
+
export type DatabendMigrationConfig = MigrationConfig | string;
|
|
7
|
+
|
|
8
|
+
export async function migrate<TSchema extends Record<string, unknown>>(
|
|
9
|
+
db: DatabendDatabase<TSchema>,
|
|
10
|
+
config: DatabendMigrationConfig
|
|
11
|
+
) {
|
|
12
|
+
const migrationConfig: MigrationConfig =
|
|
13
|
+
typeof config === 'string' ? { migrationsFolder: config } : config;
|
|
14
|
+
|
|
15
|
+
const migrations = readMigrationFiles(migrationConfig);
|
|
16
|
+
|
|
17
|
+
await db.dialect.migrate(
|
|
18
|
+
migrations,
|
|
19
|
+
db.session as unknown as PgSession,
|
|
20
|
+
migrationConfig
|
|
21
|
+
);
|
|
22
|
+
}
|
package/src/pool.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { Client, Connection } from 'databend-driver';
|
|
2
|
+
import { closeClientConnection, type DatabendConnectionPool } from './client.ts';
|
|
3
|
+
|
|
4
|
+
export interface DatabendPoolConfig {
|
|
5
|
+
/** Maximum concurrent connections. Defaults to 4. */
|
|
6
|
+
size?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface DatabendConnectionPoolOptions {
|
|
10
|
+
/** Maximum concurrent connections. Defaults to 4. */
|
|
11
|
+
size?: number;
|
|
12
|
+
/** Timeout in milliseconds to wait for a connection. Defaults to 30000 (30s). */
|
|
13
|
+
acquireTimeout?: number;
|
|
14
|
+
/** Maximum number of requests waiting for a connection. Defaults to 100. */
|
|
15
|
+
maxWaitingRequests?: number;
|
|
16
|
+
/** Max time (ms) a connection may live before being recycled. */
|
|
17
|
+
maxLifetimeMs?: number;
|
|
18
|
+
/** Max idle time (ms) before an idle connection is discarded. */
|
|
19
|
+
idleTimeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createDatabendConnectionPool(
|
|
23
|
+
client: Client,
|
|
24
|
+
options: DatabendConnectionPoolOptions = {}
|
|
25
|
+
): DatabendConnectionPool & { size: number } {
|
|
26
|
+
const size = options.size && options.size > 0 ? options.size : 4;
|
|
27
|
+
const acquireTimeout = options.acquireTimeout ?? 30_000;
|
|
28
|
+
const maxWaitingRequests = options.maxWaitingRequests ?? 100;
|
|
29
|
+
const maxLifetimeMs = options.maxLifetimeMs;
|
|
30
|
+
const idleTimeoutMs = options.idleTimeoutMs;
|
|
31
|
+
const metadata = new WeakMap<
|
|
32
|
+
Connection,
|
|
33
|
+
{ createdAt: number; lastUsedAt: number }
|
|
34
|
+
>();
|
|
35
|
+
|
|
36
|
+
type PooledConnection = {
|
|
37
|
+
connection: Connection;
|
|
38
|
+
createdAt: number;
|
|
39
|
+
lastUsedAt: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const idle: PooledConnection[] = [];
|
|
43
|
+
const waiting: Array<{
|
|
44
|
+
resolve: (conn: Connection) => void;
|
|
45
|
+
reject: (error: Error) => void;
|
|
46
|
+
timeoutId: ReturnType<typeof setTimeout>;
|
|
47
|
+
}> = [];
|
|
48
|
+
let total = 0;
|
|
49
|
+
let closed = false;
|
|
50
|
+
let pendingAcquires = 0;
|
|
51
|
+
|
|
52
|
+
const shouldRecycle = (conn: PooledConnection, now: number): boolean => {
|
|
53
|
+
if (maxLifetimeMs !== undefined && now - conn.createdAt >= maxLifetimeMs) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (idleTimeoutMs !== undefined && now - conn.lastUsedAt >= idleTimeoutMs) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const acquire = async (): Promise<Connection> => {
|
|
63
|
+
if (closed) {
|
|
64
|
+
throw new Error('Databend connection pool is closed');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
while (idle.length > 0) {
|
|
68
|
+
const pooled = idle.pop() as PooledConnection;
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
if (shouldRecycle(pooled, now)) {
|
|
71
|
+
await closeClientConnection(pooled.connection);
|
|
72
|
+
total = Math.max(0, total - 1);
|
|
73
|
+
metadata.delete(pooled.connection);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
pooled.lastUsedAt = now;
|
|
77
|
+
metadata.set(pooled.connection, {
|
|
78
|
+
createdAt: pooled.createdAt,
|
|
79
|
+
lastUsedAt: pooled.lastUsedAt,
|
|
80
|
+
});
|
|
81
|
+
return pooled.connection;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (total < size) {
|
|
85
|
+
pendingAcquires += 1;
|
|
86
|
+
total += 1;
|
|
87
|
+
try {
|
|
88
|
+
const connection = await client.getConn();
|
|
89
|
+
if (closed) {
|
|
90
|
+
await closeClientConnection(connection);
|
|
91
|
+
total -= 1;
|
|
92
|
+
throw new Error('Databend connection pool is closed');
|
|
93
|
+
}
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
metadata.set(connection, { createdAt: now, lastUsedAt: now });
|
|
96
|
+
return connection;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
total -= 1;
|
|
99
|
+
throw error;
|
|
100
|
+
} finally {
|
|
101
|
+
pendingAcquires -= 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (waiting.length >= maxWaitingRequests) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Databend connection pool queue is full (max ${maxWaitingRequests} waiting requests)`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return await new Promise((resolve, reject) => {
|
|
112
|
+
const timeoutId = setTimeout(() => {
|
|
113
|
+
const idx = waiting.findIndex((w) => w.timeoutId === timeoutId);
|
|
114
|
+
if (idx !== -1) {
|
|
115
|
+
waiting.splice(idx, 1);
|
|
116
|
+
}
|
|
117
|
+
reject(
|
|
118
|
+
new Error(
|
|
119
|
+
`Databend connection pool acquire timeout after ${acquireTimeout}ms`
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
}, acquireTimeout);
|
|
123
|
+
|
|
124
|
+
waiting.push({ resolve, reject, timeoutId });
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const release = async (connection: Connection): Promise<void> => {
|
|
129
|
+
const waiter = waiting.shift();
|
|
130
|
+
if (waiter) {
|
|
131
|
+
clearTimeout(waiter.timeoutId);
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
const meta =
|
|
134
|
+
metadata.get(connection) ??
|
|
135
|
+
({ createdAt: now, lastUsedAt: now } as {
|
|
136
|
+
createdAt: number;
|
|
137
|
+
lastUsedAt: number;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const expired =
|
|
141
|
+
maxLifetimeMs !== undefined && now - meta.createdAt >= maxLifetimeMs;
|
|
142
|
+
|
|
143
|
+
if (closed) {
|
|
144
|
+
await closeClientConnection(connection);
|
|
145
|
+
total = Math.max(0, total - 1);
|
|
146
|
+
metadata.delete(connection);
|
|
147
|
+
waiter.reject(new Error('Databend connection pool is closed'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (expired) {
|
|
152
|
+
await closeClientConnection(connection);
|
|
153
|
+
total = Math.max(0, total - 1);
|
|
154
|
+
metadata.delete(connection);
|
|
155
|
+
try {
|
|
156
|
+
const replacement = await acquire();
|
|
157
|
+
waiter.resolve(replacement);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
waiter.reject(error as Error);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
meta.lastUsedAt = now;
|
|
165
|
+
metadata.set(connection, meta);
|
|
166
|
+
waiter.resolve(connection);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (closed) {
|
|
171
|
+
await closeClientConnection(connection);
|
|
172
|
+
metadata.delete(connection);
|
|
173
|
+
total = Math.max(0, total - 1);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
const existingMeta =
|
|
179
|
+
metadata.get(connection) ??
|
|
180
|
+
({ createdAt: now, lastUsedAt: now } as {
|
|
181
|
+
createdAt: number;
|
|
182
|
+
lastUsedAt: number;
|
|
183
|
+
});
|
|
184
|
+
existingMeta.lastUsedAt = now;
|
|
185
|
+
metadata.set(connection, existingMeta);
|
|
186
|
+
|
|
187
|
+
if (
|
|
188
|
+
maxLifetimeMs !== undefined &&
|
|
189
|
+
now - existingMeta.createdAt >= maxLifetimeMs
|
|
190
|
+
) {
|
|
191
|
+
await closeClientConnection(connection);
|
|
192
|
+
total -= 1;
|
|
193
|
+
metadata.delete(connection);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
idle.push({
|
|
198
|
+
connection,
|
|
199
|
+
createdAt: existingMeta.createdAt,
|
|
200
|
+
lastUsedAt: existingMeta.lastUsedAt,
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const close = async (): Promise<void> => {
|
|
205
|
+
closed = true;
|
|
206
|
+
|
|
207
|
+
const waiters = waiting.splice(0, waiting.length);
|
|
208
|
+
for (const waiter of waiters) {
|
|
209
|
+
clearTimeout(waiter.timeoutId);
|
|
210
|
+
waiter.reject(new Error('Databend connection pool is closed'));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const toClose = idle.splice(0, idle.length);
|
|
214
|
+
await Promise.allSettled(
|
|
215
|
+
toClose.map((item) => closeClientConnection(item.connection))
|
|
216
|
+
);
|
|
217
|
+
total = Math.max(0, total - toClose.length);
|
|
218
|
+
toClose.forEach((item) => metadata.delete(item.connection));
|
|
219
|
+
|
|
220
|
+
const maxWait = 5000;
|
|
221
|
+
const start = Date.now();
|
|
222
|
+
while (pendingAcquires > 0 && Date.now() - start < maxWait) {
|
|
223
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
acquire,
|
|
229
|
+
release,
|
|
230
|
+
close,
|
|
231
|
+
size,
|
|
232
|
+
};
|
|
233
|
+
}
|