@nextlyhq/adapter-drizzle 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/dist/{adapter-C6ENLDau.d.cts → adapter-bUAaPzjV.d.cts} +307 -4
- package/dist/{adapter-D4GM1VAZ.d.ts → adapter-iJ5hbUvL.d.ts} +307 -4
- package/dist/index.cjs +570 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +571 -37
- package/dist/index.mjs.map +1 -1
- package/dist/{migration-R06fsTrz.d.ts → migration-BqHebSwM.d.ts} +65 -6
- package/dist/{migration-B6AmjCQ1.d.cts → migration-Bu0WLdX4.d.cts} +65 -6
- package/dist/migrations.d.cts +3 -3
- package/dist/migrations.d.ts +3 -3
- package/dist/schema-name.cjs +20 -0
- package/dist/schema-name.cjs.map +1 -0
- package/dist/schema-name.d.cts +24 -0
- package/dist/schema-name.d.ts +24 -0
- package/dist/schema-name.mjs +17 -0
- package/dist/schema-name.mjs.map +1 -0
- package/dist/text-match.cjs +16 -0
- package/dist/text-match.cjs.map +1 -0
- package/dist/text-match.d.cts +44 -0
- package/dist/text-match.d.ts +44 -0
- package/dist/text-match.mjs +13 -0
- package/dist/text-match.mjs.map +1 -0
- package/dist/types/index.cjs +7 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +16 -2
- package/dist/types/index.d.ts +16 -2
- package/dist/types/index.mjs +7 -0
- package/dist/types/index.mjs.map +1 -1
- package/dist/version-check.cjs +10 -1
- package/dist/version-check.cjs.map +1 -1
- package/dist/version-check.d.cts +18 -2
- package/dist/version-check.d.ts +18 -2
- package/dist/version-check.mjs +10 -2
- package/dist/version-check.mjs.map +1 -1
- package/package.json +14 -4
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var async_hooks = require('async_hooks');
|
|
3
4
|
var drizzleOrm = require('drizzle-orm');
|
|
4
5
|
|
|
5
6
|
// src/adapter.ts
|
|
7
|
+
|
|
8
|
+
// src/column-kinds.ts
|
|
9
|
+
function isJsonColumn(column) {
|
|
10
|
+
return column.dataType === "json" || column.columnType === "PgJsonb" || column.columnType === "PgJson" || column.columnType === "MySqlJson" || column.columnType === "SQLiteTextJson";
|
|
11
|
+
}
|
|
12
|
+
function isStructuredValue(value) {
|
|
13
|
+
if (value === null || typeof value !== "object") return false;
|
|
14
|
+
if (value instanceof Date) return false;
|
|
15
|
+
if (Buffer.isBuffer(value) || ArrayBuffer.isView(value)) return false;
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
function bindStructuredAsJson(value) {
|
|
19
|
+
return isStructuredValue(value) ? JSON.stringify(value) : value;
|
|
20
|
+
}
|
|
6
21
|
function buildDrizzleOrderBy(columns, orderBy) {
|
|
7
22
|
if (!orderBy?.length) return [];
|
|
8
23
|
return orderBy.flatMap((spec) => {
|
|
@@ -14,6 +29,14 @@ function buildDrizzleOrderBy(columns, orderBy) {
|
|
|
14
29
|
return [spec.nulls === "last" ? drizzleOrm.asc(isNull2) : drizzleOrm.desc(isNull2), direction];
|
|
15
30
|
});
|
|
16
31
|
}
|
|
32
|
+
function containsPattern(value) {
|
|
33
|
+
return `%${value.replace(/[!%_]/g, (character) => `!${character}`)}%`;
|
|
34
|
+
}
|
|
35
|
+
function caseInsensitiveContains(column, value) {
|
|
36
|
+
return drizzleOrm.sql`lower(${column}) like lower(${containsPattern(value)}) escape '!'`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/drizzle-where.ts
|
|
17
40
|
function buildDrizzleWhere(table, where) {
|
|
18
41
|
const columns = drizzleOrm.getColumns(table);
|
|
19
42
|
return processWhereClause(columns, where);
|
|
@@ -66,9 +89,6 @@ function isWhereCondition(item) {
|
|
|
66
89
|
function namesABranch(where) {
|
|
67
90
|
return Boolean(where.and?.length || where.or?.length || where.not);
|
|
68
91
|
}
|
|
69
|
-
function containsPattern(value) {
|
|
70
|
-
return `%${value.replace(/[!%_]/g, (character) => `!${character}`)}%`;
|
|
71
|
-
}
|
|
72
92
|
function buildCondition(columns, cond) {
|
|
73
93
|
const column = columns[cond.column];
|
|
74
94
|
if (!column) {
|
|
@@ -93,7 +113,7 @@ function buildCondition(columns, cond) {
|
|
|
93
113
|
case "LIKE":
|
|
94
114
|
return drizzleOrm.like(col, cond.value);
|
|
95
115
|
case "ILIKE":
|
|
96
|
-
return drizzleOrm.
|
|
116
|
+
return drizzleOrm.sql`lower(${col}) like lower(${cond.value})`;
|
|
97
117
|
case "IN":
|
|
98
118
|
return drizzleOrm.inArray(col, cond.value);
|
|
99
119
|
case "NOT IN":
|
|
@@ -108,11 +128,45 @@ function buildCondition(columns, cond) {
|
|
|
108
128
|
return drizzleOrm.notBetween(col, cond.value, cond.valueTo);
|
|
109
129
|
case "CONTAINS":
|
|
110
130
|
return drizzleOrm.sql`${col} like ${containsPattern(String(cond.value))} escape '!'`;
|
|
131
|
+
case "ICONTAINS":
|
|
132
|
+
return caseInsensitiveContains(col, String(cond.value));
|
|
111
133
|
default:
|
|
112
134
|
throw new Error(`Unsupported operator: ${cond.op}`);
|
|
113
135
|
}
|
|
114
136
|
}
|
|
115
137
|
|
|
138
|
+
// src/pinned-transaction.ts
|
|
139
|
+
function pinTransactions(db, checkout) {
|
|
140
|
+
const pinned = async (work, config) => {
|
|
141
|
+
const connection = await checkout();
|
|
142
|
+
let dirty = false;
|
|
143
|
+
let workFailure;
|
|
144
|
+
try {
|
|
145
|
+
return await connection.drizzle.transaction(async (tx) => {
|
|
146
|
+
try {
|
|
147
|
+
return await work(tx);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
workFailure = { error };
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}, config);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
await connection.rollback().catch(() => {
|
|
155
|
+
dirty = true;
|
|
156
|
+
});
|
|
157
|
+
throw workFailure ? workFailure.error : error;
|
|
158
|
+
} finally {
|
|
159
|
+
connection.release(dirty);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
Object.defineProperty(db, "transaction", {
|
|
163
|
+
value: pinned,
|
|
164
|
+
configurable: true,
|
|
165
|
+
writable: true
|
|
166
|
+
});
|
|
167
|
+
return db;
|
|
168
|
+
}
|
|
169
|
+
|
|
116
170
|
// src/transaction-forwarders.ts
|
|
117
171
|
function createTransactionForwarders(delegator, txDb) {
|
|
118
172
|
return {
|
|
@@ -122,9 +176,6 @@ function createTransactionForwarders(delegator, txDb) {
|
|
|
122
176
|
selectOne: async (table, options) => {
|
|
123
177
|
return delegator.selectOne(table, options, txDb());
|
|
124
178
|
},
|
|
125
|
-
update: async (table, data, where, options) => {
|
|
126
|
-
return delegator.update(table, data, where, options, txDb());
|
|
127
|
-
},
|
|
128
179
|
updateCount: async (table, data, where) => {
|
|
129
180
|
return delegator.updateCount(table, data, where, txDb());
|
|
130
181
|
},
|
|
@@ -137,6 +188,20 @@ function createTransactionForwarders(delegator, txDb) {
|
|
|
137
188
|
getDrizzle: () => txDb()
|
|
138
189
|
};
|
|
139
190
|
}
|
|
191
|
+
function transactionDrizzleHandles(build) {
|
|
192
|
+
let bareInstance;
|
|
193
|
+
const bare = () => bareInstance ??= build();
|
|
194
|
+
const relational = /* @__PURE__ */ new WeakMap();
|
|
195
|
+
const withRelations = (relations) => {
|
|
196
|
+
let cached = relational.get(relations);
|
|
197
|
+
if (cached === void 0) {
|
|
198
|
+
cached = build(relations);
|
|
199
|
+
relational.set(relations, cached);
|
|
200
|
+
}
|
|
201
|
+
return cached;
|
|
202
|
+
};
|
|
203
|
+
return { bare, withRelations };
|
|
204
|
+
}
|
|
140
205
|
|
|
141
206
|
// src/types/error.ts
|
|
142
207
|
function isDatabaseError(error) {
|
|
@@ -155,6 +220,74 @@ function createDatabaseError(options) {
|
|
|
155
220
|
if (options.cause !== void 0) error.cause = options.cause;
|
|
156
221
|
return error;
|
|
157
222
|
}
|
|
223
|
+
function isBindableColumn(value) {
|
|
224
|
+
return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.mapToDriverValue === "function";
|
|
225
|
+
}
|
|
226
|
+
function valueForJsonColumn(value) {
|
|
227
|
+
if (typeof value !== "string") return value;
|
|
228
|
+
try {
|
|
229
|
+
return JSON.parse(value);
|
|
230
|
+
} catch {
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function columnsOf(tableObj) {
|
|
235
|
+
const byName = /* @__PURE__ */ new Map();
|
|
236
|
+
const declared = [];
|
|
237
|
+
for (const [jsName, column] of Object.entries(
|
|
238
|
+
drizzleOrm.getColumns(tableObj)
|
|
239
|
+
)) {
|
|
240
|
+
if (!isBindableColumn(column)) continue;
|
|
241
|
+
byName.set(column.name, column);
|
|
242
|
+
byName.set(jsName, column);
|
|
243
|
+
declared.push(column);
|
|
244
|
+
}
|
|
245
|
+
return { byName, declared };
|
|
246
|
+
}
|
|
247
|
+
function boundValue(value, column, bindUnmodeled) {
|
|
248
|
+
if (drizzleOrm.is(value, drizzleOrm.SQL) || drizzleOrm.is(value, drizzleOrm.Column)) return drizzleOrm.sql`${value}`;
|
|
249
|
+
if (column) {
|
|
250
|
+
return drizzleOrm.sql`${drizzleOrm.sql.param(
|
|
251
|
+
isJsonColumn(column) ? valueForJsonColumn(value) : value,
|
|
252
|
+
column
|
|
253
|
+
)}`;
|
|
254
|
+
}
|
|
255
|
+
return drizzleOrm.sql`${drizzleOrm.sql.param(bindUnmodeled(value))}`;
|
|
256
|
+
}
|
|
257
|
+
function buildUpdateStatement(input) {
|
|
258
|
+
const { byName, declared } = columnsOf(input.tableObj);
|
|
259
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
260
|
+
for (const [key, value] of Object.entries(input.data)) {
|
|
261
|
+
const column = byName.get(key);
|
|
262
|
+
const name = column?.name ?? key;
|
|
263
|
+
if (value === void 0) {
|
|
264
|
+
assignments.delete(name);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
assignments.set(
|
|
268
|
+
name,
|
|
269
|
+
drizzleOrm.sql`${drizzleOrm.sql.identifier(name)} = ${boundValue(value, column, input.bindUnmodeled)}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (assignments.size === 0) return null;
|
|
273
|
+
for (const column of declared) {
|
|
274
|
+
if (assignments.has(column.name) || column.onUpdateFn === void 0) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const value = column.onUpdateFn();
|
|
278
|
+
assignments.set(
|
|
279
|
+
column.name,
|
|
280
|
+
drizzleOrm.sql`${drizzleOrm.sql.identifier(column.name)} = ${boundValue(value, column, input.bindUnmodeled)}`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
const statement = drizzleOrm.sql`UPDATE ${input.tableObj} SET ${drizzleOrm.sql.join([...assignments.values()], drizzleOrm.sql`, `)}`;
|
|
284
|
+
const condition = buildDrizzleWhere(input.tableObj, input.where);
|
|
285
|
+
if (condition) statement.append(drizzleOrm.sql` WHERE ${condition}`);
|
|
286
|
+
if (input.returning && input.returning.length > 0) {
|
|
287
|
+
statement.append(drizzleOrm.sql` RETURNING ${drizzleOrm.sql.join(input.returning, drizzleOrm.sql`, `)}`);
|
|
288
|
+
}
|
|
289
|
+
return statement;
|
|
290
|
+
}
|
|
158
291
|
|
|
159
292
|
// src/adapter.ts
|
|
160
293
|
var DATE_DATA_TYPE = /(?:^|\s)date$/;
|
|
@@ -203,6 +336,155 @@ function affectedRowCount(result) {
|
|
|
203
336
|
return 0;
|
|
204
337
|
}
|
|
205
338
|
var DrizzleAdapter = class {
|
|
339
|
+
/**
|
|
340
|
+
* Whether a `transaction()` call made from the current async context would
|
|
341
|
+
* join a transaction already open on this adapter, as a savepoint, rather
|
|
342
|
+
* than open one of its own.
|
|
343
|
+
*
|
|
344
|
+
* A caller about to do something no rollback can undo — removing a stored
|
|
345
|
+
* file, say — asks this first: work that joins an enclosing transaction is
|
|
346
|
+
* undone with it, while the effect it performs after its own savepoint
|
|
347
|
+
* releases is not.
|
|
348
|
+
*
|
|
349
|
+
* False here. Concrete rather than abstract so existing adapters keep
|
|
350
|
+
* working unchanged, and false is right for a pooled adapter: a call made
|
|
351
|
+
* inside another transaction's work opens its own transaction on its own
|
|
352
|
+
* connection, which commits independently.
|
|
353
|
+
*
|
|
354
|
+
* @returns True when the call would nest in an open transaction
|
|
355
|
+
*/
|
|
356
|
+
inTransaction() {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* The transaction each async flow is inside, kept per flow rather than per
|
|
361
|
+
* connection: on SQLite every request shares the one connection, and
|
|
362
|
+
* another request's open transaction says nothing about this one.
|
|
363
|
+
*/
|
|
364
|
+
flowTransactions = new async_hooks.AsyncLocalStorage();
|
|
365
|
+
/**
|
|
366
|
+
* Whether the calling async flow is inside a transaction on this adapter
|
|
367
|
+
* that has not finished: its own, or one it was started from inside.
|
|
368
|
+
*
|
|
369
|
+
* A write that must outlive any rollback — a count of a failed attempt, the
|
|
370
|
+
* spend of a single-use secret — asks this before running in a transaction
|
|
371
|
+
* of its own. Made inside an open transaction on SQLite, it joins that
|
|
372
|
+
* transaction as a savepoint and is undone with it. On PostgreSQL and MySQL
|
|
373
|
+
* it would commit on a connection of its own, so refusing it there too
|
|
374
|
+
* keeps code that passes on one dialect from failing on another. Unlike
|
|
375
|
+
* {@link inTransaction}, which asks whether a call would nest and is always
|
|
376
|
+
* false on a pooled adapter, this answers alike on every dialect, and
|
|
377
|
+
* another flow's open transaction never answers true.
|
|
378
|
+
*
|
|
379
|
+
* Covers transactions opened through {@link transaction} and through the
|
|
380
|
+
* Drizzle handle's own `transaction` on the dialects whose handle takes
|
|
381
|
+
* async work. A dialect that keeps scopes of its own judges from those
|
|
382
|
+
* instead; an adapter that does neither answers false.
|
|
383
|
+
*
|
|
384
|
+
* @experimental
|
|
385
|
+
* @returns True while a transaction the calling flow is inside is open
|
|
386
|
+
*/
|
|
387
|
+
transactionOpenInFlow() {
|
|
388
|
+
for (let at = this.flowTransactions.getStore(); at !== void 0; at = at.enclosing) {
|
|
389
|
+
if (at.open) return true;
|
|
390
|
+
}
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Run a whole transaction, from its BEGIN until its COMMIT or ROLLBACK has
|
|
395
|
+
* run, as the calling flow's open transaction, for
|
|
396
|
+
* {@link transactionOpenInFlow}. A dialect's `transaction` runs through
|
|
397
|
+
* this unless it judges the flow from scopes of its own.
|
|
398
|
+
*
|
|
399
|
+
* The transaction counts as open for its work and for anything the work
|
|
400
|
+
* starts, awaited or not, until it has finished. Something the work starts
|
|
401
|
+
* that outlives it then sees it closed, or the one enclosing it still open.
|
|
402
|
+
*
|
|
403
|
+
* @param run - The transaction, BEGIN to COMMIT or ROLLBACK
|
|
404
|
+
* @returns What `run` resolves to
|
|
405
|
+
*/
|
|
406
|
+
runInFlowTransaction(run) {
|
|
407
|
+
const scope = {
|
|
408
|
+
open: true,
|
|
409
|
+
enclosing: this.flowTransactions.getStore()
|
|
410
|
+
};
|
|
411
|
+
return this.flowTransactions.run(scope, async () => {
|
|
412
|
+
try {
|
|
413
|
+
return await run();
|
|
414
|
+
} finally {
|
|
415
|
+
scope.open = false;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Run each of a pool-bound Drizzle handle's transactions on a connection
|
|
421
|
+
* `checkout` hands it, and return that connection as the adapter's own
|
|
422
|
+
* `transaction()` does: closed rather than pooled when no ROLLBACK can be
|
|
423
|
+
* confirmed on it, and never leaked by a failed BEGIN (`pinTransactions`).
|
|
424
|
+
*
|
|
425
|
+
* For PostgreSQL's and MySQL's handles. Apply it before
|
|
426
|
+
* {@link trackFlowTransactions}, which wraps whatever `transaction` the
|
|
427
|
+
* handle has at that point.
|
|
428
|
+
*
|
|
429
|
+
* @param db - A Drizzle handle on this adapter's pool
|
|
430
|
+
* @param checkout - Checks out a connection for one transaction
|
|
431
|
+
* @returns The same handle, its `transaction` replaced
|
|
432
|
+
*/
|
|
433
|
+
pinDrizzleTransactions(db, checkout) {
|
|
434
|
+
return pinTransactions(db, checkout);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Make a Drizzle handle's own `transaction` count for
|
|
438
|
+
* {@link transactionOpenInFlow}, as the adapter's does.
|
|
439
|
+
*
|
|
440
|
+
* Core's services run their transactions through the handle on PostgreSQL
|
|
441
|
+
* and MySQL; without this, a call made inside one would be refused on
|
|
442
|
+
* SQLite, where they go through the adapter, and let through on the
|
|
443
|
+
* others. Not for SQLite's handle: its `transaction` takes only
|
|
444
|
+
* synchronous work, which no async flow can be inside.
|
|
445
|
+
*
|
|
446
|
+
* @param db - A Drizzle handle this adapter created
|
|
447
|
+
* @returns The same handle, its `transaction` tracked
|
|
448
|
+
*/
|
|
449
|
+
trackFlowTransactions(db) {
|
|
450
|
+
const transactor = db;
|
|
451
|
+
const untracked = transactor.transaction.bind(transactor);
|
|
452
|
+
const tracked = (work, config) => this.runInFlowTransaction(() => untracked(work, config));
|
|
453
|
+
Object.defineProperty(db, "transaction", {
|
|
454
|
+
value: tracked,
|
|
455
|
+
configurable: true,
|
|
456
|
+
writable: true
|
|
457
|
+
});
|
|
458
|
+
return db;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Run `effect` once the write made from the current async context has
|
|
462
|
+
* committed: an event, a hook, a cache flush, anything outside the database
|
|
463
|
+
* that must not happen for a change that is rolled back.
|
|
464
|
+
*
|
|
465
|
+
* A caller invokes this after its own transaction has resolved. Where that
|
|
466
|
+
* transaction was nested in an enclosing one, as a savepoint, the change is
|
|
467
|
+
* not durable yet, and an adapter that nests (see {@link inTransaction})
|
|
468
|
+
* holds `effect` until the outermost transaction commits, and drops it when
|
|
469
|
+
* that transaction, or the savepoint it was registered in, rolls back. A
|
|
470
|
+
* held effect runs after the commit, before the outermost `transaction()`
|
|
471
|
+
* call resolves, in registration order; one that then throws or rejects is
|
|
472
|
+
* handed to `onDeferredFailure` rather than failing a transaction that has
|
|
473
|
+
* committed.
|
|
474
|
+
*
|
|
475
|
+
* Here `effect` runs now and the returned promise settles with it, its
|
|
476
|
+
* failure included. Concrete rather than abstract, as `inTransaction` is,
|
|
477
|
+
* and right for a pooled adapter: each transaction has its own connection
|
|
478
|
+
* and has committed by the time its caller continues.
|
|
479
|
+
*
|
|
480
|
+
* @experimental
|
|
481
|
+
* @param effect - What to run once the change is durable
|
|
482
|
+
* @param onDeferredFailure - Receives the error of a held effect that fails
|
|
483
|
+
* @returns Settles once `effect` has run, or once it has been held
|
|
484
|
+
*/
|
|
485
|
+
async afterCommit(effect, _onDeferredFailure) {
|
|
486
|
+
await effect();
|
|
487
|
+
}
|
|
206
488
|
// ============================================================
|
|
207
489
|
// Drizzle Query API Support
|
|
208
490
|
// ============================================================
|
|
@@ -286,9 +568,7 @@ var DrizzleAdapter = class {
|
|
|
286
568
|
continue;
|
|
287
569
|
const sqlName = colDef.name;
|
|
288
570
|
sqlToJs.set(sqlName, jsName);
|
|
289
|
-
|
|
290
|
-
const columnType = colDef.columnType;
|
|
291
|
-
if (dataType === "json" || columnType === "PgJsonb" || columnType === "PgJson" || columnType === "MySqlJson" || columnType === "SQLiteTextJson") {
|
|
571
|
+
if (isJsonColumn(colDef)) {
|
|
292
572
|
jsonColumns.add(jsName);
|
|
293
573
|
}
|
|
294
574
|
}
|
|
@@ -1152,7 +1432,8 @@ var DrizzleAdapter = class {
|
|
|
1152
1432
|
* @remarks
|
|
1153
1433
|
* Default implementation uses dialect-specific ON CONFLICT syntax.
|
|
1154
1434
|
* PostgreSQL/SQLite: ON CONFLICT ... DO UPDATE
|
|
1155
|
-
* MySQL:
|
|
1435
|
+
* MySQL: the MySQL adapter overrides this, resolving the conflict by the
|
|
1436
|
+
* conflict columns (see its `upsert`)
|
|
1156
1437
|
*
|
|
1157
1438
|
* @param table - Table name
|
|
1158
1439
|
* @param data - Record data
|
|
@@ -1189,9 +1470,12 @@ var DrizzleAdapter = class {
|
|
|
1189
1470
|
updateData[key] = data[key];
|
|
1190
1471
|
}
|
|
1191
1472
|
}
|
|
1473
|
+
const keepExisting = Object.keys(updateData).length === 0;
|
|
1474
|
+
this.assertUpsertTarget(table, data, options, keepExisting);
|
|
1192
1475
|
let query;
|
|
1193
1476
|
if (caps.supportsOnConflict) {
|
|
1194
|
-
|
|
1477
|
+
const inserted = db.insert(tableObj).values(data);
|
|
1478
|
+
query = keepExisting ? inserted.onConflictDoNothing({ target: conflictTarget }) : inserted.onConflictDoUpdate({
|
|
1195
1479
|
target: conflictTarget,
|
|
1196
1480
|
set: updateData
|
|
1197
1481
|
});
|
|
@@ -1200,21 +1484,24 @@ var DrizzleAdapter = class {
|
|
|
1200
1484
|
}
|
|
1201
1485
|
if (caps.supportsReturning) {
|
|
1202
1486
|
const result = await query.returning();
|
|
1203
|
-
|
|
1487
|
+
const row = Array.isArray(result) ? result[0] : result;
|
|
1488
|
+
if (row !== void 0) return row;
|
|
1489
|
+
} else {
|
|
1490
|
+
await query;
|
|
1204
1491
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1492
|
+
const conflictValues = options.conflictColumns.map(
|
|
1493
|
+
(column) => [column, data[column]]
|
|
1494
|
+
);
|
|
1495
|
+
if (conflictValues.length > 0 && conflictValues.every(([, value]) => value !== void 0)) {
|
|
1207
1496
|
return await this.selectOne(
|
|
1208
1497
|
table,
|
|
1209
1498
|
{
|
|
1210
1499
|
where: {
|
|
1211
|
-
and: [
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
}
|
|
1217
|
-
]
|
|
1500
|
+
and: conflictValues.map(([column, value]) => ({
|
|
1501
|
+
column,
|
|
1502
|
+
op: "=",
|
|
1503
|
+
value
|
|
1504
|
+
}))
|
|
1218
1505
|
}
|
|
1219
1506
|
},
|
|
1220
1507
|
executor
|
|
@@ -1458,11 +1745,11 @@ var DrizzleAdapter = class {
|
|
|
1458
1745
|
*/
|
|
1459
1746
|
async tableExists(tableName, schema) {
|
|
1460
1747
|
try {
|
|
1461
|
-
let
|
|
1748
|
+
let sql6;
|
|
1462
1749
|
const params = [];
|
|
1463
1750
|
switch (this.dialect) {
|
|
1464
1751
|
case "postgresql":
|
|
1465
|
-
|
|
1752
|
+
sql6 = `
|
|
1466
1753
|
SELECT EXISTS (
|
|
1467
1754
|
SELECT FROM information_schema.tables
|
|
1468
1755
|
WHERE table_schema = COALESCE($1, (
|
|
@@ -1476,7 +1763,7 @@ var DrizzleAdapter = class {
|
|
|
1476
1763
|
params.push(schema ?? null, tableName);
|
|
1477
1764
|
break;
|
|
1478
1765
|
case "mysql":
|
|
1479
|
-
|
|
1766
|
+
sql6 = `
|
|
1480
1767
|
SELECT COUNT(*) as count
|
|
1481
1768
|
FROM information_schema.tables
|
|
1482
1769
|
WHERE table_schema = DATABASE()
|
|
@@ -1485,7 +1772,7 @@ var DrizzleAdapter = class {
|
|
|
1485
1772
|
params.push(tableName);
|
|
1486
1773
|
break;
|
|
1487
1774
|
case "sqlite":
|
|
1488
|
-
|
|
1775
|
+
sql6 = `
|
|
1489
1776
|
SELECT COUNT(*) as count
|
|
1490
1777
|
FROM sqlite_master
|
|
1491
1778
|
WHERE type = 'table'
|
|
@@ -1501,7 +1788,7 @@ var DrizzleAdapter = class {
|
|
|
1501
1788
|
);
|
|
1502
1789
|
}
|
|
1503
1790
|
const results = await this.executeQuery(
|
|
1504
|
-
|
|
1791
|
+
sql6,
|
|
1505
1792
|
params
|
|
1506
1793
|
);
|
|
1507
1794
|
if (results.length === 0) {
|
|
@@ -1533,11 +1820,11 @@ var DrizzleAdapter = class {
|
|
|
1533
1820
|
*/
|
|
1534
1821
|
async listTables(schema) {
|
|
1535
1822
|
try {
|
|
1536
|
-
let
|
|
1823
|
+
let sql6;
|
|
1537
1824
|
const params = [];
|
|
1538
1825
|
switch (this.dialect) {
|
|
1539
1826
|
case "postgresql":
|
|
1540
|
-
|
|
1827
|
+
sql6 = `
|
|
1541
1828
|
SELECT table_name
|
|
1542
1829
|
FROM information_schema.tables
|
|
1543
1830
|
WHERE table_schema = $1
|
|
@@ -1547,7 +1834,7 @@ var DrizzleAdapter = class {
|
|
|
1547
1834
|
params.push(schema ?? "public");
|
|
1548
1835
|
break;
|
|
1549
1836
|
case "mysql":
|
|
1550
|
-
|
|
1837
|
+
sql6 = `
|
|
1551
1838
|
SELECT table_name AS table_name
|
|
1552
1839
|
FROM information_schema.tables
|
|
1553
1840
|
WHERE table_schema = DATABASE()
|
|
@@ -1556,7 +1843,7 @@ var DrizzleAdapter = class {
|
|
|
1556
1843
|
`;
|
|
1557
1844
|
break;
|
|
1558
1845
|
case "sqlite":
|
|
1559
|
-
|
|
1846
|
+
sql6 = `
|
|
1560
1847
|
SELECT name as table_name
|
|
1561
1848
|
FROM sqlite_master
|
|
1562
1849
|
WHERE type = 'table'
|
|
@@ -1572,7 +1859,7 @@ var DrizzleAdapter = class {
|
|
|
1572
1859
|
);
|
|
1573
1860
|
}
|
|
1574
1861
|
const results = await this.executeQuery(
|
|
1575
|
-
|
|
1862
|
+
sql6,
|
|
1576
1863
|
params
|
|
1577
1864
|
);
|
|
1578
1865
|
return results.map((row) => row.table_name);
|
|
@@ -1638,6 +1925,224 @@ var DrizzleAdapter = class {
|
|
|
1638
1925
|
createTransactionForwarders(txDb) {
|
|
1639
1926
|
return createTransactionForwarders(this, txDb);
|
|
1640
1927
|
}
|
|
1928
|
+
/**
|
|
1929
|
+
* The Drizzle handles one transaction hands out; see
|
|
1930
|
+
* `transactionDrizzleHandles`. `build` makes an instance on the
|
|
1931
|
+
* transaction's own connection, with the relations config when given one.
|
|
1932
|
+
*
|
|
1933
|
+
* @protected
|
|
1934
|
+
*/
|
|
1935
|
+
transactionDrizzleHandles(build) {
|
|
1936
|
+
return transactionDrizzleHandles(build);
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* A transaction context's `update`, for each adapter to bind.
|
|
1940
|
+
*
|
|
1941
|
+
* Not forwarded to the pooled `update` the way `select` and `delete` are:
|
|
1942
|
+
* that one goes through the Drizzle query builder, which writes the columns
|
|
1943
|
+
* the runtime MODEL declares, and a transaction's update must reach the
|
|
1944
|
+
* columns the physical TABLE has — the localization transition window
|
|
1945
|
+
* writes a column the model has already moved to a companion table that
|
|
1946
|
+
* does not exist yet. The INSERT half of every transaction context already
|
|
1947
|
+
* builds its own statement for that reason; `update-statement.ts` is the
|
|
1948
|
+
* UPDATE half, spelled once for the three adapters, and this runs it: on
|
|
1949
|
+
* the transaction executor, classified with the operation and table named
|
|
1950
|
+
* as the pooled `update` classifies its failures, and read back through
|
|
1951
|
+
* `select` on the same executor when the caller asked for rows.
|
|
1952
|
+
*
|
|
1953
|
+
* That read is by the rows' IDENTITY where the dialect can report it: on
|
|
1954
|
+
* PostgreSQL and SQLite the statement carries `RETURNING <primary key>`, and
|
|
1955
|
+
* the read-back asks for exactly those rows — so a row another transaction
|
|
1956
|
+
* adds under the same predicate meanwhile is not among them, and an update
|
|
1957
|
+
* whose own write falsifies its predicate still reads its rows back. MySQL
|
|
1958
|
+
* has no RETURNING, so there the read re-runs the predicate, as the pooled
|
|
1959
|
+
* `update` always has on that dialect; a table with no primary key in its
|
|
1960
|
+
* model reads back the same way.
|
|
1961
|
+
*
|
|
1962
|
+
* @param txDb - thunk returning the transaction-bound Drizzle instance
|
|
1963
|
+
* @param run - how this dialect runs a statement on that instance, and the
|
|
1964
|
+
* rows it returns when the statement carries RETURNING: better-sqlite3
|
|
1965
|
+
* answers synchronously, the pooled drivers do not
|
|
1966
|
+
* @param bindUnmodeled - how a value binds when the model declares no
|
|
1967
|
+
* column for it; a declared column binds through its own encoder
|
|
1968
|
+
* @returns the context's `update` method
|
|
1969
|
+
*/
|
|
1970
|
+
transactionUpdate(txDb, run, bindUnmodeled) {
|
|
1971
|
+
return async (table, data, where, options) => {
|
|
1972
|
+
const wantsRows = this.updateReturnsRows(options?.returning);
|
|
1973
|
+
const tableObj = this.resolvedTableObject(table);
|
|
1974
|
+
const identity = wantsRows && this.getCapabilities().supportsReturning ? this.identityColumns(tableObj) : [];
|
|
1975
|
+
let reported;
|
|
1976
|
+
try {
|
|
1977
|
+
reported = await run(
|
|
1978
|
+
this.buildTransactionUpdate(tableObj, table, data, where, {
|
|
1979
|
+
bindUnmodeled,
|
|
1980
|
+
returning: identity.map((key) => drizzleOrm.sql`${drizzleOrm.sql.identifier(key.sqlName)}`)
|
|
1981
|
+
}),
|
|
1982
|
+
identity.length > 0
|
|
1983
|
+
);
|
|
1984
|
+
} catch (error) {
|
|
1985
|
+
throw this.handleQueryError(error, "update", table);
|
|
1986
|
+
}
|
|
1987
|
+
if (!wantsRows) return [];
|
|
1988
|
+
if (identity.length === 0 || !Array.isArray(reported)) {
|
|
1989
|
+
return this.select(table, { where }, txDb());
|
|
1990
|
+
}
|
|
1991
|
+
const rows = [];
|
|
1992
|
+
for (let at = 0; at < reported.length; at += IDENTITY_READ_BACK_CHUNK) {
|
|
1993
|
+
rows.push(
|
|
1994
|
+
...await this.select(
|
|
1995
|
+
table,
|
|
1996
|
+
{
|
|
1997
|
+
where: rowsByIdentity(
|
|
1998
|
+
identity,
|
|
1999
|
+
reported.slice(at, at + IDENTITY_READ_BACK_CHUNK)
|
|
2000
|
+
)
|
|
2001
|
+
},
|
|
2002
|
+
txDb()
|
|
2003
|
+
)
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
return rows;
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* The columns that identify a row of this table, under both spellings: the
|
|
2011
|
+
* SQL name RETURNING reports and the Drizzle property name the query
|
|
2012
|
+
* builder's WHERE is addressed by. Columns flagged primary first; failing
|
|
2013
|
+
* that, the table-level primary key the dialect's table config carries
|
|
2014
|
+
* (`primaryKey({ columns })` leaves each column's flag false), which each
|
|
2015
|
+
* adapter reads through its own dialect. Empty when the model declares
|
|
2016
|
+
* neither, and the read-back falls back to the caller's predicate.
|
|
2017
|
+
*/
|
|
2018
|
+
identityColumns(tableObj) {
|
|
2019
|
+
const columns = Object.entries(drizzleOrm.getColumns(tableObj));
|
|
2020
|
+
const flagged = columns.filter(([, c]) => c.primary === true);
|
|
2021
|
+
const members = flagged.length > 0 ? flagged : columns.filter(
|
|
2022
|
+
([, c]) => this.compositePrimaryKey(tableObj).includes(c)
|
|
2023
|
+
);
|
|
2024
|
+
return members.flatMap(
|
|
2025
|
+
([jsName, c]) => typeof c.name === "string" ? [{ jsName, sqlName: c.name }] : []
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* How a value binds to a column the model does not declare, on a driver
|
|
2030
|
+
* that binds scalars, dates and binary natively: a structured value goes
|
|
2031
|
+
* as JSON text, since what node-postgres and mysql2 make of a bare object
|
|
2032
|
+
* or array is not JSON. For the adapters to hand `transactionUpdate`.
|
|
2033
|
+
*/
|
|
2034
|
+
bindUnmodeledStructuredAsJson(value) {
|
|
2035
|
+
return bindStructuredAsJson(value);
|
|
2036
|
+
}
|
|
2037
|
+
/**
|
|
2038
|
+
* Refuse an upsert target every dialect would not answer alike: a conflict
|
|
2039
|
+
* column named twice, which SQLite rejects as no constraint's target and
|
|
2040
|
+
* MySQL would accept; and, when nothing is to be updated, a conflict column
|
|
2041
|
+
* left for the database to fill, since the row that stands is then read
|
|
2042
|
+
* back by a value the caller never gave. Shared with the dialects that
|
|
2043
|
+
* override `upsert`, so the refusals are one rule.
|
|
2044
|
+
*/
|
|
2045
|
+
assertUpsertTarget(table, data, options, keepExisting) {
|
|
2046
|
+
const columns = options.conflictColumns;
|
|
2047
|
+
if (new Set(columns).size !== columns.length) {
|
|
2048
|
+
throw createDatabaseError({
|
|
2049
|
+
kind: "query",
|
|
2050
|
+
message: `upsert on ${table} names a conflict column more than once (${columns.join(", ")}).`
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
if (keepExisting && columns.some((column) => data[column] === void 0)) {
|
|
2054
|
+
throw createDatabaseError({
|
|
2055
|
+
kind: "query",
|
|
2056
|
+
message: `upsert on ${table} needs a value for every conflict column (${columns.join(", ")}).`
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
/**
|
|
2061
|
+
* The columns of a table-level `primaryKey({ columns })`, as the dialect's
|
|
2062
|
+
* table config reports them. The base class cannot read that config without
|
|
2063
|
+
* naming a dialect, so each adapter answers for its own; a dialect that does
|
|
2064
|
+
* not answer reports none.
|
|
2065
|
+
*/
|
|
2066
|
+
compositePrimaryKey(_tableObj) {
|
|
2067
|
+
return [];
|
|
2068
|
+
}
|
|
2069
|
+
/** The registered table object, or the error every CRUD method throws without one. */
|
|
2070
|
+
resolvedTableObject(table) {
|
|
2071
|
+
const tableObj = this.getTableObject(table);
|
|
2072
|
+
if (!tableObj || typeof tableObj !== "object") {
|
|
2073
|
+
throw this.createDatabaseError(
|
|
2074
|
+
"query",
|
|
2075
|
+
`Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`,
|
|
2076
|
+
void 0
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
return tableObj;
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* The statement `transactionUpdate` runs.
|
|
2083
|
+
*
|
|
2084
|
+
* @throws when nothing would be written — every key `undefined`, or none at
|
|
2085
|
+
* all. The query builder refused that too ("No values to set"), and a
|
|
2086
|
+
* patch that names nothing is a caller's mistake rather than a write of
|
|
2087
|
+
* nothing.
|
|
2088
|
+
*/
|
|
2089
|
+
buildTransactionUpdate(tableObj, table, data, where, binding) {
|
|
2090
|
+
const statement = buildUpdateStatement({
|
|
2091
|
+
tableObj,
|
|
2092
|
+
data,
|
|
2093
|
+
where,
|
|
2094
|
+
bindUnmodeled: binding.bindUnmodeled,
|
|
2095
|
+
returning: binding.returning
|
|
2096
|
+
});
|
|
2097
|
+
if (!statement) {
|
|
2098
|
+
throw this.createDatabaseError(
|
|
2099
|
+
"query",
|
|
2100
|
+
`No values to set: the update of "${table}" names no column with a defined value.`,
|
|
2101
|
+
void 0
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
return statement;
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
2107
|
+
* Whether a transaction's `update` reads the rows it changed back: the
|
|
2108
|
+
* caller named columns, or `*`. An empty list, like no option at all, asks
|
|
2109
|
+
* for nothing and `update` answers `[]`, as it always has. The rows come
|
|
2110
|
+
* from a read of the update's own WHERE on its own transaction, so they are
|
|
2111
|
+
* the model's view of the row, decoded as every read is decoded — which is
|
|
2112
|
+
* what the query-builder update returned for them, and why the named list
|
|
2113
|
+
* selects nothing narrower.
|
|
2114
|
+
*/
|
|
2115
|
+
updateReturnsRows(returning) {
|
|
2116
|
+
return returning !== void 0 && !(Array.isArray(returning) && returning.length === 0);
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* The driver's own error inside `error`: the first in its `cause` chain that
|
|
2120
|
+
* `isDriverError` recognises, or `error` itself when none does.
|
|
2121
|
+
*
|
|
2122
|
+
* A query that runs through Drizzle fails with Drizzle's `DrizzleQueryError`,
|
|
2123
|
+
* whose message is the SQL statement and which carries no driver code; the
|
|
2124
|
+
* driver's error, the only object that knows what actually went wrong, is
|
|
2125
|
+
* its `cause`. A dialect's {@link classifyError} classifies from what this
|
|
2126
|
+
* returns, so a failure reads the same whether or not Drizzle wrapped it.
|
|
2127
|
+
*
|
|
2128
|
+
* The walk is bounded, because an error chain can be made cyclic and a
|
|
2129
|
+
* classifier that can hang is worse than one that gives up. Falling back to
|
|
2130
|
+
* `error` keeps an error that carries no code anywhere classified as before.
|
|
2131
|
+
*
|
|
2132
|
+
* @param error - The error as it was caught
|
|
2133
|
+
* @param isDriverError - Whether a link in the chain is the driver's error
|
|
2134
|
+
* @returns The driver's error, or `error` when no link is
|
|
2135
|
+
*/
|
|
2136
|
+
driverErrorIn(error, isDriverError) {
|
|
2137
|
+
let cursor = error;
|
|
2138
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
2139
|
+
if (cursor === null || typeof cursor !== "object") break;
|
|
2140
|
+
const candidate = cursor;
|
|
2141
|
+
if (isDriverError(candidate)) return candidate;
|
|
2142
|
+
cursor = candidate.cause;
|
|
2143
|
+
}
|
|
2144
|
+
return error ?? {};
|
|
2145
|
+
}
|
|
1641
2146
|
/**
|
|
1642
2147
|
* Classify an error into a DatabaseError.
|
|
1643
2148
|
*
|
|
@@ -1650,14 +2155,14 @@ var DrizzleAdapter = class {
|
|
|
1650
2155
|
*
|
|
1651
2156
|
* @protected
|
|
1652
2157
|
*/
|
|
1653
|
-
classifyError(error,
|
|
2158
|
+
classifyError(error, sql6) {
|
|
1654
2159
|
if (isDatabaseError(error)) {
|
|
1655
2160
|
return error;
|
|
1656
2161
|
}
|
|
1657
2162
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1658
2163
|
return this.createDatabaseError(
|
|
1659
2164
|
"query",
|
|
1660
|
-
|
|
2165
|
+
sql6 ? `Query failed: ${errorMessage}` : errorMessage,
|
|
1661
2166
|
error instanceof Error ? error : void 0
|
|
1662
2167
|
);
|
|
1663
2168
|
}
|
|
@@ -1678,8 +2183,9 @@ var DrizzleAdapter = class {
|
|
|
1678
2183
|
*/
|
|
1679
2184
|
handleQueryError(error, operation, table) {
|
|
1680
2185
|
const dbError = this.classifyError(error);
|
|
1681
|
-
|
|
1682
|
-
|
|
2186
|
+
const context = `${operation} operation failed on table '${table}'`;
|
|
2187
|
+
if (!dbError.message.includes(context)) {
|
|
2188
|
+
dbError.message = `${context}: ${dbError.message}`;
|
|
1683
2189
|
}
|
|
1684
2190
|
if (!dbError.table) {
|
|
1685
2191
|
dbError.table = table;
|
|
@@ -1687,6 +2193,34 @@ var DrizzleAdapter = class {
|
|
|
1687
2193
|
return dbError;
|
|
1688
2194
|
}
|
|
1689
2195
|
};
|
|
2196
|
+
var IDENTITY_READ_BACK_CHUNK = 200;
|
|
2197
|
+
function rowsByIdentity(identity, rows) {
|
|
2198
|
+
if (identity.length === 1) {
|
|
2199
|
+
const [key] = identity;
|
|
2200
|
+
return {
|
|
2201
|
+
and: [
|
|
2202
|
+
{
|
|
2203
|
+
column: key.jsName,
|
|
2204
|
+
op: "IN",
|
|
2205
|
+
value: rows.map((row) => row[key.sqlName])
|
|
2206
|
+
}
|
|
2207
|
+
]
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
return {
|
|
2211
|
+
or: rows.map(
|
|
2212
|
+
(row) => ({
|
|
2213
|
+
and: identity.map(
|
|
2214
|
+
(key) => ({
|
|
2215
|
+
column: key.jsName,
|
|
2216
|
+
op: "=",
|
|
2217
|
+
value: row[key.sqlName]
|
|
2218
|
+
})
|
|
2219
|
+
)
|
|
2220
|
+
})
|
|
2221
|
+
)
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
1690
2224
|
var ALLOWED_DEFAULT_SQL_EXPRESSIONS = /* @__PURE__ */ new Set([
|
|
1691
2225
|
"current_timestamp",
|
|
1692
2226
|
"now()",
|