@nextlyhq/adapter-mysql 0.0.2-alpha.72 → 0.0.2-alpha.74
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/dist/index.cjs +231 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -4
- package/dist/index.d.ts +75 -4
- package/dist/index.mjs +232 -81
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DrizzleAdapter } from "@nextlyhq/adapter-drizzle";
|
|
2
|
-
import { AdapterLogger, BaseAdapterConfig, DatabaseCapabilities, DatabaseError, DatabaseErrorKind, DeleteOptions, InsertOptions, JoinSpec, MySqlAdapterConfig, OrderBySpec, PoolConfig, PoolStats, SelectOptions, SqlParam, SslConfig, TransactionContext, TransactionOptions, UpdateOptions, UpsertOptions, WhereClause, WhereCondition, WhereOperator } from "@nextlyhq/adapter-drizzle/types";
|
|
2
|
+
import { AdapterLogger, BaseAdapterConfig, DatabaseCapabilities, DatabaseError, DatabaseErrorKind, DeleteOptions, InsertOptions, JoinSpec, MySqlAdapterConfig, OrderBySpec, PoolConfig, PoolStats, SelectOptions, SessionOptions, SqlParam, SslConfig, StatementSession, TransactionContext, TransactionOptions, UpdateOptions, UpsertOptions, WhereClause, WhereCondition, WhereOperator } from "@nextlyhq/adapter-drizzle/types";
|
|
3
3
|
import { AnyRelations } from "drizzle-orm";
|
|
4
4
|
import { MySql2Database } from "drizzle-orm/mysql2";
|
|
5
5
|
/**
|
|
@@ -45,6 +45,8 @@ export declare class MySqlAdapter extends DrizzleAdapter {
|
|
|
45
45
|
* Connection pool instance.
|
|
46
46
|
*/
|
|
47
47
|
private pool;
|
|
48
|
+
/** The `withSession` calls running, each on a connection of its own, for `disconnect` to wait for. */
|
|
49
|
+
private readonly sessions;
|
|
48
50
|
/**
|
|
49
51
|
* Connection state flag.
|
|
50
52
|
*/
|
|
@@ -54,6 +56,7 @@ export declare class MySqlAdapter extends DrizzleAdapter {
|
|
|
54
56
|
* string at connect. Undefined until then, which callers treat as not known.
|
|
55
57
|
*/
|
|
56
58
|
private sharedRowLock;
|
|
59
|
+
private functionalIndexes;
|
|
57
60
|
/**
|
|
58
61
|
* Creates a new MySQL adapter instance.
|
|
59
62
|
*
|
|
@@ -78,7 +81,8 @@ export declare class MySqlAdapter extends DrizzleAdapter {
|
|
|
78
81
|
*
|
|
79
82
|
* @remarks
|
|
80
83
|
* This method is idempotent - calling it multiple times is safe.
|
|
81
|
-
* It waits for all connections to be released before shutting down
|
|
84
|
+
* It waits for all connections to be released before shutting down,
|
|
85
|
+
* including each running `withSession`'s connection of its own.
|
|
82
86
|
*/
|
|
83
87
|
disconnect(): Promise<void>;
|
|
84
88
|
/**
|
|
@@ -119,10 +123,46 @@ export declare class MySqlAdapter extends DrizzleAdapter {
|
|
|
119
123
|
*
|
|
120
124
|
* Note: Savepoints are disabled in this adapter for safety due to
|
|
121
125
|
* MySQL's quirks with nested transactions.
|
|
126
|
+
*
|
|
127
|
+
* A call made inside another transaction's work therefore joins it flat,
|
|
128
|
+
* on its connection: its statements are the outer's. Its failure cannot be
|
|
129
|
+
* undone alone, so it fails the whole transaction, even when the outer
|
|
130
|
+
* catches it: the outer's COMMIT is refused, it rolls back, and it rejects
|
|
131
|
+
* with the error that failed the joined call; any later joined call fails
|
|
132
|
+
* with that error at once. A joined call keeps the outer's options and is
|
|
133
|
+
* never retried itself. `retryWhen` is asked first, of every error that
|
|
134
|
+
* ends an attempt, application ones included.
|
|
122
135
|
*/
|
|
123
136
|
transaction<T>(work: (tx: TransactionContext) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
124
|
-
/**
|
|
125
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Runs `work` on a connection of its own, opened for it with the pool's
|
|
139
|
+
* connection settings, outside a transaction, each statement committing as
|
|
140
|
+
* it runs.
|
|
141
|
+
*
|
|
142
|
+
* @remarks
|
|
143
|
+
* Not a pooled connection: on a server whose sessions start with autocommit
|
|
144
|
+
* off, one can hold a transaction an earlier caller left open, and turning
|
|
145
|
+
* autocommit on would commit that caller's writes. A new connection holds
|
|
146
|
+
* nothing. It does not count against `pool.max`.
|
|
147
|
+
*
|
|
148
|
+
* Each statement commits as it runs, whatever the server's own autocommit
|
|
149
|
+
* default. With `strictSqlMode` the session runs in strict mode, as a
|
|
150
|
+
* transaction given the same option does, so a statement that would cut a
|
|
151
|
+
* value short fails and changes nothing. The connection is closed
|
|
152
|
+
* afterwards, whether `work` finished or threw, so whatever `work` set on
|
|
153
|
+
* its session (a temporary table, a session variable, a lock) ends with it.
|
|
154
|
+
*/
|
|
155
|
+
override withSession<T>(work: (session: StatementSession) => Promise<T>, options?: SessionOptions): Promise<T>;
|
|
156
|
+
/** `withSession`'s work, on a connection it opens and closes. */
|
|
157
|
+
private runSession;
|
|
158
|
+
/** One attempt at an outermost transaction, on a connection of its own. */
|
|
159
|
+
private runAttempt;
|
|
160
|
+
/**
|
|
161
|
+
* How a call made inside a transaction on `connection` joins it: flat, on
|
|
162
|
+
* the same connection, with nothing to send. Any failure inside it fails
|
|
163
|
+
* the whole transaction, since no savepoint can undo the part alone.
|
|
164
|
+
*/
|
|
165
|
+
private joinSteps;
|
|
126
166
|
/**
|
|
127
167
|
* Get MySQL database capabilities.
|
|
128
168
|
*
|
|
@@ -181,16 +221,47 @@ export declare class MySqlAdapter extends DrizzleAdapter {
|
|
|
181
221
|
* `transactionOpenInFlow`, as the adapter's does.
|
|
182
222
|
*/
|
|
183
223
|
private poolDrizzle;
|
|
224
|
+
/**
|
|
225
|
+
* Warns when a caller's configured driver zone does not agree with the
|
|
226
|
+
* pinned UTC session.
|
|
227
|
+
*
|
|
228
|
+
* mysql2's driver-level `timezone` decides how it parses the date strings
|
|
229
|
+
* the pinned session answers with and how it binds raw Date parameters, so
|
|
230
|
+
* a non-UTC spelling there would shift values a round-trip fails to
|
|
231
|
+
* cancel. The configuration is aligned to the pin (`buildConnectionConfig`
|
|
232
|
+
* sets UTC unconditionally, for pooled and standalone connections alike),
|
|
233
|
+
* never rewritten silently: the caller configured a reading, and this is
|
|
234
|
+
* where that reading is named as lost. Runs from `connect`, once per pool
|
|
235
|
+
* build — the configuration cannot change between calls.
|
|
236
|
+
*/
|
|
237
|
+
private warnOnDriverZoneDisagreement;
|
|
184
238
|
/**
|
|
185
239
|
* Builds mysql2 Pool configuration from adapter config.
|
|
186
240
|
*/
|
|
187
241
|
private buildPoolConfig;
|
|
242
|
+
/**
|
|
243
|
+
* Builds the settings every connection opens with, pooled or not, from
|
|
244
|
+
* adapter config.
|
|
245
|
+
*/
|
|
246
|
+
private buildConnectionConfig;
|
|
247
|
+
/** Where to connect: the connection string, or the explicit options. */
|
|
248
|
+
private buildAddressConfig;
|
|
249
|
+
/** mysql2's `ssl` option for the adapter's `ssl` setting. */
|
|
250
|
+
private buildSslConfig;
|
|
188
251
|
/**
|
|
189
252
|
* Begins a transaction with the specified options, recording in `changed`
|
|
190
253
|
* each session setting it changes, as it changes it, for `restoreSession`
|
|
191
254
|
* to put back.
|
|
192
255
|
*/
|
|
193
256
|
private beginTransaction;
|
|
257
|
+
/**
|
|
258
|
+
* Strict mode for the rest of this connection's session: an INSERT, UPDATE
|
|
259
|
+
* or ALTER that would cut a string to its column's length, or clamp a
|
|
260
|
+
* number to its range, fails instead of applying with a warning. Added to
|
|
261
|
+
* the session's own mode, so every other flag it had still holds, and
|
|
262
|
+
* recorded in `changed` for `restoreSession` to put back.
|
|
263
|
+
*/
|
|
264
|
+
private enterStrictMode;
|
|
194
265
|
/**
|
|
195
266
|
* Creates a TransactionContext for the given connection.
|
|
196
267
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DrizzleAdapter } from "@nextlyhq/adapter-drizzle";
|
|
2
|
-
import { createDatabaseError,
|
|
2
|
+
import { createDatabaseError, isDatabaseError, poolWaitTimeoutError } from "@nextlyhq/adapter-drizzle/types";
|
|
3
3
|
import { acceptsForShare, checkDialectVersion } from "@nextlyhq/adapter-drizzle/version-check";
|
|
4
4
|
import { and, eq, getColumns } from "drizzle-orm";
|
|
5
5
|
import { getTableConfig } from "drizzle-orm/mysql-core";
|
|
@@ -145,6 +145,16 @@ async function returnTransactionConnection(connection, rollbackFailed, restore)
|
|
|
145
145
|
else connection.destroy();
|
|
146
146
|
}
|
|
147
147
|
/**
|
|
148
|
+
* mysql2 accepts only `local`, `Z` and `±HH:MM` spellings for its driver
|
|
149
|
+
* zone, and the ones that resolve to UTC are exactly these. `local` resolves
|
|
150
|
+
* to the Node process's own zone, so it agrees with a UTC session only on a
|
|
151
|
+
* UTC host.
|
|
152
|
+
*/
|
|
153
|
+
function isUtcDriverZone(zone) {
|
|
154
|
+
if (zone === "local") return (/* @__PURE__ */ new Date()).getTimezoneOffset() === 0;
|
|
155
|
+
return zone === "Z" || zone === "+00:00" || zone === "-00:00";
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
148
158
|
* Package version
|
|
149
159
|
*/
|
|
150
160
|
const VERSION = "0.1.0";
|
|
@@ -191,11 +201,19 @@ const MYSQL_ERROR_CODES = {
|
|
|
191
201
|
1146: "query",
|
|
192
202
|
1054: "query"
|
|
193
203
|
};
|
|
204
|
+
/** ER_LOCK_DEADLOCK: the transaction lost a race, and running it again may succeed. */
|
|
205
|
+
const DEADLOCK = 1213;
|
|
194
206
|
/**
|
|
195
|
-
*
|
|
207
|
+
* The MySQL error number an error carries: its own, or that of the driver
|
|
208
|
+
* error it wraps, as a classified or a Drizzle query error does.
|
|
196
209
|
*/
|
|
197
|
-
function
|
|
198
|
-
|
|
210
|
+
function errnoOf(error) {
|
|
211
|
+
for (let at = error, hops = 0; hops < 4; hops++) {
|
|
212
|
+
if (typeof at !== "object" || at === null) return void 0;
|
|
213
|
+
const errno = Reflect.get(at, "errno");
|
|
214
|
+
if (typeof errno === "number") return errno;
|
|
215
|
+
at = Reflect.get(at, "cause");
|
|
216
|
+
}
|
|
199
217
|
}
|
|
200
218
|
/**
|
|
201
219
|
* MySQL database adapter for Nextly.
|
|
@@ -236,6 +254,8 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
236
254
|
* Connection pool instance.
|
|
237
255
|
*/
|
|
238
256
|
pool = null;
|
|
257
|
+
/** The `withSession` calls running, each on a connection of its own, for `disconnect` to wait for. */
|
|
258
|
+
sessions = /* @__PURE__ */ new Set();
|
|
239
259
|
/**
|
|
240
260
|
* Connection state flag.
|
|
241
261
|
*/
|
|
@@ -245,6 +265,7 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
245
265
|
* string at connect. Undefined until then, which callers treat as not known.
|
|
246
266
|
*/
|
|
247
267
|
sharedRowLock;
|
|
268
|
+
functionalIndexes;
|
|
248
269
|
/**
|
|
249
270
|
* Creates a new MySQL adapter instance.
|
|
250
271
|
*
|
|
@@ -268,15 +289,22 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
268
289
|
async connect() {
|
|
269
290
|
if (this.connected && this.pool) return;
|
|
270
291
|
try {
|
|
292
|
+
this.warnOnDriverZoneDisagreement();
|
|
271
293
|
const poolConfig = this.buildPoolConfig();
|
|
272
294
|
const pool = mysql.createPool(poolConfig);
|
|
295
|
+
pool.pool.on("connection", (connection) => {
|
|
296
|
+
connection.query("SET SESSION time_zone = '+00:00'", (error) => {
|
|
297
|
+
if (error) connection.destroy();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
273
300
|
boundPoolWait(pool.pool, poolConfig.connectTimeout ?? 0);
|
|
274
301
|
this.pool = pool;
|
|
275
302
|
const connection = await this.pool.getConnection();
|
|
276
303
|
try {
|
|
277
304
|
await connection.query("SELECT 1");
|
|
278
|
-
const { variant } = await checkDialectVersion(connection, "mysql", { onWarning: (msg) => this.config.logger?.warn?.(msg) });
|
|
305
|
+
const { variant, functionalIndexes } = await checkDialectVersion(connection, "mysql", { onWarning: (msg) => this.config.logger?.warn?.(msg) });
|
|
279
306
|
this.sharedRowLock = acceptsForShare(variant);
|
|
307
|
+
this.functionalIndexes = functionalIndexes;
|
|
280
308
|
this.connected = true;
|
|
281
309
|
if (this.config.logger?.info) this.config.logger.info("MySQL connection established", {
|
|
282
310
|
host: this.config.host ?? "from URL",
|
|
@@ -299,7 +327,8 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
299
327
|
*
|
|
300
328
|
* @remarks
|
|
301
329
|
* This method is idempotent - calling it multiple times is safe.
|
|
302
|
-
* It waits for all connections to be released before shutting down
|
|
330
|
+
* It waits for all connections to be released before shutting down,
|
|
331
|
+
* including each running `withSession`'s connection of its own.
|
|
303
332
|
*/
|
|
304
333
|
async disconnect() {
|
|
305
334
|
const pool = this.pool;
|
|
@@ -308,6 +337,7 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
308
337
|
this.drizzleByRelations = /* @__PURE__ */ new WeakMap();
|
|
309
338
|
if (!pool) return;
|
|
310
339
|
try {
|
|
340
|
+
await Promise.allSettled([...this.sessions]);
|
|
311
341
|
await pool.end();
|
|
312
342
|
if (this.config.logger?.info) this.config.logger.info("MySQL connection closed");
|
|
313
343
|
} finally {
|
|
@@ -385,54 +415,131 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
385
415
|
*
|
|
386
416
|
* Note: Savepoints are disabled in this adapter for safety due to
|
|
387
417
|
* MySQL's quirks with nested transactions.
|
|
418
|
+
*
|
|
419
|
+
* A call made inside another transaction's work therefore joins it flat,
|
|
420
|
+
* on its connection: its statements are the outer's. Its failure cannot be
|
|
421
|
+
* undone alone, so it fails the whole transaction, even when the outer
|
|
422
|
+
* catches it: the outer's COMMIT is refused, it rolls back, and it rejects
|
|
423
|
+
* with the error that failed the joined call; any later joined call fails
|
|
424
|
+
* with that error at once. A joined call keeps the outer's options and is
|
|
425
|
+
* never retried itself. `retryWhen` is asked first, of every error that
|
|
426
|
+
* ends an attempt, application ones included.
|
|
388
427
|
*/
|
|
389
|
-
|
|
390
|
-
return this.
|
|
428
|
+
transaction(work, options) {
|
|
429
|
+
return this.openOrJoin(work, options, {
|
|
430
|
+
run: (_attempt, openedAt) => this.runAttempt(work, options, openedAt),
|
|
431
|
+
retryable: (error) => errnoOf(error) === DEADLOCK,
|
|
432
|
+
describe: (error) => {
|
|
433
|
+
const errno = errnoOf(error);
|
|
434
|
+
return {
|
|
435
|
+
label: errno === DEADLOCK ? "deadlock" : "a retryable error",
|
|
436
|
+
detail: { errno }
|
|
437
|
+
};
|
|
438
|
+
},
|
|
439
|
+
logger: this.config.logger
|
|
440
|
+
});
|
|
391
441
|
}
|
|
392
|
-
/**
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
442
|
+
/**
|
|
443
|
+
* Runs `work` on a connection of its own, opened for it with the pool's
|
|
444
|
+
* connection settings, outside a transaction, each statement committing as
|
|
445
|
+
* it runs.
|
|
446
|
+
*
|
|
447
|
+
* @remarks
|
|
448
|
+
* Not a pooled connection: on a server whose sessions start with autocommit
|
|
449
|
+
* off, one can hold a transaction an earlier caller left open, and turning
|
|
450
|
+
* autocommit on would commit that caller's writes. A new connection holds
|
|
451
|
+
* nothing. It does not count against `pool.max`.
|
|
452
|
+
*
|
|
453
|
+
* Each statement commits as it runs, whatever the server's own autocommit
|
|
454
|
+
* default. With `strictSqlMode` the session runs in strict mode, as a
|
|
455
|
+
* transaction given the same option does, so a statement that would cut a
|
|
456
|
+
* value short fails and changes nothing. The connection is closed
|
|
457
|
+
* afterwards, whether `work` finished or threw, so whatever `work` set on
|
|
458
|
+
* its session (a temporary table, a session variable, a lock) ends with it.
|
|
459
|
+
*/
|
|
460
|
+
async withSession(work, options) {
|
|
461
|
+
this.refuseSessionInTransaction();
|
|
462
|
+
this.ensurePool();
|
|
463
|
+
const running = this.runSession(work, options);
|
|
464
|
+
this.sessions.add(running);
|
|
465
|
+
try {
|
|
466
|
+
return await running;
|
|
467
|
+
} finally {
|
|
468
|
+
this.sessions.delete(running);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/** `withSession`'s work, on a connection it opens and closes. */
|
|
472
|
+
async runSession(work, options) {
|
|
473
|
+
let connection;
|
|
474
|
+
try {
|
|
475
|
+
connection = await mysql.createConnection(this.buildConnectionConfig());
|
|
476
|
+
} catch (error) {
|
|
477
|
+
throw this.classifyError(error);
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
403
480
|
try {
|
|
404
|
-
await
|
|
405
|
-
|
|
406
|
-
await
|
|
407
|
-
if (this.config.logger?.debug) {
|
|
408
|
-
const durationMs = Date.now() - startTime;
|
|
409
|
-
this.config.logger.debug("Transaction committed", {
|
|
410
|
-
attempt,
|
|
411
|
-
durationMs
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
return result;
|
|
481
|
+
await connection.query("SET SESSION autocommit = 1");
|
|
482
|
+
await connection.query("SET SESSION time_zone = '+00:00'");
|
|
483
|
+
if (options?.strictSqlMode) await this.enterStrictMode(connection, {});
|
|
415
484
|
} catch (error) {
|
|
416
|
-
await connection.query("ROLLBACK").catch(() => {
|
|
417
|
-
rollbackFailed = true;
|
|
418
|
-
});
|
|
419
|
-
lastError = error;
|
|
420
|
-
if (isApplicationError(error)) throw error;
|
|
421
|
-
const mysqlError = error;
|
|
422
|
-
if (mysqlError.errno === 1213 && attempt < maxAttempts) {
|
|
423
|
-
if (this.config.logger?.warn) this.config.logger.warn(`Transaction failed with deadlock, retrying (${attempt}/${maxAttempts})`, {
|
|
424
|
-
errno: mysqlError.errno,
|
|
425
|
-
attempt
|
|
426
|
-
});
|
|
427
|
-
await delay(retryDelayMs * attempt);
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
485
|
throw this.classifyError(error);
|
|
431
|
-
} finally {
|
|
432
|
-
await returnTransactionConnection(connection, rollbackFailed, restore);
|
|
433
486
|
}
|
|
487
|
+
return await work({ execute: async (statement, params = []) => {
|
|
488
|
+
const startTime = Date.now();
|
|
489
|
+
try {
|
|
490
|
+
const [rows] = await connection.query(statement, params);
|
|
491
|
+
if (this.config.logger?.query) this.config.logger.query(statement, params, Date.now() - startTime);
|
|
492
|
+
return rows;
|
|
493
|
+
} catch (error) {
|
|
494
|
+
throw this.classifyError(error, statement);
|
|
495
|
+
}
|
|
496
|
+
} });
|
|
497
|
+
} finally {
|
|
498
|
+
connection.destroy();
|
|
434
499
|
}
|
|
435
|
-
|
|
500
|
+
}
|
|
501
|
+
/** One attempt at an outermost transaction, on a connection of its own. */
|
|
502
|
+
async runAttempt(work, options, openedAt) {
|
|
503
|
+
const pool = this.ensurePool();
|
|
504
|
+
this.notePoolAccess();
|
|
505
|
+
const connection = await pool.getConnection();
|
|
506
|
+
let rollbackFailed = false;
|
|
507
|
+
const restore = {};
|
|
508
|
+
const settled = await this.settleOutermost({
|
|
509
|
+
context: this.createTransactionContext(connection),
|
|
510
|
+
join: this.joinSteps(connection),
|
|
511
|
+
openedAt,
|
|
512
|
+
begin: () => this.beginTransaction(connection, options, restore),
|
|
513
|
+
commit: async () => {
|
|
514
|
+
await connection.query("COMMIT");
|
|
515
|
+
},
|
|
516
|
+
rollback: async () => {
|
|
517
|
+
await connection.query("ROLLBACK").catch((rollbackError) => {
|
|
518
|
+
rollbackFailed = true;
|
|
519
|
+
throw rollbackError;
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}, work, options);
|
|
523
|
+
await returnTransactionConnection(connection, rollbackFailed, restore);
|
|
524
|
+
return settled;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* How a call made inside a transaction on `connection` joins it: flat, on
|
|
528
|
+
* the same connection, with nothing to send. Any failure inside it fails
|
|
529
|
+
* the whole transaction, since no savepoint can undo the part alone.
|
|
530
|
+
*/
|
|
531
|
+
joinSteps(connection) {
|
|
532
|
+
const callbackConnection = connection.connection;
|
|
533
|
+
return this.joinStepsOn(() => this.createTransactionContext(connection), (relations) => relations ? drizzle({
|
|
534
|
+
client: callbackConnection,
|
|
535
|
+
relations
|
|
536
|
+
}) : drizzle({ client: callbackConnection }), (context) => ({
|
|
537
|
+
context,
|
|
538
|
+
begin() {},
|
|
539
|
+
commit() {},
|
|
540
|
+
rollback() {},
|
|
541
|
+
dooms: () => true
|
|
542
|
+
}));
|
|
436
543
|
}
|
|
437
544
|
/**
|
|
438
545
|
* Get MySQL database capabilities.
|
|
@@ -459,7 +566,8 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
459
566
|
supportsOnConflict: true,
|
|
460
567
|
maxParamsPerQuery: 65535,
|
|
461
568
|
maxIdentifierLength: 64,
|
|
462
|
-
sharedRowLock: this.sharedRowLock
|
|
569
|
+
sharedRowLock: this.sharedRowLock,
|
|
570
|
+
functionalIndexes: this.functionalIndexes
|
|
463
571
|
};
|
|
464
572
|
}
|
|
465
573
|
/**
|
|
@@ -542,51 +650,86 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
542
650
|
"execute",
|
|
543
651
|
"getConnection"
|
|
544
652
|
]);
|
|
545
|
-
return this.
|
|
546
|
-
const connection = await
|
|
653
|
+
return this.joiningHandle(onClient(callbackPool), async () => {
|
|
654
|
+
const connection = await pool.getConnection();
|
|
547
655
|
return {
|
|
548
656
|
drizzle: onClient(connection.connection),
|
|
549
657
|
rollback: () => connection.query("ROLLBACK"),
|
|
550
658
|
release: (dirty) => {
|
|
551
659
|
if (dirty) connection.destroy();
|
|
552
660
|
else connection.release();
|
|
553
|
-
}
|
|
661
|
+
},
|
|
662
|
+
join: this.joinSteps(connection)
|
|
554
663
|
};
|
|
555
|
-
})
|
|
664
|
+
}, relations);
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Warns when a caller's configured driver zone does not agree with the
|
|
668
|
+
* pinned UTC session.
|
|
669
|
+
*
|
|
670
|
+
* mysql2's driver-level `timezone` decides how it parses the date strings
|
|
671
|
+
* the pinned session answers with and how it binds raw Date parameters, so
|
|
672
|
+
* a non-UTC spelling there would shift values a round-trip fails to
|
|
673
|
+
* cancel. The configuration is aligned to the pin (`buildConnectionConfig`
|
|
674
|
+
* sets UTC unconditionally, for pooled and standalone connections alike),
|
|
675
|
+
* never rewritten silently: the caller configured a reading, and this is
|
|
676
|
+
* where that reading is named as lost. Runs from `connect`, once per pool
|
|
677
|
+
* build — the configuration cannot change between calls.
|
|
678
|
+
*/
|
|
679
|
+
warnOnDriverZoneDisagreement() {
|
|
680
|
+
const requestedZone = this.config.timezone ?? "local";
|
|
681
|
+
if (isUtcDriverZone(requestedZone)) return;
|
|
682
|
+
this.config.logger?.warn?.(`Driver timezone "${requestedZone}" is not UTC; it is aligned to the pinned UTC session and no longer shifts reads or writes.`);
|
|
556
683
|
}
|
|
557
684
|
/**
|
|
558
685
|
* Builds mysql2 Pool configuration from adapter config.
|
|
559
686
|
*/
|
|
560
687
|
buildPoolConfig() {
|
|
561
|
-
const config =
|
|
562
|
-
if (this.config.url) config.uri = this.config.url;
|
|
563
|
-
else {
|
|
564
|
-
if (this.config.host) config.host = this.config.host;
|
|
565
|
-
if (this.config.port) config.port = this.config.port;
|
|
566
|
-
if (this.config.database) config.database = this.config.database;
|
|
567
|
-
if (this.config.user) config.user = this.config.user;
|
|
568
|
-
if (this.config.password) config.password = this.config.password;
|
|
569
|
-
}
|
|
688
|
+
const config = this.buildConnectionConfig();
|
|
570
689
|
config.connectionLimit = this.config.pool?.max ?? DEFAULT_POOL_CONFIG.max;
|
|
571
690
|
config.idleTimeout = this.config.pool?.idleTimeoutMs ?? DEFAULT_POOL_CONFIG.idleTimeoutMs;
|
|
572
|
-
config.connectTimeout = this.config.pool?.connectionTimeoutMs ?? DEFAULT_POOL_CONFIG.connectionTimeoutMs;
|
|
573
691
|
config.waitForConnections = true;
|
|
574
692
|
config.queueLimit = 0;
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (this.config.
|
|
693
|
+
return config;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Builds the settings every connection opens with, pooled or not, from
|
|
697
|
+
* adapter config.
|
|
698
|
+
*/
|
|
699
|
+
buildConnectionConfig() {
|
|
700
|
+
const config = this.buildAddressConfig();
|
|
701
|
+
config.connectTimeout = this.config.pool?.connectionTimeoutMs ?? DEFAULT_POOL_CONFIG.connectionTimeoutMs;
|
|
702
|
+
if (this.config.ssl) config.ssl = this.buildSslConfig(this.config.ssl);
|
|
703
|
+
config.timezone = "Z";
|
|
585
704
|
if (this.config.charset) config.charset = this.config.charset;
|
|
586
705
|
config.multipleStatements = false;
|
|
587
706
|
config.dateStrings = false;
|
|
588
707
|
return config;
|
|
589
708
|
}
|
|
709
|
+
/** Where to connect: the connection string, or the explicit options. */
|
|
710
|
+
buildAddressConfig() {
|
|
711
|
+
if (this.config.url) return { uri: this.config.url };
|
|
712
|
+
const { host, port, database, user, password } = this.config;
|
|
713
|
+
const config = {
|
|
714
|
+
host,
|
|
715
|
+
port,
|
|
716
|
+
database,
|
|
717
|
+
user,
|
|
718
|
+
password
|
|
719
|
+
};
|
|
720
|
+
for (const key of Object.keys(config)) if (!config[key]) delete config[key];
|
|
721
|
+
return config;
|
|
722
|
+
}
|
|
723
|
+
/** mysql2's `ssl` option for the adapter's `ssl` setting. */
|
|
724
|
+
buildSslConfig(ssl) {
|
|
725
|
+
if (typeof ssl === "boolean") return ssl ? {} : void 0;
|
|
726
|
+
return {
|
|
727
|
+
rejectUnauthorized: ssl.rejectUnauthorized,
|
|
728
|
+
ca: ssl.ca,
|
|
729
|
+
cert: ssl.cert,
|
|
730
|
+
key: ssl.key
|
|
731
|
+
};
|
|
732
|
+
}
|
|
590
733
|
/**
|
|
591
734
|
* Begins a transaction with the specified options, recording in `changed`
|
|
592
735
|
* each session setting it changes, as it changes it, for `restoreSession`
|
|
@@ -611,12 +754,20 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
611
754
|
await connection.query(`SET SESSION innodb_lock_wait_timeout = ${timeoutSeconds}`);
|
|
612
755
|
changed.lockWaitTimeout = Number.isInteger(previous) ? previous : null;
|
|
613
756
|
}
|
|
614
|
-
if (options?.strictSqlMode)
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
757
|
+
if (options?.strictSqlMode) await this.enterStrictMode(connection, changed);
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Strict mode for the rest of this connection's session: an INSERT, UPDATE
|
|
761
|
+
* or ALTER that would cut a string to its column's length, or clamp a
|
|
762
|
+
* number to its range, fails instead of applying with a warning. Added to
|
|
763
|
+
* the session's own mode, so every other flag it had still holds, and
|
|
764
|
+
* recorded in `changed` for `restoreSession` to put back.
|
|
765
|
+
*/
|
|
766
|
+
async enterStrictMode(connection, changed) {
|
|
767
|
+
const [rows] = await connection.query("SELECT @@SESSION.sql_mode AS mode");
|
|
768
|
+
const previous = rows[0]?.mode;
|
|
769
|
+
changed.sqlMode = typeof previous === "string" ? previous : null;
|
|
770
|
+
await connection.query("SET SESSION sql_mode = TRIM(BOTH ',' FROM CONCAT(@@SESSION.sql_mode, ',STRICT_ALL_TABLES'))");
|
|
620
771
|
}
|
|
621
772
|
/**
|
|
622
773
|
* Creates a TransactionContext for the given connection.
|
|
@@ -812,7 +963,8 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
812
963
|
await connection.query(`SELECT ${idColumn} FROM ${this.escapeIdentifier(table)} WHERE ${idColumn} = ? FOR UPDATE`, [id]);
|
|
813
964
|
},
|
|
814
965
|
insert: async (table, data, options) => {
|
|
815
|
-
const
|
|
966
|
+
const insertTableObj = this.resolvedTableObject(table);
|
|
967
|
+
const mapped = this.mapRowToRawSql(insertTableObj, data);
|
|
816
968
|
const columns = Object.keys(mapped);
|
|
817
969
|
const values = Object.values(mapped);
|
|
818
970
|
const placeholders = this.buildPlaceholders(values.length, 0);
|
|
@@ -820,7 +972,6 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
820
972
|
const [result] = await connection.query(sql, values);
|
|
821
973
|
const ret = options?.returning;
|
|
822
974
|
if (Array.isArray(ret) && ret.length === 0) return;
|
|
823
|
-
const insertTableObj = this.getTableObject(table);
|
|
824
975
|
const aliases = this.dateWallClockAliases(insertTableObj, ret ?? "*");
|
|
825
976
|
const spelled = aliases.map((a) => `DATE_FORMAT(${this.escapeIdentifier(a.sqlName)}, '%Y-%m-%dT%H:%i:%s.%f') AS ${this.escapeIdentifier(a.alias)}`).join(", ");
|
|
826
977
|
const projected = !ret || ret === "*" ? "*" : this.mapColumnNamesToSql(insertTableObj, ret).map((c) => this.escapeIdentifier(c)).join(", ");
|
|
@@ -838,7 +989,7 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
838
989
|
if (data.length === 0) return [];
|
|
839
990
|
const retMany = options?.returning;
|
|
840
991
|
const skipReread = Array.isArray(retMany) && retMany.length === 0;
|
|
841
|
-
const tableObj = this.
|
|
992
|
+
const tableObj = this.resolvedTableObject(table);
|
|
842
993
|
const mappedRecords = data.map((r) => this.mapRowToRawSql(tableObj, r));
|
|
843
994
|
const columns = Object.keys(mappedRecords[0]);
|
|
844
995
|
const allValues = [];
|