@nextlyhq/adapter-mysql 0.0.2-alpha.65 → 0.0.2-alpha.67
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/README.md +5 -5
- package/dist/index.cjs +323 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -2
- package/dist/index.d.ts +95 -2
- package/dist/index.mjs +324 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
import { DrizzleAdapter } from '@nextlyhq/adapter-drizzle';
|
|
2
2
|
import { isApplicationError, createDatabaseError, isDatabaseError } from '@nextlyhq/adapter-drizzle/types';
|
|
3
|
-
import { checkDialectVersion } from '@nextlyhq/adapter-drizzle/version-check';
|
|
3
|
+
import { checkDialectVersion, acceptsForShare } from '@nextlyhq/adapter-drizzle/version-check';
|
|
4
|
+
import { and, eq, getColumns } from 'drizzle-orm';
|
|
5
|
+
import { getTableConfig } from 'drizzle-orm/mysql-core';
|
|
4
6
|
import { drizzle } from 'drizzle-orm/mysql2';
|
|
5
7
|
import mysql from 'mysql2/promise';
|
|
6
8
|
|
|
7
9
|
// src/index.ts
|
|
10
|
+
async function restoreSession(connection, restore) {
|
|
11
|
+
if (restore === null) return true;
|
|
12
|
+
if (restore.lockWaitTimeout === null) return false;
|
|
13
|
+
try {
|
|
14
|
+
await connection.query(
|
|
15
|
+
`SET SESSION innodb_lock_wait_timeout = ${restore.lockWaitTimeout}`
|
|
16
|
+
);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function returnTransactionConnection(connection, rollbackFailed, restore) {
|
|
23
|
+
if (!rollbackFailed && await restoreSession(connection, restore)) {
|
|
24
|
+
connection.release();
|
|
25
|
+
} else {
|
|
26
|
+
connection.destroy();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
8
29
|
var VERSION = "0.1.0";
|
|
9
30
|
var DEFAULT_POOL_CONFIG = {
|
|
10
31
|
max: 10,
|
|
@@ -103,6 +124,11 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
103
124
|
* Connection state flag.
|
|
104
125
|
*/
|
|
105
126
|
connected = false;
|
|
127
|
+
/**
|
|
128
|
+
* Whether the server accepts `SELECT ... FOR SHARE`, read from its version
|
|
129
|
+
* string at connect. Undefined until then, which callers treat as not known.
|
|
130
|
+
*/
|
|
131
|
+
sharedRowLock;
|
|
106
132
|
/**
|
|
107
133
|
* Creates a new MySQL adapter instance.
|
|
108
134
|
*
|
|
@@ -133,11 +159,12 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
133
159
|
const connection = await this.pool.getConnection();
|
|
134
160
|
try {
|
|
135
161
|
await connection.query("SELECT 1");
|
|
136
|
-
await checkDialectVersion(connection, "mysql", {
|
|
162
|
+
const { variant } = await checkDialectVersion(connection, "mysql", {
|
|
137
163
|
// Why: route variant warnings through the adapter's logger so
|
|
138
164
|
// users see a single, consistent log surface.
|
|
139
165
|
onWarning: (msg) => this.config.logger?.warn?.(msg)
|
|
140
166
|
});
|
|
167
|
+
this.sharedRowLock = acceptsForShare(variant);
|
|
141
168
|
this.connected = true;
|
|
142
169
|
if (this.config.logger?.info) {
|
|
143
170
|
this.config.logger.info("MySQL connection established", {
|
|
@@ -263,6 +290,12 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
263
290
|
* MySQL's quirks with nested transactions.
|
|
264
291
|
*/
|
|
265
292
|
async transaction(work, options) {
|
|
293
|
+
return this.runInFlowTransaction(
|
|
294
|
+
() => this.runTransactionAttempts(work, options)
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
/** The attempts `transaction` makes, each on a connection of its own. */
|
|
298
|
+
async runTransactionAttempts(work, options) {
|
|
266
299
|
const pool = this.ensurePool();
|
|
267
300
|
const maxAttempts = (options?.retryCount ?? 0) + 1;
|
|
268
301
|
const retryDelayMs = options?.retryDelayMs ?? 100;
|
|
@@ -270,8 +303,10 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
270
303
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
271
304
|
const connection = await pool.getConnection();
|
|
272
305
|
const startTime = Date.now();
|
|
306
|
+
let rollbackFailed = false;
|
|
307
|
+
let restore = null;
|
|
273
308
|
try {
|
|
274
|
-
await this.beginTransaction(connection, options);
|
|
309
|
+
restore = await this.beginTransaction(connection, options);
|
|
275
310
|
const ctx = this.createTransactionContext(connection);
|
|
276
311
|
const result = await work(ctx);
|
|
277
312
|
await connection.query("COMMIT");
|
|
@@ -285,6 +320,7 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
285
320
|
return result;
|
|
286
321
|
} catch (error) {
|
|
287
322
|
await connection.query("ROLLBACK").catch(() => {
|
|
323
|
+
rollbackFailed = true;
|
|
288
324
|
});
|
|
289
325
|
lastError = error;
|
|
290
326
|
if (isApplicationError(error)) throw error;
|
|
@@ -302,7 +338,7 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
302
338
|
}
|
|
303
339
|
throw this.classifyError(error);
|
|
304
340
|
} finally {
|
|
305
|
-
connection
|
|
341
|
+
await returnTransactionConnection(connection, rollbackFailed, restore);
|
|
306
342
|
}
|
|
307
343
|
}
|
|
308
344
|
throw this.classifyError(lastError);
|
|
@@ -340,8 +376,10 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
340
376
|
// ON DUPLICATE KEY UPDATE
|
|
341
377
|
maxParamsPerQuery: 65535,
|
|
342
378
|
// MySQL limit
|
|
343
|
-
maxIdentifierLength: 64
|
|
379
|
+
maxIdentifierLength: 64,
|
|
344
380
|
// MySQL limit
|
|
381
|
+
sharedRowLock: this.sharedRowLock
|
|
382
|
+
// Known once connected
|
|
345
383
|
};
|
|
346
384
|
}
|
|
347
385
|
/**
|
|
@@ -402,18 +440,44 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
402
440
|
throw new Error("getDrizzle() is server-only");
|
|
403
441
|
}
|
|
404
442
|
const pool = this.ensurePool();
|
|
405
|
-
const client = pool.pool;
|
|
406
443
|
if (!relations) {
|
|
407
|
-
this.drizzleBare ??=
|
|
444
|
+
this.drizzleBare ??= this.poolDrizzle(pool);
|
|
408
445
|
return this.drizzleBare;
|
|
409
446
|
}
|
|
410
447
|
let cached = this.drizzleByRelations.get(relations);
|
|
411
448
|
if (!cached) {
|
|
412
|
-
cached =
|
|
449
|
+
cached = this.poolDrizzle(pool, relations);
|
|
413
450
|
this.drizzleByRelations.set(relations, cached);
|
|
414
451
|
}
|
|
415
452
|
return cached;
|
|
416
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* A Drizzle handle on the pool, for `getDrizzle`.
|
|
456
|
+
*
|
|
457
|
+
* Its `transaction` runs on a connection this adapter checks out and
|
|
458
|
+
* returns itself (`pinDrizzleTransactions`): destroyed rather than pooled
|
|
459
|
+
* when no ROLLBACK could be confirmed on it. And it counts for
|
|
460
|
+
* `transactionOpenInFlow`, as the adapter's does.
|
|
461
|
+
*/
|
|
462
|
+
poolDrizzle(pool, relations) {
|
|
463
|
+
const onClient = (client) => relations ? drizzle({ client, relations }) : drizzle({ client });
|
|
464
|
+
const callbackPool = pool.pool;
|
|
465
|
+
return this.trackFlowTransactions(
|
|
466
|
+
this.pinDrizzleTransactions(onClient(callbackPool), async () => {
|
|
467
|
+
const connection = await pool.getConnection();
|
|
468
|
+
return {
|
|
469
|
+
drizzle: onClient(
|
|
470
|
+
connection.connection
|
|
471
|
+
),
|
|
472
|
+
rollback: () => connection.query("ROLLBACK"),
|
|
473
|
+
release: (dirty) => {
|
|
474
|
+
if (dirty) connection.destroy();
|
|
475
|
+
else connection.release();
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
}
|
|
417
481
|
/**
|
|
418
482
|
* Builds mysql2 Pool configuration from adapter config.
|
|
419
483
|
*/
|
|
@@ -456,7 +520,8 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
456
520
|
return config;
|
|
457
521
|
}
|
|
458
522
|
/**
|
|
459
|
-
* Begins a transaction with the specified options
|
|
523
|
+
* Begins a transaction with the specified options, and returns what it
|
|
524
|
+
* changed on the session for `restoreSession` to put back.
|
|
460
525
|
*/
|
|
461
526
|
async beginTransaction(connection, options) {
|
|
462
527
|
if (options?.isolationLevel) {
|
|
@@ -476,11 +541,17 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
476
541
|
}
|
|
477
542
|
await connection.query("START TRANSACTION");
|
|
478
543
|
if (options?.timeoutMs) {
|
|
544
|
+
const [rows] = await connection.query(
|
|
545
|
+
"SELECT @@SESSION.innodb_lock_wait_timeout AS timeout"
|
|
546
|
+
);
|
|
547
|
+
const previous = Number(rows[0]?.timeout);
|
|
479
548
|
const timeoutSeconds = Math.ceil(options.timeoutMs / 1e3);
|
|
480
549
|
await connection.query(
|
|
481
550
|
`SET SESSION innodb_lock_wait_timeout = ${timeoutSeconds}`
|
|
482
551
|
);
|
|
552
|
+
return { lockWaitTimeout: Number.isInteger(previous) ? previous : null };
|
|
483
553
|
}
|
|
554
|
+
return null;
|
|
484
555
|
}
|
|
485
556
|
/**
|
|
486
557
|
* Creates a TransactionContext for the given connection.
|
|
@@ -489,13 +560,208 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
489
560
|
* Note: Savepoint methods are not implemented (set to undefined)
|
|
490
561
|
* as savepoints are disabled in this adapter per approved approach.
|
|
491
562
|
*/
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
563
|
+
/**
|
|
564
|
+
* Upsert with the meaning `INSERT … ON CONFLICT (target)` has on PostgreSQL
|
|
565
|
+
* and SQLite, on the tables where MySQL can keep that promise.
|
|
566
|
+
*
|
|
567
|
+
* MySQL cannot limit a duplicate to one key: `ON DUPLICATE KEY UPDATE`
|
|
568
|
+
* fires on a collision with ANY unique key, and a failed insert does not
|
|
569
|
+
* say reliably which one. So the conflict target must be the table's only
|
|
570
|
+
* unique key apart from its primary key, and any other table is refused
|
|
571
|
+
* before anything is written. On such a table the row is inserted first —
|
|
572
|
+
* its validation and triggers apply, as they do before `ON CONFLICT`
|
|
573
|
+
* elsewhere — and a duplicate is resolved by a locking read of the target
|
|
574
|
+
* key, which sees the latest committed row rather than the transaction's
|
|
575
|
+
* snapshot. The row found is kept as it is when there is nothing to update
|
|
576
|
+
* (a no-op assignment would still run its update triggers), or updated by
|
|
577
|
+
* its key. No row there means the collision was on the primary key, and it
|
|
578
|
+
* is refused as `ON CONFLICT (target)` refuses it.
|
|
579
|
+
*
|
|
580
|
+
* The steps run in the caller's transaction, or in one of their own, since a
|
|
581
|
+
* locking read locks nothing past its statement under autocommit. Rows are
|
|
582
|
+
* read back by their primary key, so a trigger that rewrites the conflict
|
|
583
|
+
* value still returns the row stored. The conflict columns must also be
|
|
584
|
+
* given values: MySQL cannot look a row up by a key the database would only
|
|
585
|
+
* fill in itself. A conflict is resolved on the caller's key value: a
|
|
586
|
+
* BEFORE INSERT trigger that moves the key onto another row's value is
|
|
587
|
+
* refused as a unique violation rather than resolved onto that row. A failed insert rolls back only itself on MySQL, so a
|
|
588
|
+
* caller's transaction stays usable.
|
|
589
|
+
*/
|
|
590
|
+
async upsert(table, data, options, executor) {
|
|
591
|
+
const tableObj = this.getTableObject(table);
|
|
592
|
+
if (!tableObj) return super.upsert(table, data, options, executor);
|
|
593
|
+
let target;
|
|
594
|
+
try {
|
|
595
|
+
target = this.upsertTarget(tableObj, table, data, options);
|
|
596
|
+
} catch (error) {
|
|
597
|
+
throw this.handleQueryError(error, "upsert", table);
|
|
598
|
+
}
|
|
599
|
+
if (executor === void 0) {
|
|
600
|
+
return this.transaction((tx) => tx.upsert(table, data, options));
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
const db = executor;
|
|
604
|
+
if (target.nullKey) {
|
|
605
|
+
return await this.insertRow(db, tableObj, data) ?? data;
|
|
606
|
+
}
|
|
607
|
+
return await this.insertOrResolve(
|
|
608
|
+
db,
|
|
609
|
+
tableObj,
|
|
610
|
+
data,
|
|
611
|
+
options,
|
|
612
|
+
target.where
|
|
613
|
+
);
|
|
614
|
+
} catch (error) {
|
|
615
|
+
throw this.handleQueryError(error, "upsert", table);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* What an upsert is keyed on: the target's condition, and whether its key
|
|
620
|
+
* holds a NULL. Refuses a target this dialect cannot keep to, and a conflict
|
|
621
|
+
* column left for the database to fill — MySQL cannot look a row up by a key
|
|
622
|
+
* value only the database would supply.
|
|
623
|
+
*/
|
|
624
|
+
upsertTarget(tableObj, table, data, options) {
|
|
625
|
+
this.assertUpsertTarget(table, data, options, true);
|
|
626
|
+
const key = this.conflictKey(tableObj, table, options.conflictColumns);
|
|
627
|
+
const values = options.conflictColumns.map((column) => data[column]);
|
|
628
|
+
return {
|
|
629
|
+
where: and(...key.map((column, i) => eq(column, values[i]))),
|
|
630
|
+
nullKey: values.some((value) => value === null)
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* The table's columns for a conflict target, refusing a target MySQL cannot
|
|
635
|
+
* keep to: one that is not a primary or unique key, which an update could
|
|
636
|
+
* spread over several rows, and one beside which the table has another
|
|
637
|
+
* unique key, a collision on which MySQL would not tell apart.
|
|
638
|
+
*/
|
|
639
|
+
conflictKey(tableObj, table, conflictColumns) {
|
|
640
|
+
const record = tableObj;
|
|
641
|
+
const columns = conflictColumns.map((name) => record[name]);
|
|
642
|
+
const names = new Set(columns.map((column) => column?.name));
|
|
643
|
+
const isTarget = (key) => key.length === names.size && key.every((name) => names.has(name));
|
|
644
|
+
const { primary, unique } = keysOf(tableObj);
|
|
645
|
+
if (primary.length === 0) {
|
|
646
|
+
throw createDatabaseError({
|
|
647
|
+
kind: "query",
|
|
648
|
+
message: `upsert on ${table}: MySQL upserts need a table with a primary key, to read back the row they write.`
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
const known = columns.every((column) => column !== void 0);
|
|
652
|
+
if (!known || !(isTarget(primary) || unique.some((k) => isTarget(k.columns)))) {
|
|
653
|
+
throw createDatabaseError({
|
|
654
|
+
kind: "query",
|
|
655
|
+
message: `upsert on ${table}: the conflict columns (${conflictColumns.join(", ")}) are not a primary or unique key of the table.`
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
const others = unique.filter((key) => !isTarget(key.columns));
|
|
659
|
+
if (others.length > 0) {
|
|
660
|
+
throw createDatabaseError({
|
|
661
|
+
kind: "query",
|
|
662
|
+
message: `upsert on ${table}: MySQL cannot limit a duplicate to the conflict columns (${conflictColumns.join(", ")}) while the table has other unique keys (${others.map((key) => key.name).join(", ")}). Upsert only on a table whose one unique key besides the primary key is the conflict target, or insert and update inside a transaction.`
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
return columns;
|
|
666
|
+
}
|
|
667
|
+
/** The update an upsert applies to an existing row: never its key. */
|
|
668
|
+
updateValues(data, options) {
|
|
669
|
+
const key = new Set(options.conflictColumns);
|
|
670
|
+
const update = {};
|
|
671
|
+
for (const column of options.updateColumns ?? Object.keys(data)) {
|
|
672
|
+
if (!key.has(column) && column in data) update[column] = data[column];
|
|
673
|
+
}
|
|
674
|
+
return update;
|
|
675
|
+
}
|
|
676
|
+
/** The row matching `where`, by a locking (current) read. */
|
|
677
|
+
async lockedRow(db, tableObj, where) {
|
|
678
|
+
const rows = await db.select().from(tableObj).where(where).limit(1).for("update");
|
|
679
|
+
return rows[0];
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Insert the row, or resolve a duplicate on the target key. The insert is
|
|
683
|
+
* read back by the primary key it reports, not by the conflict key: a
|
|
684
|
+
* BEFORE INSERT trigger may have rewritten the conflict value, and the row
|
|
685
|
+
* stored is the one to return.
|
|
686
|
+
*
|
|
687
|
+
* A duplicate is resolved when a row holds the TARGET key, whichever unique
|
|
688
|
+
* key MySQL names in its error. PostgreSQL checks the target's index before
|
|
689
|
+
* any other, so a row colliding on the target and on another key resolves
|
|
690
|
+
* on the target there too; refusing it here because MySQL happened to check
|
|
691
|
+
* the other index first would answer that case differently. A target row
|
|
692
|
+
* committed by another transaction after this insert failed is the order in
|
|
693
|
+
* which that transaction ran first, where PostgreSQL also resolves on it.
|
|
694
|
+
*/
|
|
695
|
+
async insertOrResolve(db, tableObj, data, options, where) {
|
|
696
|
+
let failure;
|
|
697
|
+
try {
|
|
698
|
+
const stored = await this.insertRow(db, tableObj, data);
|
|
699
|
+
return stored ?? await this.lockedRow(db, tableObj, where);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
if (this.classifyError(error).kind !== "unique_violation") throw error;
|
|
702
|
+
failure = error;
|
|
703
|
+
}
|
|
704
|
+
const existing = await this.lockedRow(db, tableObj, where);
|
|
705
|
+
if (existing === void 0) throw failure;
|
|
706
|
+
const update = this.updateValues(data, options);
|
|
707
|
+
if (Object.keys(update).length === 0) return existing;
|
|
708
|
+
await db.update(tableObj).set(update).where(where);
|
|
709
|
+
const identity = this.byIdentity(tableObj, {
|
|
710
|
+
...existing,
|
|
711
|
+
...update
|
|
495
712
|
});
|
|
496
|
-
|
|
497
|
-
|
|
713
|
+
const updated = identity === void 0 ? void 0 : await this.lockedRow(db, tableObj, identity);
|
|
714
|
+
return updated ?? this.lockedRow(db, tableObj, where);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Insert a row and read it back by its primary key: the value the caller
|
|
718
|
+
* gave, or the one MySQL or the model generated, which `$returningId()`
|
|
719
|
+
* reports and a given value it does not. Undefined when the table has no
|
|
720
|
+
* primary key to read by.
|
|
721
|
+
*/
|
|
722
|
+
async insertRow(db, tableObj, data) {
|
|
723
|
+
const [generated] = await db.insert(tableObj).values(data).$returningId();
|
|
724
|
+
const given = Object.fromEntries(
|
|
725
|
+
Object.entries(data).filter(
|
|
726
|
+
([, value]) => value !== void 0 && value !== null
|
|
727
|
+
)
|
|
728
|
+
);
|
|
729
|
+
const identity = this.byIdentity(tableObj, { ...generated, ...given });
|
|
730
|
+
return identity === void 0 ? void 0 : this.lockedRow(db, tableObj, identity);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* A row's primary key as a condition, from a record keyed as the model's
|
|
734
|
+
* properties are. Undefined when the table has no primary key or the record
|
|
735
|
+
* lacks part of it.
|
|
736
|
+
*/
|
|
737
|
+
byIdentity(tableObj, record) {
|
|
738
|
+
const primary = new Set(keysOf(tableObj).primary);
|
|
739
|
+
const parts = Object.entries(getColumns(tableObj)).filter(
|
|
740
|
+
([, column]) => primary.has(column.name)
|
|
741
|
+
);
|
|
742
|
+
if (parts.length === 0 || parts.some(
|
|
743
|
+
([name]) => record[name] === void 0 || record[name] === null
|
|
744
|
+
)) {
|
|
745
|
+
return void 0;
|
|
746
|
+
}
|
|
747
|
+
return and(
|
|
748
|
+
...parts.map(([name, column]) => eq(column, record[name]))
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
/** A table-level `primaryKey({ columns })`, read through the MySQL table config. */
|
|
752
|
+
compositePrimaryKey(tableObj) {
|
|
753
|
+
return getTableConfig(
|
|
754
|
+
tableObj
|
|
755
|
+
).primaryKeys.flatMap((key) => key.columns);
|
|
756
|
+
}
|
|
757
|
+
createTransactionContext(connection) {
|
|
758
|
+
const txConnection = connection.connection;
|
|
759
|
+
const txHandles = this.transactionDrizzleHandles(
|
|
760
|
+
(relations) => relations ? drizzle({ client: txConnection, relations }) : drizzle({ client: txConnection })
|
|
761
|
+
);
|
|
762
|
+
const txDb = txHandles.bare;
|
|
498
763
|
return {
|
|
764
|
+
drizzleWithRelations: txHandles.withRelations,
|
|
499
765
|
execute: async (sql, params = []) => {
|
|
500
766
|
const [rows] = await connection.query(
|
|
501
767
|
sql,
|
|
@@ -605,6 +871,19 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
605
871
|
}
|
|
606
872
|
return [];
|
|
607
873
|
},
|
|
874
|
+
// Adapter-built, as `insert` above is; `transactionUpdate` says why.
|
|
875
|
+
// A column the model does not declare binds natively, as every value
|
|
876
|
+
// on this path's insert does.
|
|
877
|
+
update: this.transactionUpdate(
|
|
878
|
+
txDb,
|
|
879
|
+
async (statement) => {
|
|
880
|
+
await txDb().execute(statement);
|
|
881
|
+
return void 0;
|
|
882
|
+
},
|
|
883
|
+
// Structured values as JSON text: mysql2's formatter spells an object
|
|
884
|
+
// as `key = value` pairs and an array as a value list, neither JSON.
|
|
885
|
+
(value) => this.bindUnmodeledStructuredAsJson(value)
|
|
886
|
+
),
|
|
608
887
|
...this.createTransactionForwarders(txDb),
|
|
609
888
|
// Savepoints disabled per approved approach
|
|
610
889
|
savepoint: void 0,
|
|
@@ -621,7 +900,7 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
621
900
|
*/
|
|
622
901
|
classifyError(error, sql) {
|
|
623
902
|
if (isDatabaseError(error)) return error;
|
|
624
|
-
const mysqlError = error;
|
|
903
|
+
const mysqlError = this.driverErrorIn(error, (candidate) => typeof candidate.errno === "number");
|
|
625
904
|
const kind = mysqlError.errno && MYSQL_ERROR_CODES[mysqlError.errno] || "unknown";
|
|
626
905
|
let message = mysqlError.message ?? String(error);
|
|
627
906
|
if (sql && kind === "query") {
|
|
@@ -636,6 +915,36 @@ var MySqlAdapter = class extends DrizzleAdapter {
|
|
|
636
915
|
});
|
|
637
916
|
}
|
|
638
917
|
};
|
|
918
|
+
var TABLE_KEYS = /* @__PURE__ */ new WeakMap();
|
|
919
|
+
function keysOf(tableObj) {
|
|
920
|
+
const cached = TABLE_KEYS.get(tableObj);
|
|
921
|
+
if (cached) return cached;
|
|
922
|
+
const config = getTableConfig(tableObj);
|
|
923
|
+
const primary = [
|
|
924
|
+
...config.columns.filter((column) => column.primary),
|
|
925
|
+
...config.primaryKeys.flatMap((key) => key.columns)
|
|
926
|
+
].map((column) => column.name);
|
|
927
|
+
const unique = [
|
|
928
|
+
...config.columns.filter((column) => column.isUnique && !column.primary).map((column) => ({
|
|
929
|
+
// Drizzle's own name for an unnamed column-level unique key.
|
|
930
|
+
name: column.uniqueName ?? `${config.name}_${column.name}_unique`,
|
|
931
|
+
columns: [column.name]
|
|
932
|
+
})),
|
|
933
|
+
...config.uniqueConstraints.map((constraint) => ({
|
|
934
|
+
name: constraint.name,
|
|
935
|
+
columns: constraint.columns.map((column) => column.name)
|
|
936
|
+
})),
|
|
937
|
+
...config.indexes.filter((index) => index.config.unique === true).map((index) => ({
|
|
938
|
+
name: index.config.name,
|
|
939
|
+
columns: index.config.columns.map(
|
|
940
|
+
(column) => column.name
|
|
941
|
+
)
|
|
942
|
+
}))
|
|
943
|
+
];
|
|
944
|
+
const keys = { primary, unique };
|
|
945
|
+
TABLE_KEYS.set(tableObj, keys);
|
|
946
|
+
return keys;
|
|
947
|
+
}
|
|
639
948
|
function createMySqlAdapter(config) {
|
|
640
949
|
return new MySqlAdapter(config);
|
|
641
950
|
}
|