@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/README.md
CHANGED
|
@@ -83,11 +83,11 @@ MySQL lacks a few features Nextly uses natively in PostgreSQL. The adapter emula
|
|
|
83
83
|
|
|
84
84
|
## Compatibility
|
|
85
85
|
|
|
86
|
-
| Tool | Version
|
|
87
|
-
| -------- |
|
|
88
|
-
| Node.js | 20+
|
|
89
|
-
| `mysql2` | 3+
|
|
90
|
-
| `nextly` | 0.0.x
|
|
86
|
+
| Tool | Version |
|
|
87
|
+
| -------- | --------------------- |
|
|
88
|
+
| Node.js | 20.19+, 22.12+ or 24+ |
|
|
89
|
+
| `mysql2` | 3+ |
|
|
90
|
+
| `nextly` | 0.0.x |
|
|
91
91
|
|
|
92
92
|
## Documentation
|
|
93
93
|
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
var adapterDrizzle = require('@nextlyhq/adapter-drizzle');
|
|
4
4
|
var types = require('@nextlyhq/adapter-drizzle/types');
|
|
5
5
|
var versionCheck = require('@nextlyhq/adapter-drizzle/version-check');
|
|
6
|
+
var drizzleOrm = require('drizzle-orm');
|
|
7
|
+
var mysqlCore = require('drizzle-orm/mysql-core');
|
|
6
8
|
var mysql2 = require('drizzle-orm/mysql2');
|
|
7
9
|
var mysql = require('mysql2/promise');
|
|
8
10
|
|
|
@@ -11,6 +13,25 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
11
13
|
var mysql__default = /*#__PURE__*/_interopDefault(mysql);
|
|
12
14
|
|
|
13
15
|
// src/index.ts
|
|
16
|
+
async function restoreSession(connection, restore) {
|
|
17
|
+
if (restore === null) return true;
|
|
18
|
+
if (restore.lockWaitTimeout === null) return false;
|
|
19
|
+
try {
|
|
20
|
+
await connection.query(
|
|
21
|
+
`SET SESSION innodb_lock_wait_timeout = ${restore.lockWaitTimeout}`
|
|
22
|
+
);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function returnTransactionConnection(connection, rollbackFailed, restore) {
|
|
29
|
+
if (!rollbackFailed && await restoreSession(connection, restore)) {
|
|
30
|
+
connection.release();
|
|
31
|
+
} else {
|
|
32
|
+
connection.destroy();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
14
35
|
var VERSION = "0.1.0";
|
|
15
36
|
var DEFAULT_POOL_CONFIG = {
|
|
16
37
|
max: 10,
|
|
@@ -109,6 +130,11 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
109
130
|
* Connection state flag.
|
|
110
131
|
*/
|
|
111
132
|
connected = false;
|
|
133
|
+
/**
|
|
134
|
+
* Whether the server accepts `SELECT ... FOR SHARE`, read from its version
|
|
135
|
+
* string at connect. Undefined until then, which callers treat as not known.
|
|
136
|
+
*/
|
|
137
|
+
sharedRowLock;
|
|
112
138
|
/**
|
|
113
139
|
* Creates a new MySQL adapter instance.
|
|
114
140
|
*
|
|
@@ -139,11 +165,12 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
139
165
|
const connection = await this.pool.getConnection();
|
|
140
166
|
try {
|
|
141
167
|
await connection.query("SELECT 1");
|
|
142
|
-
await versionCheck.checkDialectVersion(connection, "mysql", {
|
|
168
|
+
const { variant } = await versionCheck.checkDialectVersion(connection, "mysql", {
|
|
143
169
|
// Why: route variant warnings through the adapter's logger so
|
|
144
170
|
// users see a single, consistent log surface.
|
|
145
171
|
onWarning: (msg) => this.config.logger?.warn?.(msg)
|
|
146
172
|
});
|
|
173
|
+
this.sharedRowLock = versionCheck.acceptsForShare(variant);
|
|
147
174
|
this.connected = true;
|
|
148
175
|
if (this.config.logger?.info) {
|
|
149
176
|
this.config.logger.info("MySQL connection established", {
|
|
@@ -269,6 +296,12 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
269
296
|
* MySQL's quirks with nested transactions.
|
|
270
297
|
*/
|
|
271
298
|
async transaction(work, options) {
|
|
299
|
+
return this.runInFlowTransaction(
|
|
300
|
+
() => this.runTransactionAttempts(work, options)
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
/** The attempts `transaction` makes, each on a connection of its own. */
|
|
304
|
+
async runTransactionAttempts(work, options) {
|
|
272
305
|
const pool = this.ensurePool();
|
|
273
306
|
const maxAttempts = (options?.retryCount ?? 0) + 1;
|
|
274
307
|
const retryDelayMs = options?.retryDelayMs ?? 100;
|
|
@@ -276,8 +309,10 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
276
309
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
277
310
|
const connection = await pool.getConnection();
|
|
278
311
|
const startTime = Date.now();
|
|
312
|
+
let rollbackFailed = false;
|
|
313
|
+
let restore = null;
|
|
279
314
|
try {
|
|
280
|
-
await this.beginTransaction(connection, options);
|
|
315
|
+
restore = await this.beginTransaction(connection, options);
|
|
281
316
|
const ctx = this.createTransactionContext(connection);
|
|
282
317
|
const result = await work(ctx);
|
|
283
318
|
await connection.query("COMMIT");
|
|
@@ -291,6 +326,7 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
291
326
|
return result;
|
|
292
327
|
} catch (error) {
|
|
293
328
|
await connection.query("ROLLBACK").catch(() => {
|
|
329
|
+
rollbackFailed = true;
|
|
294
330
|
});
|
|
295
331
|
lastError = error;
|
|
296
332
|
if (types.isApplicationError(error)) throw error;
|
|
@@ -308,7 +344,7 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
308
344
|
}
|
|
309
345
|
throw this.classifyError(error);
|
|
310
346
|
} finally {
|
|
311
|
-
connection
|
|
347
|
+
await returnTransactionConnection(connection, rollbackFailed, restore);
|
|
312
348
|
}
|
|
313
349
|
}
|
|
314
350
|
throw this.classifyError(lastError);
|
|
@@ -346,8 +382,10 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
346
382
|
// ON DUPLICATE KEY UPDATE
|
|
347
383
|
maxParamsPerQuery: 65535,
|
|
348
384
|
// MySQL limit
|
|
349
|
-
maxIdentifierLength: 64
|
|
385
|
+
maxIdentifierLength: 64,
|
|
350
386
|
// MySQL limit
|
|
387
|
+
sharedRowLock: this.sharedRowLock
|
|
388
|
+
// Known once connected
|
|
351
389
|
};
|
|
352
390
|
}
|
|
353
391
|
/**
|
|
@@ -408,18 +446,44 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
408
446
|
throw new Error("getDrizzle() is server-only");
|
|
409
447
|
}
|
|
410
448
|
const pool = this.ensurePool();
|
|
411
|
-
const client = pool.pool;
|
|
412
449
|
if (!relations) {
|
|
413
|
-
this.drizzleBare ??=
|
|
450
|
+
this.drizzleBare ??= this.poolDrizzle(pool);
|
|
414
451
|
return this.drizzleBare;
|
|
415
452
|
}
|
|
416
453
|
let cached = this.drizzleByRelations.get(relations);
|
|
417
454
|
if (!cached) {
|
|
418
|
-
cached =
|
|
455
|
+
cached = this.poolDrizzle(pool, relations);
|
|
419
456
|
this.drizzleByRelations.set(relations, cached);
|
|
420
457
|
}
|
|
421
458
|
return cached;
|
|
422
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* A Drizzle handle on the pool, for `getDrizzle`.
|
|
462
|
+
*
|
|
463
|
+
* Its `transaction` runs on a connection this adapter checks out and
|
|
464
|
+
* returns itself (`pinDrizzleTransactions`): destroyed rather than pooled
|
|
465
|
+
* when no ROLLBACK could be confirmed on it. And it counts for
|
|
466
|
+
* `transactionOpenInFlow`, as the adapter's does.
|
|
467
|
+
*/
|
|
468
|
+
poolDrizzle(pool, relations) {
|
|
469
|
+
const onClient = (client) => relations ? mysql2.drizzle({ client, relations }) : mysql2.drizzle({ client });
|
|
470
|
+
const callbackPool = pool.pool;
|
|
471
|
+
return this.trackFlowTransactions(
|
|
472
|
+
this.pinDrizzleTransactions(onClient(callbackPool), async () => {
|
|
473
|
+
const connection = await pool.getConnection();
|
|
474
|
+
return {
|
|
475
|
+
drizzle: onClient(
|
|
476
|
+
connection.connection
|
|
477
|
+
),
|
|
478
|
+
rollback: () => connection.query("ROLLBACK"),
|
|
479
|
+
release: (dirty) => {
|
|
480
|
+
if (dirty) connection.destroy();
|
|
481
|
+
else connection.release();
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
})
|
|
485
|
+
);
|
|
486
|
+
}
|
|
423
487
|
/**
|
|
424
488
|
* Builds mysql2 Pool configuration from adapter config.
|
|
425
489
|
*/
|
|
@@ -462,7 +526,8 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
462
526
|
return config;
|
|
463
527
|
}
|
|
464
528
|
/**
|
|
465
|
-
* Begins a transaction with the specified options
|
|
529
|
+
* Begins a transaction with the specified options, and returns what it
|
|
530
|
+
* changed on the session for `restoreSession` to put back.
|
|
466
531
|
*/
|
|
467
532
|
async beginTransaction(connection, options) {
|
|
468
533
|
if (options?.isolationLevel) {
|
|
@@ -482,11 +547,17 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
482
547
|
}
|
|
483
548
|
await connection.query("START TRANSACTION");
|
|
484
549
|
if (options?.timeoutMs) {
|
|
550
|
+
const [rows] = await connection.query(
|
|
551
|
+
"SELECT @@SESSION.innodb_lock_wait_timeout AS timeout"
|
|
552
|
+
);
|
|
553
|
+
const previous = Number(rows[0]?.timeout);
|
|
485
554
|
const timeoutSeconds = Math.ceil(options.timeoutMs / 1e3);
|
|
486
555
|
await connection.query(
|
|
487
556
|
`SET SESSION innodb_lock_wait_timeout = ${timeoutSeconds}`
|
|
488
557
|
);
|
|
558
|
+
return { lockWaitTimeout: Number.isInteger(previous) ? previous : null };
|
|
489
559
|
}
|
|
560
|
+
return null;
|
|
490
561
|
}
|
|
491
562
|
/**
|
|
492
563
|
* Creates a TransactionContext for the given connection.
|
|
@@ -495,13 +566,208 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
495
566
|
* Note: Savepoint methods are not implemented (set to undefined)
|
|
496
567
|
* as savepoints are disabled in this adapter per approved approach.
|
|
497
568
|
*/
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
569
|
+
/**
|
|
570
|
+
* Upsert with the meaning `INSERT … ON CONFLICT (target)` has on PostgreSQL
|
|
571
|
+
* and SQLite, on the tables where MySQL can keep that promise.
|
|
572
|
+
*
|
|
573
|
+
* MySQL cannot limit a duplicate to one key: `ON DUPLICATE KEY UPDATE`
|
|
574
|
+
* fires on a collision with ANY unique key, and a failed insert does not
|
|
575
|
+
* say reliably which one. So the conflict target must be the table's only
|
|
576
|
+
* unique key apart from its primary key, and any other table is refused
|
|
577
|
+
* before anything is written. On such a table the row is inserted first —
|
|
578
|
+
* its validation and triggers apply, as they do before `ON CONFLICT`
|
|
579
|
+
* elsewhere — and a duplicate is resolved by a locking read of the target
|
|
580
|
+
* key, which sees the latest committed row rather than the transaction's
|
|
581
|
+
* snapshot. The row found is kept as it is when there is nothing to update
|
|
582
|
+
* (a no-op assignment would still run its update triggers), or updated by
|
|
583
|
+
* its key. No row there means the collision was on the primary key, and it
|
|
584
|
+
* is refused as `ON CONFLICT (target)` refuses it.
|
|
585
|
+
*
|
|
586
|
+
* The steps run in the caller's transaction, or in one of their own, since a
|
|
587
|
+
* locking read locks nothing past its statement under autocommit. Rows are
|
|
588
|
+
* read back by their primary key, so a trigger that rewrites the conflict
|
|
589
|
+
* value still returns the row stored. The conflict columns must also be
|
|
590
|
+
* given values: MySQL cannot look a row up by a key the database would only
|
|
591
|
+
* fill in itself. A conflict is resolved on the caller's key value: a
|
|
592
|
+
* BEFORE INSERT trigger that moves the key onto another row's value is
|
|
593
|
+
* refused as a unique violation rather than resolved onto that row. A failed insert rolls back only itself on MySQL, so a
|
|
594
|
+
* caller's transaction stays usable.
|
|
595
|
+
*/
|
|
596
|
+
async upsert(table, data, options, executor) {
|
|
597
|
+
const tableObj = this.getTableObject(table);
|
|
598
|
+
if (!tableObj) return super.upsert(table, data, options, executor);
|
|
599
|
+
let target;
|
|
600
|
+
try {
|
|
601
|
+
target = this.upsertTarget(tableObj, table, data, options);
|
|
602
|
+
} catch (error) {
|
|
603
|
+
throw this.handleQueryError(error, "upsert", table);
|
|
604
|
+
}
|
|
605
|
+
if (executor === void 0) {
|
|
606
|
+
return this.transaction((tx) => tx.upsert(table, data, options));
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
const db = executor;
|
|
610
|
+
if (target.nullKey) {
|
|
611
|
+
return await this.insertRow(db, tableObj, data) ?? data;
|
|
612
|
+
}
|
|
613
|
+
return await this.insertOrResolve(
|
|
614
|
+
db,
|
|
615
|
+
tableObj,
|
|
616
|
+
data,
|
|
617
|
+
options,
|
|
618
|
+
target.where
|
|
619
|
+
);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
throw this.handleQueryError(error, "upsert", table);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* What an upsert is keyed on: the target's condition, and whether its key
|
|
626
|
+
* holds a NULL. Refuses a target this dialect cannot keep to, and a conflict
|
|
627
|
+
* column left for the database to fill — MySQL cannot look a row up by a key
|
|
628
|
+
* value only the database would supply.
|
|
629
|
+
*/
|
|
630
|
+
upsertTarget(tableObj, table, data, options) {
|
|
631
|
+
this.assertUpsertTarget(table, data, options, true);
|
|
632
|
+
const key = this.conflictKey(tableObj, table, options.conflictColumns);
|
|
633
|
+
const values = options.conflictColumns.map((column) => data[column]);
|
|
634
|
+
return {
|
|
635
|
+
where: drizzleOrm.and(...key.map((column, i) => drizzleOrm.eq(column, values[i]))),
|
|
636
|
+
nullKey: values.some((value) => value === null)
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* The table's columns for a conflict target, refusing a target MySQL cannot
|
|
641
|
+
* keep to: one that is not a primary or unique key, which an update could
|
|
642
|
+
* spread over several rows, and one beside which the table has another
|
|
643
|
+
* unique key, a collision on which MySQL would not tell apart.
|
|
644
|
+
*/
|
|
645
|
+
conflictKey(tableObj, table, conflictColumns) {
|
|
646
|
+
const record = tableObj;
|
|
647
|
+
const columns = conflictColumns.map((name) => record[name]);
|
|
648
|
+
const names = new Set(columns.map((column) => column?.name));
|
|
649
|
+
const isTarget = (key) => key.length === names.size && key.every((name) => names.has(name));
|
|
650
|
+
const { primary, unique } = keysOf(tableObj);
|
|
651
|
+
if (primary.length === 0) {
|
|
652
|
+
throw types.createDatabaseError({
|
|
653
|
+
kind: "query",
|
|
654
|
+
message: `upsert on ${table}: MySQL upserts need a table with a primary key, to read back the row they write.`
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
const known = columns.every((column) => column !== void 0);
|
|
658
|
+
if (!known || !(isTarget(primary) || unique.some((k) => isTarget(k.columns)))) {
|
|
659
|
+
throw types.createDatabaseError({
|
|
660
|
+
kind: "query",
|
|
661
|
+
message: `upsert on ${table}: the conflict columns (${conflictColumns.join(", ")}) are not a primary or unique key of the table.`
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
const others = unique.filter((key) => !isTarget(key.columns));
|
|
665
|
+
if (others.length > 0) {
|
|
666
|
+
throw types.createDatabaseError({
|
|
667
|
+
kind: "query",
|
|
668
|
+
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.`
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
return columns;
|
|
672
|
+
}
|
|
673
|
+
/** The update an upsert applies to an existing row: never its key. */
|
|
674
|
+
updateValues(data, options) {
|
|
675
|
+
const key = new Set(options.conflictColumns);
|
|
676
|
+
const update = {};
|
|
677
|
+
for (const column of options.updateColumns ?? Object.keys(data)) {
|
|
678
|
+
if (!key.has(column) && column in data) update[column] = data[column];
|
|
679
|
+
}
|
|
680
|
+
return update;
|
|
681
|
+
}
|
|
682
|
+
/** The row matching `where`, by a locking (current) read. */
|
|
683
|
+
async lockedRow(db, tableObj, where) {
|
|
684
|
+
const rows = await db.select().from(tableObj).where(where).limit(1).for("update");
|
|
685
|
+
return rows[0];
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Insert the row, or resolve a duplicate on the target key. The insert is
|
|
689
|
+
* read back by the primary key it reports, not by the conflict key: a
|
|
690
|
+
* BEFORE INSERT trigger may have rewritten the conflict value, and the row
|
|
691
|
+
* stored is the one to return.
|
|
692
|
+
*
|
|
693
|
+
* A duplicate is resolved when a row holds the TARGET key, whichever unique
|
|
694
|
+
* key MySQL names in its error. PostgreSQL checks the target's index before
|
|
695
|
+
* any other, so a row colliding on the target and on another key resolves
|
|
696
|
+
* on the target there too; refusing it here because MySQL happened to check
|
|
697
|
+
* the other index first would answer that case differently. A target row
|
|
698
|
+
* committed by another transaction after this insert failed is the order in
|
|
699
|
+
* which that transaction ran first, where PostgreSQL also resolves on it.
|
|
700
|
+
*/
|
|
701
|
+
async insertOrResolve(db, tableObj, data, options, where) {
|
|
702
|
+
let failure;
|
|
703
|
+
try {
|
|
704
|
+
const stored = await this.insertRow(db, tableObj, data);
|
|
705
|
+
return stored ?? await this.lockedRow(db, tableObj, where);
|
|
706
|
+
} catch (error) {
|
|
707
|
+
if (this.classifyError(error).kind !== "unique_violation") throw error;
|
|
708
|
+
failure = error;
|
|
709
|
+
}
|
|
710
|
+
const existing = await this.lockedRow(db, tableObj, where);
|
|
711
|
+
if (existing === void 0) throw failure;
|
|
712
|
+
const update = this.updateValues(data, options);
|
|
713
|
+
if (Object.keys(update).length === 0) return existing;
|
|
714
|
+
await db.update(tableObj).set(update).where(where);
|
|
715
|
+
const identity = this.byIdentity(tableObj, {
|
|
716
|
+
...existing,
|
|
717
|
+
...update
|
|
501
718
|
});
|
|
502
|
-
|
|
503
|
-
|
|
719
|
+
const updated = identity === void 0 ? void 0 : await this.lockedRow(db, tableObj, identity);
|
|
720
|
+
return updated ?? this.lockedRow(db, tableObj, where);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Insert a row and read it back by its primary key: the value the caller
|
|
724
|
+
* gave, or the one MySQL or the model generated, which `$returningId()`
|
|
725
|
+
* reports and a given value it does not. Undefined when the table has no
|
|
726
|
+
* primary key to read by.
|
|
727
|
+
*/
|
|
728
|
+
async insertRow(db, tableObj, data) {
|
|
729
|
+
const [generated] = await db.insert(tableObj).values(data).$returningId();
|
|
730
|
+
const given = Object.fromEntries(
|
|
731
|
+
Object.entries(data).filter(
|
|
732
|
+
([, value]) => value !== void 0 && value !== null
|
|
733
|
+
)
|
|
734
|
+
);
|
|
735
|
+
const identity = this.byIdentity(tableObj, { ...generated, ...given });
|
|
736
|
+
return identity === void 0 ? void 0 : this.lockedRow(db, tableObj, identity);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* A row's primary key as a condition, from a record keyed as the model's
|
|
740
|
+
* properties are. Undefined when the table has no primary key or the record
|
|
741
|
+
* lacks part of it.
|
|
742
|
+
*/
|
|
743
|
+
byIdentity(tableObj, record) {
|
|
744
|
+
const primary = new Set(keysOf(tableObj).primary);
|
|
745
|
+
const parts = Object.entries(drizzleOrm.getColumns(tableObj)).filter(
|
|
746
|
+
([, column]) => primary.has(column.name)
|
|
747
|
+
);
|
|
748
|
+
if (parts.length === 0 || parts.some(
|
|
749
|
+
([name]) => record[name] === void 0 || record[name] === null
|
|
750
|
+
)) {
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
return drizzleOrm.and(
|
|
754
|
+
...parts.map(([name, column]) => drizzleOrm.eq(column, record[name]))
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
/** A table-level `primaryKey({ columns })`, read through the MySQL table config. */
|
|
758
|
+
compositePrimaryKey(tableObj) {
|
|
759
|
+
return mysqlCore.getTableConfig(
|
|
760
|
+
tableObj
|
|
761
|
+
).primaryKeys.flatMap((key) => key.columns);
|
|
762
|
+
}
|
|
763
|
+
createTransactionContext(connection) {
|
|
764
|
+
const txConnection = connection.connection;
|
|
765
|
+
const txHandles = this.transactionDrizzleHandles(
|
|
766
|
+
(relations) => relations ? mysql2.drizzle({ client: txConnection, relations }) : mysql2.drizzle({ client: txConnection })
|
|
767
|
+
);
|
|
768
|
+
const txDb = txHandles.bare;
|
|
504
769
|
return {
|
|
770
|
+
drizzleWithRelations: txHandles.withRelations,
|
|
505
771
|
execute: async (sql, params = []) => {
|
|
506
772
|
const [rows] = await connection.query(
|
|
507
773
|
sql,
|
|
@@ -611,6 +877,19 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
611
877
|
}
|
|
612
878
|
return [];
|
|
613
879
|
},
|
|
880
|
+
// Adapter-built, as `insert` above is; `transactionUpdate` says why.
|
|
881
|
+
// A column the model does not declare binds natively, as every value
|
|
882
|
+
// on this path's insert does.
|
|
883
|
+
update: this.transactionUpdate(
|
|
884
|
+
txDb,
|
|
885
|
+
async (statement) => {
|
|
886
|
+
await txDb().execute(statement);
|
|
887
|
+
return void 0;
|
|
888
|
+
},
|
|
889
|
+
// Structured values as JSON text: mysql2's formatter spells an object
|
|
890
|
+
// as `key = value` pairs and an array as a value list, neither JSON.
|
|
891
|
+
(value) => this.bindUnmodeledStructuredAsJson(value)
|
|
892
|
+
),
|
|
614
893
|
...this.createTransactionForwarders(txDb),
|
|
615
894
|
// Savepoints disabled per approved approach
|
|
616
895
|
savepoint: void 0,
|
|
@@ -627,7 +906,7 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
627
906
|
*/
|
|
628
907
|
classifyError(error, sql) {
|
|
629
908
|
if (types.isDatabaseError(error)) return error;
|
|
630
|
-
const mysqlError = error;
|
|
909
|
+
const mysqlError = this.driverErrorIn(error, (candidate) => typeof candidate.errno === "number");
|
|
631
910
|
const kind = mysqlError.errno && MYSQL_ERROR_CODES[mysqlError.errno] || "unknown";
|
|
632
911
|
let message = mysqlError.message ?? String(error);
|
|
633
912
|
if (sql && kind === "query") {
|
|
@@ -642,6 +921,36 @@ var MySqlAdapter = class extends adapterDrizzle.DrizzleAdapter {
|
|
|
642
921
|
});
|
|
643
922
|
}
|
|
644
923
|
};
|
|
924
|
+
var TABLE_KEYS = /* @__PURE__ */ new WeakMap();
|
|
925
|
+
function keysOf(tableObj) {
|
|
926
|
+
const cached = TABLE_KEYS.get(tableObj);
|
|
927
|
+
if (cached) return cached;
|
|
928
|
+
const config = mysqlCore.getTableConfig(tableObj);
|
|
929
|
+
const primary = [
|
|
930
|
+
...config.columns.filter((column) => column.primary),
|
|
931
|
+
...config.primaryKeys.flatMap((key) => key.columns)
|
|
932
|
+
].map((column) => column.name);
|
|
933
|
+
const unique = [
|
|
934
|
+
...config.columns.filter((column) => column.isUnique && !column.primary).map((column) => ({
|
|
935
|
+
// Drizzle's own name for an unnamed column-level unique key.
|
|
936
|
+
name: column.uniqueName ?? `${config.name}_${column.name}_unique`,
|
|
937
|
+
columns: [column.name]
|
|
938
|
+
})),
|
|
939
|
+
...config.uniqueConstraints.map((constraint) => ({
|
|
940
|
+
name: constraint.name,
|
|
941
|
+
columns: constraint.columns.map((column) => column.name)
|
|
942
|
+
})),
|
|
943
|
+
...config.indexes.filter((index) => index.config.unique === true).map((index) => ({
|
|
944
|
+
name: index.config.name,
|
|
945
|
+
columns: index.config.columns.map(
|
|
946
|
+
(column) => column.name
|
|
947
|
+
)
|
|
948
|
+
}))
|
|
949
|
+
];
|
|
950
|
+
const keys = { primary, unique };
|
|
951
|
+
TABLE_KEYS.set(tableObj, keys);
|
|
952
|
+
return keys;
|
|
953
|
+
}
|
|
645
954
|
function createMySqlAdapter(config) {
|
|
646
955
|
return new MySqlAdapter(config);
|
|
647
956
|
}
|