@nextlyhq/adapter-drizzle 0.0.2-alpha.67 → 0.0.2-alpha.69
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-BypoOEEN.d.cts +1166 -0
- package/dist/adapter-CSP-Ek-L.d.ts +1166 -0
- package/dist/index-BYb6cDli.d.ts +1168 -0
- package/dist/index-C4HJsfwA.d.cts +1168 -0
- package/dist/index.cjs +2258 -2183
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -10
- package/dist/index.d.ts +5 -10
- package/dist/index.mjs +2257 -2181
- package/dist/index.mjs.map +1 -1
- package/dist/migrations.cjs +345 -146
- package/dist/migrations.cjs.map +1 -1
- package/dist/migrations.d.cts +93 -110
- package/dist/migrations.d.ts +93 -110
- package/dist/migrations.mjs +344 -144
- package/dist/migrations.mjs.map +1 -1
- package/dist/schema/index.cjs +21 -6
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.cts +5 -116
- package/dist/schema/index.d.ts +5 -116
- package/dist/schema/index.mjs +20 -4
- package/dist/schema/index.mjs.map +1 -1
- package/dist/schema-Bkkr3JMC.d.cts +264 -0
- package/dist/schema-Bkkr3JMC.d.ts +264 -0
- package/dist/schema-name.cjs +34 -12
- package/dist/schema-name.cjs.map +1 -1
- package/dist/schema-name.d.cts +3 -15
- package/dist/schema-name.d.ts +3 -15
- package/dist/schema-name.mjs +33 -10
- package/dist/schema-name.mjs.map +1 -1
- package/dist/text-match.cjs +42 -8
- package/dist/text-match.cjs.map +1 -1
- package/dist/text-match.d.cts +4 -16
- package/dist/text-match.d.ts +4 -16
- package/dist/text-match.mjs +41 -6
- package/dist/text-match.mjs.map +1 -1
- package/dist/types/index.cjs +8 -69
- package/dist/types/index.d.cts +3 -225
- package/dist/types/index.d.ts +3 -225
- package/dist/types/index.mjs +1 -64
- package/dist/types-CNyA1oIm.cjs +185 -0
- package/dist/types-CNyA1oIm.cjs.map +1 -0
- package/dist/types-CaF_5jNk.mjs +150 -0
- package/dist/types-CaF_5jNk.mjs.map +1 -0
- package/dist/version-check.cjs +116 -133
- package/dist/version-check.cjs.map +1 -1
- package/dist/version-check.d.cts +46 -47
- package/dist/version-check.d.ts +46 -47
- package/dist/version-check.mjs +115 -131
- package/dist/version-check.mjs.map +1 -1
- package/package.json +12 -16
- package/dist/adapter-bUAaPzjV.d.cts +0 -1143
- package/dist/adapter-iJ5hbUvL.d.ts +0 -1143
- package/dist/core-CVO7WYDj.d.cts +0 -74
- package/dist/core-CVO7WYDj.d.ts +0 -74
- package/dist/error-BrdknH2s.d.cts +0 -122
- package/dist/error-BrdknH2s.d.ts +0 -122
- package/dist/migration-BqHebSwM.d.ts +0 -831
- package/dist/migration-Bu0WLdX4.d.cts +0 -831
- package/dist/schema-BDn8WfSL.d.cts +0 -200
- package/dist/schema-BIQ0YQZ_.d.ts +0 -200
- package/dist/types/index.cjs.map +0 -1
- package/dist/types/index.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,1755 +1,1863 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// src/adapter.ts
|
|
7
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_text_match = require("./text-match.cjs");
|
|
3
|
+
const require_types = require("./types-CNyA1oIm.cjs");
|
|
4
|
+
let node_async_hooks = require("node:async_hooks");
|
|
5
|
+
let drizzle_orm = require("drizzle-orm");
|
|
8
6
|
// src/column-kinds.ts
|
|
7
|
+
/**
|
|
8
|
+
* Whether a column stores JSON, in the spelling each dialect's column class
|
|
9
|
+
* reports. Drizzle serializes objects for these itself, so a value that
|
|
10
|
+
* arrives already serialized has to be parsed first or it is serialized
|
|
11
|
+
* twice.
|
|
12
|
+
*
|
|
13
|
+
* SQLite matches only text columns declared with `{ mode: "json" }`
|
|
14
|
+
* (`SQLiteTextJson`, dataType `json`). A plain `SQLiteText` column stores a
|
|
15
|
+
* serialized string as it is and must not be parsed, or better-sqlite3 is
|
|
16
|
+
* handed an object it cannot bind.
|
|
17
|
+
*/
|
|
9
18
|
function isJsonColumn(column) {
|
|
10
|
-
|
|
19
|
+
return column.dataType === "json" || column.columnType === "PgJsonb" || column.columnType === "PgJson" || column.columnType === "MySqlJson" || column.columnType === "SQLiteTextJson";
|
|
11
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Whether a value is a structured document — a plain object or an array —
|
|
23
|
+
* rather than a scalar, a date, or binary. What a driver does with one of
|
|
24
|
+
* these when the column is not modelled differs (node-postgres spells an
|
|
25
|
+
* array as a PostgreSQL array literal, mysql2 as a value list), and none of
|
|
26
|
+
* it is JSON; serializing here is what makes the same bytes land everywhere.
|
|
27
|
+
*/
|
|
12
28
|
function isStructuredValue(value) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
if (value === null || typeof value !== "object") return false;
|
|
30
|
+
if (value instanceof Date) return false;
|
|
31
|
+
if (Buffer.isBuffer(value) || ArrayBuffer.isView(value)) return false;
|
|
32
|
+
return true;
|
|
17
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* How a value binds to a column the runtime model does not declare, on a
|
|
36
|
+
* driver that binds scalars, dates and binary natively: structured values are
|
|
37
|
+
* serialized as JSON text, everything else passes through.
|
|
38
|
+
*/
|
|
18
39
|
function bindStructuredAsJson(value) {
|
|
19
|
-
|
|
40
|
+
return isStructuredValue(value) ? JSON.stringify(value) : value;
|
|
20
41
|
}
|
|
42
|
+
// src/drizzle-order.ts
|
|
43
|
+
/**
|
|
44
|
+
* Turn order specifications into drizzle order clauses.
|
|
45
|
+
*
|
|
46
|
+
* Extracted from `DrizzleAdapter.select` so the rendered SQL can be asserted
|
|
47
|
+
* directly. That matters more than it looks: `OrderBySpec.nulls` was declared,
|
|
48
|
+
* documented as "can be explicitly controlled", and read NOWHERE — every caller
|
|
49
|
+
* that set it silently got the dialect default instead, and no test could have
|
|
50
|
+
* caught that while the mapping lived inline in a method that needs a database.
|
|
51
|
+
*
|
|
52
|
+
* ## Why null placement is a leading sort key, not `NULLS FIRST/LAST`
|
|
53
|
+
*
|
|
54
|
+
* That clause is not portable: PostgreSQL and SQLite accept it, MySQL does not.
|
|
55
|
+
* `col IS NULL` is a boolean expression all three evaluate, and sorting on it
|
|
56
|
+
* ascending puts non-null rows first — which is `NULLS LAST` by another name,
|
|
57
|
+
* spelled in something every supported engine understands.
|
|
58
|
+
*
|
|
59
|
+
* The defaults genuinely disagree, which is why the control exists at all:
|
|
60
|
+
* PostgreSQL treats NULL as largest and puts it FIRST on a descending order,
|
|
61
|
+
* while MySQL and SQLite treat it as smallest and put it last. A "newest
|
|
62
|
+
* scheduled first" list containing undated drafts therefore returns different
|
|
63
|
+
* rows per engine, and under a limit it can return only drafts.
|
|
64
|
+
*/
|
|
21
65
|
function buildDrizzleOrderBy(columns, orderBy) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
function containsPattern(value) {
|
|
33
|
-
return `%${value.replace(/[!%_]/g, (character) => `!${character}`)}%`;
|
|
66
|
+
if (!orderBy?.length) return [];
|
|
67
|
+
return orderBy.flatMap((spec) => {
|
|
68
|
+
const col = columns[spec.column];
|
|
69
|
+
if (!col) return [];
|
|
70
|
+
const direction = spec.direction === "desc" ? (0, drizzle_orm.desc)(col) : (0, drizzle_orm.asc)(col);
|
|
71
|
+
if (spec.nulls === void 0) return [direction];
|
|
72
|
+
const isNull = drizzle_orm.sql`${col} is null`;
|
|
73
|
+
return [spec.nulls === "last" ? (0, drizzle_orm.asc)(isNull) : (0, drizzle_orm.desc)(isNull), direction];
|
|
74
|
+
});
|
|
34
75
|
}
|
|
35
|
-
function caseInsensitiveContains(column, value) {
|
|
36
|
-
return drizzleOrm.sql`lower(${column}) like lower(${containsPattern(value)}) escape '!'`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
76
|
// src/drizzle-where.ts
|
|
40
77
|
function buildDrizzleWhere(table, where) {
|
|
41
|
-
|
|
42
|
-
return processWhereClause(columns, where);
|
|
78
|
+
return processWhereClause((0, drizzle_orm.getColumns)(table), where);
|
|
43
79
|
}
|
|
44
80
|
function processWhereClause(columns, where) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (notCondition) {
|
|
72
|
-
parts.push(drizzleOrm.not(notCondition));
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
if (parts.length === 0) {
|
|
76
|
-
if (namesABranch(where)) {
|
|
77
|
-
throw new Error(
|
|
78
|
-
`Where clause produced no condition: ${JSON.stringify(where)}. Every branch resolved to nothing, which would match every row. Pass {} to mean "no filter".`
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
return void 0;
|
|
82
|
-
}
|
|
83
|
-
if (parts.length === 1) return parts[0];
|
|
84
|
-
return drizzleOrm.and(...parts);
|
|
81
|
+
const parts = [];
|
|
82
|
+
if (where.and?.length) {
|
|
83
|
+
const andParts = where.and.map((item) => {
|
|
84
|
+
if (isWhereCondition(item)) return buildCondition(columns, item);
|
|
85
|
+
return processWhereClause(columns, item);
|
|
86
|
+
}).filter((p) => p !== void 0);
|
|
87
|
+
if (andParts.length) parts.push((0, drizzle_orm.and)(...andParts));
|
|
88
|
+
}
|
|
89
|
+
if (where.or?.length) {
|
|
90
|
+
const orParts = where.or.map((item) => {
|
|
91
|
+
if (isWhereCondition(item)) return buildCondition(columns, item);
|
|
92
|
+
return processWhereClause(columns, item);
|
|
93
|
+
}).filter((p) => p !== void 0);
|
|
94
|
+
if (orParts.length) parts.push((0, drizzle_orm.or)(...orParts));
|
|
95
|
+
}
|
|
96
|
+
if (where.not) {
|
|
97
|
+
const notItem = where.not;
|
|
98
|
+
const notCondition = isWhereCondition(notItem) ? buildCondition(columns, notItem) : processWhereClause(columns, notItem);
|
|
99
|
+
if (notCondition) parts.push((0, drizzle_orm.not)(notCondition));
|
|
100
|
+
}
|
|
101
|
+
if (parts.length === 0) {
|
|
102
|
+
if (namesABranch(where)) throw new Error(`Where clause produced no condition: ${JSON.stringify(where)}. Every branch resolved to nothing, which would match every row. Pass {} to mean "no filter".`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (parts.length === 1) return parts[0];
|
|
106
|
+
return (0, drizzle_orm.and)(...parts);
|
|
85
107
|
}
|
|
86
108
|
function isWhereCondition(item) {
|
|
87
|
-
|
|
109
|
+
return "column" in item && "op" in item;
|
|
88
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Whether the clause asked for a constraint at all — as opposed to being the empty clause a
|
|
113
|
+
* caller passes to mean "no filter".
|
|
114
|
+
*
|
|
115
|
+
* An empty `and`/`or` array does NOT count: nothing was named, so nothing could be dropped.
|
|
116
|
+
* What counts is a branch with members in it, or a `not`, because those are the shapes that can
|
|
117
|
+
* resolve to nothing and leave the caller believing a filter was applied.
|
|
118
|
+
*/
|
|
89
119
|
function namesABranch(where) {
|
|
90
|
-
|
|
120
|
+
return Boolean(where.and?.length || where.or?.length || where.not);
|
|
91
121
|
}
|
|
92
122
|
function buildCondition(columns, cond) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
case "ILIKE":
|
|
116
|
-
return drizzleOrm.sql`lower(${col}) like lower(${cond.value})`;
|
|
117
|
-
case "IN":
|
|
118
|
-
return drizzleOrm.inArray(col, cond.value);
|
|
119
|
-
case "NOT IN":
|
|
120
|
-
return drizzleOrm.notInArray(col, cond.value);
|
|
121
|
-
case "IS NULL":
|
|
122
|
-
return drizzleOrm.isNull(col);
|
|
123
|
-
case "IS NOT NULL":
|
|
124
|
-
return drizzleOrm.isNotNull(col);
|
|
125
|
-
case "BETWEEN":
|
|
126
|
-
return drizzleOrm.between(col, cond.value, cond.valueTo);
|
|
127
|
-
case "NOT BETWEEN":
|
|
128
|
-
return drizzleOrm.notBetween(col, cond.value, cond.valueTo);
|
|
129
|
-
case "CONTAINS":
|
|
130
|
-
return drizzleOrm.sql`${col} like ${containsPattern(String(cond.value))} escape '!'`;
|
|
131
|
-
case "ICONTAINS":
|
|
132
|
-
return caseInsensitiveContains(col, String(cond.value));
|
|
133
|
-
default:
|
|
134
|
-
throw new Error(`Unsupported operator: ${cond.op}`);
|
|
135
|
-
}
|
|
123
|
+
const column = columns[cond.column];
|
|
124
|
+
if (!column) throw new Error(`Column "${cond.column}" not found in table. Available: ${Object.keys(columns).join(", ")}`);
|
|
125
|
+
const col = column;
|
|
126
|
+
switch (cond.op) {
|
|
127
|
+
case "=": return (0, drizzle_orm.eq)(col, cond.value);
|
|
128
|
+
case "!=": return (0, drizzle_orm.ne)(col, cond.value);
|
|
129
|
+
case ">": return (0, drizzle_orm.gt)(col, cond.value);
|
|
130
|
+
case "<": return (0, drizzle_orm.lt)(col, cond.value);
|
|
131
|
+
case ">=": return (0, drizzle_orm.gte)(col, cond.value);
|
|
132
|
+
case "<=": return (0, drizzle_orm.lte)(col, cond.value);
|
|
133
|
+
case "LIKE": return (0, drizzle_orm.like)(col, cond.value);
|
|
134
|
+
case "ILIKE": return drizzle_orm.sql`lower(${col}) like lower(${cond.value})`;
|
|
135
|
+
case "IN": return (0, drizzle_orm.inArray)(col, cond.value);
|
|
136
|
+
case "NOT IN": return (0, drizzle_orm.notInArray)(col, cond.value);
|
|
137
|
+
case "IS NULL": return (0, drizzle_orm.isNull)(col);
|
|
138
|
+
case "IS NOT NULL": return (0, drizzle_orm.isNotNull)(col);
|
|
139
|
+
case "BETWEEN": return (0, drizzle_orm.between)(col, cond.value, cond.valueTo);
|
|
140
|
+
case "NOT BETWEEN": return (0, drizzle_orm.notBetween)(col, cond.value, cond.valueTo);
|
|
141
|
+
case "CONTAINS": return drizzle_orm.sql`${col} like ${require_text_match.containsPattern(String(cond.value))} escape '!'`;
|
|
142
|
+
case "ICONTAINS": return require_text_match.caseInsensitiveContains(col, String(cond.value));
|
|
143
|
+
default: throw new Error(`Unsupported operator: ${cond.op}`);
|
|
144
|
+
}
|
|
136
145
|
}
|
|
137
|
-
|
|
138
146
|
// src/pinned-transaction.ts
|
|
147
|
+
/**
|
|
148
|
+
* Replaces `db`'s `transaction` with one that runs each transaction on a
|
|
149
|
+
* connection from `checkout`.
|
|
150
|
+
*
|
|
151
|
+
* Drizzle, bound to that single connection, runs BEGIN, the work and COMMIT
|
|
152
|
+
* or ROLLBACK as it always does. When the transaction fails, a second
|
|
153
|
+
* ROLLBACK goes to the connection: a no-op when Drizzle's own reached it,
|
|
154
|
+
* and the confirmation that the connection is out of the transaction when
|
|
155
|
+
* Drizzle's did not. If that ROLLBACK fails too, nothing can confirm it, and
|
|
156
|
+
* the connection is closed rather than pooled. The work's own error is what
|
|
157
|
+
* the caller receives, never a ROLLBACK's.
|
|
158
|
+
*
|
|
159
|
+
* @param db - A Drizzle handle on the adapter's pool
|
|
160
|
+
* @param checkout - Checks out a connection for one transaction
|
|
161
|
+
* @returns The same handle, its `transaction` replaced
|
|
162
|
+
*/
|
|
139
163
|
function pinTransactions(db, checkout) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
const pinned = async (work, config) => {
|
|
165
|
+
const connection = await checkout();
|
|
166
|
+
let dirty = false;
|
|
167
|
+
let workFailure;
|
|
168
|
+
try {
|
|
169
|
+
return await connection.drizzle.transaction(async (tx) => {
|
|
170
|
+
try {
|
|
171
|
+
return await work(tx);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
workFailure = { error };
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}, config);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
await connection.rollback().catch(() => {
|
|
179
|
+
dirty = true;
|
|
180
|
+
});
|
|
181
|
+
throw workFailure ? workFailure.error : error;
|
|
182
|
+
} finally {
|
|
183
|
+
connection.release(dirty);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
Object.defineProperty(db, "transaction", {
|
|
187
|
+
value: pinned,
|
|
188
|
+
configurable: true,
|
|
189
|
+
writable: true
|
|
190
|
+
});
|
|
191
|
+
return db;
|
|
168
192
|
}
|
|
169
|
-
|
|
170
193
|
// src/transaction-forwarders.ts
|
|
194
|
+
/**
|
|
195
|
+
* Create CRUD and Drizzle instance forwarding methods for a TransactionContext.
|
|
196
|
+
*
|
|
197
|
+
* @remarks
|
|
198
|
+
* TransactionContext delegates its CRUD methods to the adapter's Drizzle CRUD
|
|
199
|
+
* implementation while binding the transaction executor so queries run inside
|
|
200
|
+
* the transaction rather than on the connection pool.
|
|
201
|
+
*
|
|
202
|
+
* @param delegator - Object providing the underlying adapter CRUD methods
|
|
203
|
+
* @param txDb - Thunk returning the transaction-bound Drizzle instance
|
|
204
|
+
* @returns Object with forwarded TransactionContext methods
|
|
205
|
+
*/
|
|
171
206
|
function createTransactionForwarders(delegator, txDb) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
207
|
+
return {
|
|
208
|
+
select: async (table, options) => {
|
|
209
|
+
return delegator.select(table, options, txDb());
|
|
210
|
+
},
|
|
211
|
+
selectOne: async (table, options) => {
|
|
212
|
+
return delegator.selectOne(table, options, txDb());
|
|
213
|
+
},
|
|
214
|
+
updateCount: async (table, data, where) => {
|
|
215
|
+
return delegator.updateCount(table, data, where, txDb());
|
|
216
|
+
},
|
|
217
|
+
delete: async (table, where, options) => {
|
|
218
|
+
return delegator.delete(table, where, options, txDb());
|
|
219
|
+
},
|
|
220
|
+
upsert: async (table, data, options) => {
|
|
221
|
+
return delegator.upsert(table, data, options, txDb());
|
|
222
|
+
},
|
|
223
|
+
getDrizzle: () => txDb()
|
|
224
|
+
};
|
|
190
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* The Drizzle handles one transaction hands out, built once each.
|
|
228
|
+
*
|
|
229
|
+
* `bare` is the instance the delegated CRUD and `getDrizzle()` run on. `withRelations(relations)` is `drizzleWithRelations`: an instance
|
|
230
|
+
* on the SAME connection whose `query` namespace is populated, so relational
|
|
231
|
+
* reads stay inside the transaction. Both are built lazily — a transaction
|
|
232
|
+
* using only raw execute never constructs one — and the relational ones are
|
|
233
|
+
* memoized per relations object, exactly as `getDrizzle()` memoizes its
|
|
234
|
+
* pooled ones.
|
|
235
|
+
*
|
|
236
|
+
* Shared because the three adapters differ only in how they build an
|
|
237
|
+
* instance on their connection, which is what `build` supplies.
|
|
238
|
+
*/
|
|
191
239
|
function transactionDrizzleHandles(build) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
function isDatabaseError(error) {
|
|
208
|
-
return typeof error === "object" && error !== null && "kind" in error && typeof error.kind === "string";
|
|
209
|
-
}
|
|
210
|
-
function createDatabaseError(options) {
|
|
211
|
-
const error = new Error(options.message);
|
|
212
|
-
error.name = "DatabaseError";
|
|
213
|
-
error.kind = options.kind;
|
|
214
|
-
if (options.code !== void 0) error.code = options.code;
|
|
215
|
-
if (options.constraint !== void 0) error.constraint = options.constraint;
|
|
216
|
-
if (options.table !== void 0) error.table = options.table;
|
|
217
|
-
if (options.column !== void 0) error.column = options.column;
|
|
218
|
-
if (options.detail !== void 0) error.detail = options.detail;
|
|
219
|
-
if (options.hint !== void 0) error.hint = options.hint;
|
|
220
|
-
if (options.cause !== void 0) error.cause = options.cause;
|
|
221
|
-
return error;
|
|
240
|
+
let bareInstance;
|
|
241
|
+
const bare = () => bareInstance ??= build();
|
|
242
|
+
const relational = /* @__PURE__ */ new WeakMap();
|
|
243
|
+
const withRelations = (relations) => {
|
|
244
|
+
let cached = relational.get(relations);
|
|
245
|
+
if (cached === void 0) {
|
|
246
|
+
cached = build(relations);
|
|
247
|
+
relational.set(relations, cached);
|
|
248
|
+
}
|
|
249
|
+
return cached;
|
|
250
|
+
};
|
|
251
|
+
return {
|
|
252
|
+
bare,
|
|
253
|
+
withRelations
|
|
254
|
+
};
|
|
222
255
|
}
|
|
256
|
+
// src/update-statement.ts
|
|
257
|
+
/**
|
|
258
|
+
* The transactional UPDATE, assembled by the adapter rather than by the
|
|
259
|
+
* Drizzle query builder.
|
|
260
|
+
*
|
|
261
|
+
* `db.update(table).set(row)` builds its SET clause by walking the table's
|
|
262
|
+
* DECLARED columns and probing `row` for each — never the other way round —
|
|
263
|
+
* so a key naming a column the runtime model does not declare is dropped
|
|
264
|
+
* without a word. One place relies on writing exactly such a column: the
|
|
265
|
+
* localization transition window. `localized` has been flipped on a
|
|
266
|
+
* collection, the runtime model has moved its translatable columns to a
|
|
267
|
+
* companion table that does not exist yet, and the physical main table still
|
|
268
|
+
* holds them. The default locale keeps writing there until the companion
|
|
269
|
+
* appears, and the transactional INSERT already reaches it because every
|
|
270
|
+
* adapter builds that statement itself. This is the UPDATE half of the same
|
|
271
|
+
* rule, shared by the three adapters so the statement is spelled once.
|
|
272
|
+
*
|
|
273
|
+
* Built on Drizzle's `sql` template rather than a string, so each dialect
|
|
274
|
+
* quotes identifiers and numbers placeholders its own way, and the WHERE is
|
|
275
|
+
* the `buildDrizzleWhere` every other read and write goes through — the raw
|
|
276
|
+
* where builders were removed on purpose and this does not bring one back.
|
|
277
|
+
* It returns at most the rows' identity: a caller who asked for the rows back
|
|
278
|
+
* gets them from a read on the same transaction, decoded the way every read
|
|
279
|
+
* is decoded, rather than from a RETURNING list this module would have to
|
|
280
|
+
* decode a second way — and on a dialect with RETURNING that read is by the
|
|
281
|
+
* identities this statement reports, so it is exactly the rows it changed.
|
|
282
|
+
*
|
|
283
|
+
* A value binds in one of three ways, decided per column:
|
|
284
|
+
* - a column the model DECLARES binds through that column's own encoder.
|
|
285
|
+
* `sql.param(value, column)` is the `Param` the query builder would have
|
|
286
|
+
* made for it, so a date, a JSON document or a boolean reaches the driver
|
|
287
|
+
* as the bytes the builder sent before this existed — for every caller
|
|
288
|
+
* that was already using the builder;
|
|
289
|
+
* - a column it does NOT declare binds through the adapter's own sanitizer,
|
|
290
|
+
* which is how that adapter's transactional INSERT binds every value;
|
|
291
|
+
* - a value that is itself a `Column` or an `SQL` fragment is an expression
|
|
292
|
+
* (`{ destination: table.source }` copies a column) and is written as one,
|
|
293
|
+
* which is what the query builder's `mapUpdateSet` does with it.
|
|
294
|
+
*
|
|
295
|
+
* A declared column with an `$onUpdate` callback that the caller did not name
|
|
296
|
+
* is assigned the callback's value, as the query builder assigns it on every
|
|
297
|
+
* update it builds.
|
|
298
|
+
*
|
|
299
|
+
* A key whose value is `undefined` is not written at all — JSON's meaning of
|
|
300
|
+
* an absent key, and what the builder's `mapUpdateSet` did with it; under
|
|
301
|
+
* both spellings of one column the later key decides, undefined included.
|
|
302
|
+
* `null` is written as NULL. A key naming no column on the physical table is
|
|
303
|
+
* a SQL error from the database: the query builder was the one path that
|
|
304
|
+
* could not say so, which is the defect the transition window was hiding
|
|
305
|
+
* behind. A patch that names nothing is refused before any `$onUpdate`
|
|
306
|
+
* column is considered, as the query builder refused it.
|
|
307
|
+
*
|
|
308
|
+
* @module update-statement
|
|
309
|
+
*/
|
|
223
310
|
function isBindableColumn(value) {
|
|
224
|
-
|
|
311
|
+
return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.mapToDriverValue === "function";
|
|
225
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* A JSON column's encoder serializes whatever it is given, so a value that
|
|
315
|
+
* arrives already serialized would be serialized twice. The query-builder path
|
|
316
|
+
* parses such a string first; this keeps the same bytes on the wire.
|
|
317
|
+
*/
|
|
226
318
|
function valueForJsonColumn(value) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
319
|
+
if (typeof value !== "string") return value;
|
|
320
|
+
try {
|
|
321
|
+
return JSON.parse(value);
|
|
322
|
+
} catch {
|
|
323
|
+
return value;
|
|
324
|
+
}
|
|
233
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* A table's columns, indexed by both spellings a caller may use, and the
|
|
328
|
+
* declared columns in order for the `$onUpdate` sweep.
|
|
329
|
+
*/
|
|
234
330
|
function columnsOf(tableObj) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
331
|
+
const byName = /* @__PURE__ */ new Map();
|
|
332
|
+
const declared = [];
|
|
333
|
+
for (const [jsName, column] of Object.entries((0, drizzle_orm.getColumns)(tableObj))) {
|
|
334
|
+
if (!isBindableColumn(column)) continue;
|
|
335
|
+
byName.set(column.name, column);
|
|
336
|
+
byName.set(jsName, column);
|
|
337
|
+
declared.push(column);
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
byName,
|
|
341
|
+
declared
|
|
342
|
+
};
|
|
246
343
|
}
|
|
344
|
+
/** How one value is written: as the expression it already is, or bound. */
|
|
247
345
|
function boundValue(value, column, bindUnmodeled) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
isJsonColumn(column) ? valueForJsonColumn(value) : value,
|
|
252
|
-
column
|
|
253
|
-
)}`;
|
|
254
|
-
}
|
|
255
|
-
return drizzleOrm.sql`${drizzleOrm.sql.param(bindUnmodeled(value))}`;
|
|
346
|
+
if ((0, drizzle_orm.is)(value, drizzle_orm.SQL) || (0, drizzle_orm.is)(value, drizzle_orm.Column)) return drizzle_orm.sql`${value}`;
|
|
347
|
+
if (column) return drizzle_orm.sql`${drizzle_orm.sql.param(isJsonColumn(column) ? valueForJsonColumn(value) : value, column)}`;
|
|
348
|
+
return drizzle_orm.sql`${drizzle_orm.sql.param(bindUnmodeled(value))}`;
|
|
256
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Build the UPDATE for one table.
|
|
352
|
+
*
|
|
353
|
+
* @returns the statement, or `null` when there is nothing to write — every
|
|
354
|
+
* key was `undefined`, or there were none. An UPDATE with an empty SET is a
|
|
355
|
+
* syntax error, so the adapter refuses it by name instead of running it.
|
|
356
|
+
*/
|
|
257
357
|
function buildUpdateStatement(input) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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;
|
|
358
|
+
const { byName, declared } = columnsOf(input.tableObj);
|
|
359
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
360
|
+
for (const [key, value] of Object.entries(input.data)) {
|
|
361
|
+
const column = byName.get(key);
|
|
362
|
+
const name = column?.name ?? key;
|
|
363
|
+
if (value === void 0) {
|
|
364
|
+
assignments.delete(name);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
assignments.set(name, drizzle_orm.sql`${drizzle_orm.sql.identifier(name)} = ${boundValue(value, column, input.bindUnmodeled)}`);
|
|
368
|
+
}
|
|
369
|
+
if (assignments.size === 0) return null;
|
|
370
|
+
for (const column of declared) {
|
|
371
|
+
if (assignments.has(column.name) || column.onUpdateFn === void 0) continue;
|
|
372
|
+
const value = column.onUpdateFn();
|
|
373
|
+
assignments.set(column.name, drizzle_orm.sql`${drizzle_orm.sql.identifier(column.name)} = ${boundValue(value, column, input.bindUnmodeled)}`);
|
|
374
|
+
}
|
|
375
|
+
const statement = drizzle_orm.sql`UPDATE ${input.tableObj} SET ${drizzle_orm.sql.join([...assignments.values()], drizzle_orm.sql`, `)}`;
|
|
376
|
+
const condition = buildDrizzleWhere(input.tableObj, input.where);
|
|
377
|
+
if (condition) statement.append(drizzle_orm.sql` WHERE ${condition}`);
|
|
378
|
+
if (input.returning && input.returning.length > 0) statement.append(drizzle_orm.sql` RETURNING ${drizzle_orm.sql.join(input.returning, drizzle_orm.sql`, `)}`);
|
|
379
|
+
return statement;
|
|
290
380
|
}
|
|
291
|
-
|
|
292
381
|
// src/adapter.ts
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Base database adapter abstract class.
|
|
384
|
+
*
|
|
385
|
+
* @remarks
|
|
386
|
+
* This abstract class provides the foundation for all dialect-specific database adapters
|
|
387
|
+
* (PostgreSQL, MySQL, SQLite). It defines required abstract methods that must be implemented
|
|
388
|
+
* by subclasses, while providing default implementations for CRUD operations and query building.
|
|
389
|
+
*
|
|
390
|
+
* Dialect adapters can override any default method to provide optimized implementations.
|
|
391
|
+
*
|
|
392
|
+
* @packageDocumentation
|
|
393
|
+
*/
|
|
394
|
+
/**
|
|
395
|
+
* Where a test harness installs the observer {@link DrizzleAdapter.notePoolAccess}
|
|
396
|
+
* reports a transaction escape to: a global registry key rather than an
|
|
397
|
+
* import, so the harness and every copy of this package agree on it.
|
|
398
|
+
*/
|
|
399
|
+
const TRANSACTION_ESCAPE_OBSERVER = Symbol.for("nextly.transactionEscapeObserver");
|
|
400
|
+
/**
|
|
401
|
+
* The SQL text a pool call was handed: a string, or the `text` (pg) or `sql`
|
|
402
|
+
* (mysql2) of a query object. Undefined for a connection checkout.
|
|
403
|
+
*/
|
|
404
|
+
function statementOf(argument) {
|
|
405
|
+
if (typeof argument === "string") return argument;
|
|
406
|
+
if (typeof argument !== "object" || argument === null) return void 0;
|
|
407
|
+
const { text, sql } = argument;
|
|
408
|
+
if (typeof text === "string") return text;
|
|
409
|
+
return typeof sql === "string" ? sql : void 0;
|
|
410
|
+
}
|
|
411
|
+
/** The escape observer a test harness installed; undefined in production. */
|
|
412
|
+
function escapeObserver() {
|
|
413
|
+
const observe = globalThis[TRANSACTION_ESCAPE_OBSERVER];
|
|
414
|
+
return typeof observe === "function" ? observe : void 0;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* The current stack, deep enough to reach the code that made the query. The
|
|
418
|
+
* driver's and Drizzle's own frames fill V8's default of ten, which cut every
|
|
419
|
+
* report off before its caller. Raised only for this capture and restored
|
|
420
|
+
* after, so nothing else sees the deeper limit; it runs only for an observer a
|
|
421
|
+
* test harness installed.
|
|
422
|
+
*/
|
|
423
|
+
function deepStack() {
|
|
424
|
+
const limit = Error.stackTraceLimit;
|
|
425
|
+
try {
|
|
426
|
+
Error.stackTraceLimit = 64;
|
|
427
|
+
return (/* @__PURE__ */ new Error()).stack ?? "";
|
|
428
|
+
} finally {
|
|
429
|
+
Error.stackTraceLimit = limit;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Matches the `dataType` a Drizzle timestamp/datetime column reports, in both
|
|
434
|
+
* the bare form and the object-qualified form some column classes carry.
|
|
435
|
+
*/
|
|
436
|
+
const DATE_DATA_TYPE = /(?:^|\s)date$/;
|
|
437
|
+
/**
|
|
438
|
+
* How far an echoed timestamp may sit from the one that was bound and still
|
|
439
|
+
* count as the same value.
|
|
440
|
+
*
|
|
441
|
+
* A column with no fractional precision keeps whole seconds, so a stored value
|
|
442
|
+
* can differ from the bound one by up to a second without anything being wrong.
|
|
443
|
+
* Every real UTC offset is at least fifteen minutes, so a shift is never
|
|
444
|
+
* mistaken for rounding.
|
|
445
|
+
*/
|
|
446
|
+
/**
|
|
447
|
+
* Stem for the alias a statement spells a timestamp's wall clock out under.
|
|
448
|
+
*
|
|
449
|
+
* Short on purpose. PostgreSQL truncates an identifier at 63 bytes, and a
|
|
450
|
+
* truncated alias is worse than a rejected one: the statement still succeeds,
|
|
451
|
+
* the lookup misses the name it asked for, and the row keeps both the driver's
|
|
452
|
+
* value and a stray property. A stem plus an index stays far inside the limit
|
|
453
|
+
* whatever the column is called.
|
|
454
|
+
*/
|
|
455
|
+
const WALL_CLOCK_ALIAS_STEM = "__nx_wc";
|
|
456
|
+
/**
|
|
457
|
+
* A timestamp as the database itself spells it, in a fixed field order.
|
|
458
|
+
*
|
|
459
|
+
* Matched rather than handed to `new Date`, so nothing depends on the host's
|
|
460
|
+
* parsing of a partial date. The fraction is optional and of any length, which
|
|
461
|
+
* covers PostgreSQL's milliseconds and MySQL's microseconds alike.
|
|
462
|
+
*/
|
|
463
|
+
const WALL_CLOCK_PATTERN = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?$/;
|
|
464
|
+
/**
|
|
465
|
+
* Read a database's own rendering of a timestamp as UTC.
|
|
466
|
+
*
|
|
467
|
+
* The statement wrote a UTC wall clock, so reading one back as UTC is what
|
|
468
|
+
* makes a write agree with a later read. The rendering is one the statement
|
|
469
|
+
* asked for by name, so its shape does not depend on a session setting.
|
|
470
|
+
*
|
|
471
|
+
* Anything that does not match answers nothing, leaving the driver's value in
|
|
472
|
+
* place rather than replacing it with an `Invalid Date`.
|
|
473
|
+
*/
|
|
296
474
|
function parseWallClockAsUtc(text) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
Number(month) - 1,
|
|
305
|
-
Number(day),
|
|
306
|
-
Number(hour),
|
|
307
|
-
Number(minute),
|
|
308
|
-
Number(second),
|
|
309
|
-
millis
|
|
310
|
-
)
|
|
311
|
-
);
|
|
312
|
-
if (Number(year) < 100) parsed.setUTCFullYear(Number(year));
|
|
313
|
-
return parsed;
|
|
475
|
+
const match = WALL_CLOCK_PATTERN.exec(text.trim());
|
|
476
|
+
if (!match) return void 0;
|
|
477
|
+
const [, year, month, day, hour, minute, second, fraction = ""] = match;
|
|
478
|
+
const millis = Number(`${fraction}000`.slice(0, 3));
|
|
479
|
+
const parsed = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second), millis));
|
|
480
|
+
if (Number(year) < 100) parsed.setUTCFullYear(Number(year));
|
|
481
|
+
return parsed;
|
|
314
482
|
}
|
|
483
|
+
/** Whether a table property is a date column that can convert its own values. */
|
|
315
484
|
function isDateColumn(colDef) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
485
|
+
if (!colDef || typeof colDef !== "object") return false;
|
|
486
|
+
const candidate = colDef;
|
|
487
|
+
return typeof candidate.mapFromDriverValue === "function" && typeof candidate.mapToDriverValue === "function" && DATE_DATA_TYPE.test(String(candidate.dataType));
|
|
319
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Whether a date column records the zone its value belongs to.
|
|
491
|
+
*
|
|
492
|
+
* A column declared WITH a time zone stores an instant, so a driver reading it
|
|
493
|
+
* back gets the same moment whatever zone either end is in -- there is nothing
|
|
494
|
+
* ambiguous to resolve and nothing to correct. Rendering one as text would
|
|
495
|
+
* spell it in the session's zone, and reading that as UTC would move it.
|
|
496
|
+
*/
|
|
320
497
|
function carriesTimeZone(colDef) {
|
|
321
|
-
|
|
498
|
+
return colDef.withTimezone === true;
|
|
322
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Abstract base class for database adapters.
|
|
502
|
+
*
|
|
503
|
+
* @remarks
|
|
504
|
+
* All dialect-specific adapters must extend this class and implement the abstract methods.
|
|
505
|
+
* Default implementations are provided for CRUD operations, which can be overridden for
|
|
506
|
+
* optimization or dialect-specific behavior.
|
|
507
|
+
*
|
|
508
|
+
* ## Required Implementations
|
|
509
|
+
*
|
|
510
|
+
* Subclasses must implement:
|
|
511
|
+
* - `dialect` - Database dialect identifier
|
|
512
|
+
* - `connect()` - Establish database connection
|
|
513
|
+
* - `disconnect()` - Close database connection
|
|
514
|
+
* - `executeQuery()` - Execute raw SQL query
|
|
515
|
+
* - `transaction()` - Execute operations within a transaction
|
|
516
|
+
* - `getCapabilities()` - Report database feature support
|
|
517
|
+
*
|
|
518
|
+
* ## Optional Overrides
|
|
519
|
+
*
|
|
520
|
+
* Subclasses can override default CRUD methods for optimization:
|
|
521
|
+
* - `select()`, `selectOne()` - Custom query optimization
|
|
522
|
+
* - `insert()`, `insertMany()` - Bulk insert optimization
|
|
523
|
+
* - `update()`, `delete()` - Custom update/delete logic
|
|
524
|
+
* - `upsert()` - Dialect-specific upsert syntax
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```typescript
|
|
528
|
+
* export class PostgresAdapter extends DrizzleAdapter {
|
|
529
|
+
* readonly dialect = 'postgresql' as const;
|
|
530
|
+
*
|
|
531
|
+
* async connect() {
|
|
532
|
+
* this.pool = new Pool({ connectionString: this.config.url });
|
|
533
|
+
* // ... connection logic
|
|
534
|
+
* }
|
|
535
|
+
*
|
|
536
|
+
* async executeQuery<T>(sql: string, params?: SqlParam[]) {
|
|
537
|
+
* const result = await this.pool.query(sql, params);
|
|
538
|
+
* return result.rows as T[];
|
|
539
|
+
* }
|
|
540
|
+
*
|
|
541
|
+
* // ... other required methods
|
|
542
|
+
* }
|
|
543
|
+
* ```
|
|
544
|
+
*
|
|
545
|
+
* @public
|
|
546
|
+
*/
|
|
547
|
+
/**
|
|
548
|
+
* Rows affected by a write, normalised across the driver shapes Drizzle returns.
|
|
549
|
+
*
|
|
550
|
+
* mysql2 resolves to `[ResultSetHeader, FieldPacket[]]`, so treating any array
|
|
551
|
+
* as a row list reports the tuple length — a constant 2 whether the statement
|
|
552
|
+
* matched nothing, one row, or many. Callers that branch on the count (a
|
|
553
|
+
* conditional claim, a retention loop) silently get the wrong answer on MySQL,
|
|
554
|
+
* so the header is read first. node-postgres reports `rowCount` and
|
|
555
|
+
* better-sqlite3 reports `changes`; a plain array is a RETURNING row list, whose
|
|
556
|
+
* length is the count.
|
|
557
|
+
*/
|
|
323
558
|
function affectedRowCount(result) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if (typeof changes === "number") return changes;
|
|
336
|
-
return 0;
|
|
559
|
+
if (Array.isArray(result)) {
|
|
560
|
+
const header = result[0];
|
|
561
|
+
if (header && typeof header.affectedRows === "number") return header.affectedRows;
|
|
562
|
+
return result.length;
|
|
563
|
+
}
|
|
564
|
+
const record = result;
|
|
565
|
+
const rowCount = record?.rowCount;
|
|
566
|
+
if (typeof rowCount === "number") return rowCount;
|
|
567
|
+
const changes = record?.changes;
|
|
568
|
+
if (typeof changes === "number") return changes;
|
|
569
|
+
return 0;
|
|
337
570
|
}
|
|
338
571
|
var DrizzleAdapter = class {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
/**
|
|
1631
|
-
* Drop a table.
|
|
1632
|
-
*
|
|
1633
|
-
* @remarks
|
|
1634
|
-
* Default implementation is a placeholder. Subclasses should implement
|
|
1635
|
-
* table dropping logic.
|
|
1636
|
-
*
|
|
1637
|
-
* @param tableName - Name of table to drop
|
|
1638
|
-
* @param options - Drop options
|
|
1639
|
-
*
|
|
1640
|
-
* @throws {DatabaseError} If table drop fails
|
|
1641
|
-
*/
|
|
1642
|
-
async dropTable(tableName, options) {
|
|
1643
|
-
const ifExists = options?.ifExists !== false ? "IF EXISTS " : "";
|
|
1644
|
-
const cascade = options?.cascade ? " CASCADE" : "";
|
|
1645
|
-
const query = `DROP TABLE ${ifExists}${this.escapeIdentifier(tableName)}${cascade}`;
|
|
1646
|
-
try {
|
|
1647
|
-
await this.executeQuery(query);
|
|
1648
|
-
} catch (error) {
|
|
1649
|
-
throw this.handleQueryError(error, "dropTable", tableName);
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
/**
|
|
1653
|
-
* Alter an existing table.
|
|
1654
|
-
*
|
|
1655
|
-
* @remarks
|
|
1656
|
-
* Default implementation is a placeholder. Subclasses should implement
|
|
1657
|
-
* table alteration logic.
|
|
1658
|
-
*
|
|
1659
|
-
* @param tableName - Name of table to alter
|
|
1660
|
-
* @param operations - Alteration operations
|
|
1661
|
-
* @param options - Alter options
|
|
1662
|
-
*
|
|
1663
|
-
* @throws {DatabaseError} If table alteration fails
|
|
1664
|
-
*/
|
|
1665
|
-
async alterTable(tableName, operations, _options) {
|
|
1666
|
-
const quotedTable = this.escapeIdentifier(tableName);
|
|
1667
|
-
for (const op of operations) {
|
|
1668
|
-
let query;
|
|
1669
|
-
switch (op.kind) {
|
|
1670
|
-
case "add_column": {
|
|
1671
|
-
let colDef = `${this.escapeIdentifier(op.column.name)} ${op.column.type.toUpperCase()}`;
|
|
1672
|
-
if (op.column.nullable === false) colDef += " NOT NULL";
|
|
1673
|
-
if (op.column.unique) colDef += " UNIQUE";
|
|
1674
|
-
if (op.column.default !== void 0) {
|
|
1675
|
-
const defaultVal = typeof op.column.default === "object" && op.column.default !== null && "sql" in op.column.default ? op.column.default.sql : typeof op.column.default === "string" ? `'${op.column.default}'` : String(op.column.default);
|
|
1676
|
-
colDef += ` DEFAULT ${defaultVal}`;
|
|
1677
|
-
}
|
|
1678
|
-
query = `ALTER TABLE ${quotedTable} ADD COLUMN ${colDef}`;
|
|
1679
|
-
break;
|
|
1680
|
-
}
|
|
1681
|
-
case "drop_column": {
|
|
1682
|
-
const cascade = op.cascade ? " CASCADE" : "";
|
|
1683
|
-
query = `ALTER TABLE ${quotedTable} DROP COLUMN ${this.escapeIdentifier(op.columnName)}${cascade}`;
|
|
1684
|
-
break;
|
|
1685
|
-
}
|
|
1686
|
-
case "rename_column":
|
|
1687
|
-
query = `ALTER TABLE ${quotedTable} RENAME COLUMN ${this.escapeIdentifier(op.from)} TO ${this.escapeIdentifier(op.to)}`;
|
|
1688
|
-
break;
|
|
1689
|
-
case "modify_column": {
|
|
1690
|
-
query = `ALTER TABLE ${quotedTable} ALTER COLUMN ${this.escapeIdentifier(op.column.name)} TYPE ${op.column.type.toUpperCase()}`;
|
|
1691
|
-
break;
|
|
1692
|
-
}
|
|
1693
|
-
case "add_constraint":
|
|
1694
|
-
{
|
|
1695
|
-
const constraintCols = op.constraint.columns ?? [];
|
|
1696
|
-
const colList = constraintCols.map((c) => this.escapeIdentifier(c)).join(", ");
|
|
1697
|
-
if (op.constraint.type === "check" && op.constraint.expression) {
|
|
1698
|
-
query = `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${this.escapeIdentifier(op.constraint.name)} CHECK (${op.constraint.expression})`;
|
|
1699
|
-
} else {
|
|
1700
|
-
query = `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${this.escapeIdentifier(op.constraint.name)} ${op.constraint.type.toUpperCase()} (${colList})`;
|
|
1701
|
-
}
|
|
1702
|
-
break;
|
|
1703
|
-
}
|
|
1704
|
-
case "drop_constraint": {
|
|
1705
|
-
const cascadeConstraint = op.cascade ? " CASCADE" : "";
|
|
1706
|
-
query = `ALTER TABLE ${quotedTable} DROP CONSTRAINT ${this.escapeIdentifier(op.constraintName)}${cascadeConstraint}`;
|
|
1707
|
-
break;
|
|
1708
|
-
}
|
|
1709
|
-
default:
|
|
1710
|
-
throw this.createDatabaseError(
|
|
1711
|
-
"query",
|
|
1712
|
-
`Unsupported alter table operation: ${op.kind}`,
|
|
1713
|
-
void 0
|
|
1714
|
-
);
|
|
1715
|
-
}
|
|
1716
|
-
try {
|
|
1717
|
-
await this.executeQuery(query);
|
|
1718
|
-
} catch (error) {
|
|
1719
|
-
throw this.handleQueryError(error, "alterTable", tableName);
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
/**
|
|
1724
|
-
* Check if a table exists in the database.
|
|
1725
|
-
*
|
|
1726
|
-
* @remarks
|
|
1727
|
-
* Uses dialect-specific information schema queries to check table existence.
|
|
1728
|
-
* This is useful for development mode auto-sync to determine whether to
|
|
1729
|
-
* CREATE or DROP/CREATE tables.
|
|
1730
|
-
*
|
|
1731
|
-
* @param tableName - Name of table to check
|
|
1732
|
-
* @param schema - Optional schema name (defaults to 'public' for PostgreSQL)
|
|
1733
|
-
* @returns True if table exists, false otherwise
|
|
1734
|
-
*
|
|
1735
|
-
* @throws {DatabaseError} If query fails
|
|
1736
|
-
*
|
|
1737
|
-
* @example
|
|
1738
|
-
* ```typescript
|
|
1739
|
-
* const exists = await adapter.tableExists('users');
|
|
1740
|
-
* if (exists) {
|
|
1741
|
-
* await adapter.dropTable('users');
|
|
1742
|
-
* }
|
|
1743
|
-
* await adapter.createTable(userTableDef);
|
|
1744
|
-
* ```
|
|
1745
|
-
*/
|
|
1746
|
-
async tableExists(tableName, schema) {
|
|
1747
|
-
try {
|
|
1748
|
-
let sql6;
|
|
1749
|
-
const params = [];
|
|
1750
|
-
switch (this.dialect) {
|
|
1751
|
-
case "postgresql":
|
|
1752
|
-
sql6 = `
|
|
572
|
+
/**
|
|
573
|
+
* Whether a `transaction()` call made from the current async context would
|
|
574
|
+
* join a transaction already open on this adapter, as a savepoint, rather
|
|
575
|
+
* than open one of its own.
|
|
576
|
+
*
|
|
577
|
+
* A caller about to do something no rollback can undo — removing a stored
|
|
578
|
+
* file, say — asks this first: work that joins an enclosing transaction is
|
|
579
|
+
* undone with it, while the effect it performs after its own savepoint
|
|
580
|
+
* releases is not.
|
|
581
|
+
*
|
|
582
|
+
* False here. Concrete rather than abstract so existing adapters keep
|
|
583
|
+
* working unchanged, and false is right for a pooled adapter: a call made
|
|
584
|
+
* inside another transaction's work opens its own transaction on its own
|
|
585
|
+
* connection, which commits independently.
|
|
586
|
+
*
|
|
587
|
+
* @returns True when the call would nest in an open transaction
|
|
588
|
+
*/
|
|
589
|
+
inTransaction() {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* The transaction each async flow is inside, kept per flow rather than per
|
|
594
|
+
* connection: on SQLite every request shares the one connection, and
|
|
595
|
+
* another request's open transaction says nothing about this one.
|
|
596
|
+
*/
|
|
597
|
+
flowTransactions = new node_async_hooks.AsyncLocalStorage();
|
|
598
|
+
/** Each pool's reporting views, by method list (`observedPool`). Gone with the pool. */
|
|
599
|
+
observedPools = /* @__PURE__ */ new WeakMap();
|
|
600
|
+
/**
|
|
601
|
+
* Whether the calling async flow is inside a transaction on this adapter
|
|
602
|
+
* that has not finished: its own, or one it was started from inside.
|
|
603
|
+
*
|
|
604
|
+
* A write that must outlive any rollback — a count of a failed attempt, the
|
|
605
|
+
* spend of a single-use secret — asks this before running in a transaction
|
|
606
|
+
* of its own. Made inside an open transaction on SQLite, it joins that
|
|
607
|
+
* transaction as a savepoint and is undone with it. On PostgreSQL and MySQL
|
|
608
|
+
* it would commit on a connection of its own, so refusing it there too
|
|
609
|
+
* keeps code that passes on one dialect from failing on another. Unlike
|
|
610
|
+
* {@link inTransaction}, which asks whether a call would nest and is always
|
|
611
|
+
* false on a pooled adapter, this answers alike on every dialect, and
|
|
612
|
+
* another flow's open transaction never answers true.
|
|
613
|
+
*
|
|
614
|
+
* Covers transactions opened through {@link transaction} and through the
|
|
615
|
+
* Drizzle handle's own `transaction` on the dialects whose handle takes
|
|
616
|
+
* async work. A dialect that keeps scopes of its own judges from those
|
|
617
|
+
* instead; an adapter that does neither answers false.
|
|
618
|
+
*
|
|
619
|
+
* @experimental
|
|
620
|
+
* @returns True while a transaction the calling flow is inside is open
|
|
621
|
+
*/
|
|
622
|
+
transactionOpenInFlow() {
|
|
623
|
+
for (let at = this.flowTransactions.getStore(); at !== void 0; at = at.enclosing) if (at.open) return true;
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Run a whole transaction, from its BEGIN until its COMMIT or ROLLBACK has
|
|
628
|
+
* run, as the calling flow's open transaction, for
|
|
629
|
+
* {@link transactionOpenInFlow}. A dialect's `transaction` runs through
|
|
630
|
+
* this unless it judges the flow from scopes of its own.
|
|
631
|
+
*
|
|
632
|
+
* The transaction counts as open for its work and for anything the work
|
|
633
|
+
* starts, awaited or not, until it has finished. Something the work starts
|
|
634
|
+
* that outlives it then sees it closed, or the one enclosing it still open.
|
|
635
|
+
*
|
|
636
|
+
* @param run - The transaction, BEGIN to COMMIT or ROLLBACK
|
|
637
|
+
* @returns What `run` resolves to
|
|
638
|
+
*/
|
|
639
|
+
runInFlowTransaction(run) {
|
|
640
|
+
const scope = {
|
|
641
|
+
open: true,
|
|
642
|
+
connected: false,
|
|
643
|
+
enclosing: this.flowTransactions.getStore(),
|
|
644
|
+
openedAt: escapeObserver() === void 0 ? void 0 : deepStack()
|
|
645
|
+
};
|
|
646
|
+
return this.flowTransactions.run(scope, async () => {
|
|
647
|
+
try {
|
|
648
|
+
return await run();
|
|
649
|
+
} finally {
|
|
650
|
+
scope.open = false;
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Record that the calling flow's transaction now holds its own connection.
|
|
656
|
+
*
|
|
657
|
+
* A pooled dialect calls this right after checking out the connection a
|
|
658
|
+
* transaction runs on, so {@link notePoolAccess} can tell that checkout,
|
|
659
|
+
* which belongs to the transaction, from any later one, which escapes it.
|
|
660
|
+
*/
|
|
661
|
+
markFlowTransactionConnected() {
|
|
662
|
+
const scope = this.flowTransactions.getStore();
|
|
663
|
+
if (scope !== void 0) scope.connected = true;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Check out, through `checkout`, the connection the calling flow's
|
|
667
|
+
* transaction runs on. A transaction opened inside another's work takes a
|
|
668
|
+
* second connection, so the checkout is reported first
|
|
669
|
+
* ({@link notePoolAccess}); once it holds its own, the transaction is
|
|
670
|
+
* marked connected, so its later checkouts are told apart from it.
|
|
671
|
+
*
|
|
672
|
+
* @param checkout - Takes a connection from the dialect's pool
|
|
673
|
+
* @returns The connection
|
|
674
|
+
*/
|
|
675
|
+
async connectFlowTransaction(checkout) {
|
|
676
|
+
this.notePoolAccess();
|
|
677
|
+
const connection = await checkout();
|
|
678
|
+
this.markFlowTransactionConnected();
|
|
679
|
+
return connection;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Called by a pooled dialect before it takes anything from its pool: a
|
|
683
|
+
* query, or a connection checkout.
|
|
684
|
+
*
|
|
685
|
+
* While the calling flow is inside a transaction that already holds its own
|
|
686
|
+
* connection, a pool access runs on another connection: outside the
|
|
687
|
+
* transaction, blind to its uncommitted writes, not undone by its rollback,
|
|
688
|
+
* and able to stall a small pool. Such an access is reported to the
|
|
689
|
+
* observer a test harness installs under
|
|
690
|
+
* `Symbol.for("nextly.transactionEscapeObserver")`, with the stack that
|
|
691
|
+
* names its call site. With no observer installed, which is every
|
|
692
|
+
* production process, this is one property read.
|
|
693
|
+
*
|
|
694
|
+
* @param statement - What the pool call was handed, its SQL passed on so
|
|
695
|
+
* the harness can say which statement escaped; nothing for a checkout
|
|
696
|
+
*/
|
|
697
|
+
notePoolAccess(statement) {
|
|
698
|
+
const observe = escapeObserver();
|
|
699
|
+
if (observe === void 0) return;
|
|
700
|
+
for (let at = this.flowTransactions.getStore(); at !== void 0; at = at.enclosing) if (at.open && at.connected) {
|
|
701
|
+
observe({
|
|
702
|
+
dialect: this.dialect,
|
|
703
|
+
stack: deepStack(),
|
|
704
|
+
openedAt: at.openedAt,
|
|
705
|
+
statement: statementOf(statement)
|
|
706
|
+
});
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* `pool`, with {@link notePoolAccess} called before each of `methods`, for
|
|
712
|
+
* handing to code that queries the pool directly, such as Drizzle's handle.
|
|
713
|
+
* Everything else, `instanceof` included, reaches the pool unchanged.
|
|
714
|
+
*
|
|
715
|
+
* One view per pool and method list, made once: every Drizzle handle on the pool is given the
|
|
716
|
+
* same one, so `getDrizzle()` and `getDrizzle(relations)` share a `$client`, as they did when
|
|
717
|
+
* each was handed the pool itself.
|
|
718
|
+
*
|
|
719
|
+
* @param pool - The driver's pool
|
|
720
|
+
* @param methods - The pool methods that take a connection
|
|
721
|
+
* @returns A view of `pool` that reports those calls
|
|
722
|
+
*/
|
|
723
|
+
observedPool(pool, methods) {
|
|
724
|
+
const key = methods.join(",");
|
|
725
|
+
const views = this.observedPools.get(pool) ?? /* @__PURE__ */ new Map();
|
|
726
|
+
this.observedPools.set(pool, views);
|
|
727
|
+
const cached = views.get(key);
|
|
728
|
+
if (cached) return cached;
|
|
729
|
+
const view = new Proxy(pool, { get: (target, property, receiver) => {
|
|
730
|
+
const value = Reflect.get(target, property, receiver);
|
|
731
|
+
if (typeof property !== "string" || !methods.includes(property) || typeof value !== "function") return value;
|
|
732
|
+
return (...args) => {
|
|
733
|
+
this.notePoolAccess(args[0]);
|
|
734
|
+
return Reflect.apply(value, target, args);
|
|
735
|
+
};
|
|
736
|
+
} });
|
|
737
|
+
views.set(key, view);
|
|
738
|
+
return view;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Run each of a pool-bound Drizzle handle's transactions on a connection
|
|
742
|
+
* `checkout` hands it, and return that connection as the adapter's own
|
|
743
|
+
* `transaction()` does: closed rather than pooled when no ROLLBACK can be
|
|
744
|
+
* confirmed on it, and never leaked by a failed BEGIN (`pinTransactions`).
|
|
745
|
+
*
|
|
746
|
+
* For PostgreSQL's and MySQL's handles. Apply it before
|
|
747
|
+
* {@link trackFlowTransactions}, which wraps whatever `transaction` the
|
|
748
|
+
* handle has at that point.
|
|
749
|
+
*
|
|
750
|
+
* @param db - A Drizzle handle on this adapter's pool
|
|
751
|
+
* @param checkout - Checks out a connection for one transaction
|
|
752
|
+
* @returns The same handle, its `transaction` replaced
|
|
753
|
+
*/
|
|
754
|
+
pinDrizzleTransactions(db, checkout) {
|
|
755
|
+
return pinTransactions(db, checkout);
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Make a Drizzle handle's own `transaction` count for
|
|
759
|
+
* {@link transactionOpenInFlow}, as the adapter's does.
|
|
760
|
+
*
|
|
761
|
+
* Core's services run their transactions through the handle on PostgreSQL
|
|
762
|
+
* and MySQL; without this, a call made inside one would be refused on
|
|
763
|
+
* SQLite, where they go through the adapter, and let through on the
|
|
764
|
+
* others. Not for SQLite's handle: its `transaction` takes only
|
|
765
|
+
* synchronous work, which no async flow can be inside.
|
|
766
|
+
*
|
|
767
|
+
* @param db - A Drizzle handle this adapter created
|
|
768
|
+
* @returns The same handle, its `transaction` tracked
|
|
769
|
+
*/
|
|
770
|
+
trackFlowTransactions(db) {
|
|
771
|
+
const transactor = db;
|
|
772
|
+
const untracked = transactor.transaction.bind(transactor);
|
|
773
|
+
const tracked = (work, config) => this.runInFlowTransaction(() => untracked(work, config));
|
|
774
|
+
Object.defineProperty(db, "transaction", {
|
|
775
|
+
value: tracked,
|
|
776
|
+
configurable: true,
|
|
777
|
+
writable: true
|
|
778
|
+
});
|
|
779
|
+
return db;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Run `effect` once the write made from the current async context has
|
|
783
|
+
* committed: an event, a hook, a cache flush, anything outside the database
|
|
784
|
+
* that must not happen for a change that is rolled back.
|
|
785
|
+
*
|
|
786
|
+
* A caller invokes this after its own transaction has resolved. Where that
|
|
787
|
+
* transaction was nested in an enclosing one, as a savepoint, the change is
|
|
788
|
+
* not durable yet, and an adapter that nests (see {@link inTransaction})
|
|
789
|
+
* holds `effect` until the outermost transaction commits, and drops it when
|
|
790
|
+
* that transaction, or the savepoint it was registered in, rolls back. A
|
|
791
|
+
* held effect runs after the commit, before the outermost `transaction()`
|
|
792
|
+
* call resolves, in registration order; one that then throws or rejects is
|
|
793
|
+
* handed to `onDeferredFailure` rather than failing a transaction that has
|
|
794
|
+
* committed.
|
|
795
|
+
*
|
|
796
|
+
* Here `effect` runs now and the returned promise settles with it, its
|
|
797
|
+
* failure included. Concrete rather than abstract, as `inTransaction` is,
|
|
798
|
+
* and right for a pooled adapter: each transaction has its own connection
|
|
799
|
+
* and has committed by the time its caller continues.
|
|
800
|
+
*
|
|
801
|
+
* @experimental
|
|
802
|
+
* @param effect - What to run once the change is durable
|
|
803
|
+
* @param onDeferredFailure - Receives the error of a held effect that fails
|
|
804
|
+
* @returns Settles once `effect` has run, or once it has been held
|
|
805
|
+
*/
|
|
806
|
+
async afterCommit(effect, _onDeferredFailure) {
|
|
807
|
+
await effect();
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Table resolver for looking up Drizzle table objects by name.
|
|
811
|
+
* When set, CRUD methods use Drizzle's query API instead of raw SQL.
|
|
812
|
+
* Set via setTableResolver() after boot-time schema loading.
|
|
813
|
+
*/
|
|
814
|
+
tableResolver = null;
|
|
815
|
+
/**
|
|
816
|
+
* Set the table resolver for Drizzle query API support.
|
|
817
|
+
* When a resolver is set, CRUD methods (select, insert, update, delete, upsert)
|
|
818
|
+
* will use Drizzle's query API (db.select().from(), etc.) instead of raw SQL
|
|
819
|
+
* string building. Falls back to raw SQL if the resolver doesn't have the table.
|
|
820
|
+
*
|
|
821
|
+
* @param resolver - TableResolver implementation (e.g. SchemaRegistry)
|
|
822
|
+
*/
|
|
823
|
+
setTableResolver(resolver) {
|
|
824
|
+
this.tableResolver = resolver;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Get a Drizzle table object by name from the resolver.
|
|
828
|
+
* Returns null if no resolver is set or table is not found.
|
|
829
|
+
*/
|
|
830
|
+
getTableObject(tableName) {
|
|
831
|
+
return this.tableResolver?.getTable(tableName) ?? null;
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Run a Drizzle-built statement and return its rows.
|
|
835
|
+
*
|
|
836
|
+
* @remarks
|
|
837
|
+
* The CRUD methods resolve their table through the schema registry and reject
|
|
838
|
+
* any name it does not declare, which leaves no way to read from a table the
|
|
839
|
+
* ORM does not know — one mid-rename, above all — except by assembling SQL and
|
|
840
|
+
* quoting identifiers by hand. This takes Drizzle's `sql` template instead, so
|
|
841
|
+
* the dialect in use decides the quoting and the parameter binding.
|
|
842
|
+
*
|
|
843
|
+
* Concrete rather than abstract so existing adapters keep working unchanged.
|
|
844
|
+
* The three drivers disagree about both the call and the result: node-postgres
|
|
845
|
+
* returns `{ rows }`, mysql2 a `[rows, fields]` tuple, and better-sqlite3 has
|
|
846
|
+
* no `execute` at all and answers `all`. Keeping that here rather than at each
|
|
847
|
+
* call site is the point — a caller reasoning about it would be reasoning
|
|
848
|
+
* about a driver it cannot see.
|
|
849
|
+
*
|
|
850
|
+
* @param statement - Drizzle `sql` template to run
|
|
851
|
+
* @returns Rows the statement produced
|
|
852
|
+
*/
|
|
853
|
+
async queryStatement(statement) {
|
|
854
|
+
if (this.getCapabilities().dialect === "sqlite") return await this.getDrizzle().all(statement);
|
|
855
|
+
const result = await this.getDrizzle().execute(statement);
|
|
856
|
+
if (Array.isArray(result)) return Array.isArray(result[0]) ? result[0] : result;
|
|
857
|
+
if (typeof result === "object" && result !== null && Array.isArray(result.rows)) return result.rows;
|
|
858
|
+
throw this.createDatabaseError("query", "Drizzle statement returned a result shape this adapter does not recognise; refusing to report it as an empty result.", void 0);
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Map data keys from SQL column names (snake_case) to Drizzle JS property names (camelCase).
|
|
862
|
+
* Drizzle schemas define columns as e.g. `createdAt: timestamp("created_at")` — the JS
|
|
863
|
+
* property is `createdAt` but the SQL column is `created_at`. Services pass snake_case keys
|
|
864
|
+
* because they match the DB column names. This method maps them to the JS names Drizzle expects.
|
|
865
|
+
*/
|
|
866
|
+
mapDataToColumnNames(tableObj, data) {
|
|
867
|
+
if (!tableObj || typeof tableObj !== "object") return data;
|
|
868
|
+
const sqlToJs = /* @__PURE__ */ new Map();
|
|
869
|
+
const jsonColumns = /* @__PURE__ */ new Set();
|
|
870
|
+
for (const [jsName, colDef] of Object.entries(tableObj)) {
|
|
871
|
+
if (!colDef || typeof colDef !== "object" || !("name" in colDef) || typeof colDef.name !== "string") continue;
|
|
872
|
+
const sqlName = colDef.name;
|
|
873
|
+
sqlToJs.set(sqlName, jsName);
|
|
874
|
+
if (isJsonColumn(colDef)) jsonColumns.add(jsName);
|
|
875
|
+
}
|
|
876
|
+
if (sqlToJs.size === 0) return data;
|
|
877
|
+
const mapped = {};
|
|
878
|
+
for (const [key, value] of Object.entries(data)) {
|
|
879
|
+
const jsKey = sqlToJs.get(key) ?? key;
|
|
880
|
+
if (jsonColumns.has(jsKey) && typeof value === "string") try {
|
|
881
|
+
mapped[jsKey] = JSON.parse(value);
|
|
882
|
+
} catch {
|
|
883
|
+
mapped[jsKey] = value;
|
|
884
|
+
}
|
|
885
|
+
else mapped[jsKey] = value;
|
|
886
|
+
}
|
|
887
|
+
return mapped;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Map data keys from Drizzle JS property names to SQL column names for the
|
|
891
|
+
* raw-SQL transaction insert path. The transaction context builds INSERT
|
|
892
|
+
* statements from Object.keys(data) used directly as column identifiers, so a
|
|
893
|
+
* table whose Drizzle property names differ from its SQL column names
|
|
894
|
+
* (camelCase core tables like nextly_versions) needs its keys translated
|
|
895
|
+
* first. For tables whose property names already equal their column names
|
|
896
|
+
* (the dynamic dc_/single_/comp_ tables) every lookup is identity, so
|
|
897
|
+
* existing callers are unaffected.
|
|
898
|
+
*/
|
|
899
|
+
mapKeysToSqlColumns(tableObj, data) {
|
|
900
|
+
if (!tableObj || typeof tableObj !== "object") return data;
|
|
901
|
+
const jsToSql = /* @__PURE__ */ new Map();
|
|
902
|
+
for (const [jsName, colDef] of Object.entries(tableObj)) if (colDef && typeof colDef === "object" && "name" in colDef && typeof colDef.name === "string") jsToSql.set(jsName, colDef.name);
|
|
903
|
+
if (jsToSql.size === 0) return data;
|
|
904
|
+
const out = {};
|
|
905
|
+
for (const [key, value] of Object.entries(data)) out[jsToSql.get(key) ?? key] = value;
|
|
906
|
+
return out;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Map a list of column identifiers (Drizzle property names) to their SQL
|
|
910
|
+
* column names, for the raw-SQL transaction insert paths that build a
|
|
911
|
+
* RETURNING clause from `options.returning`. Same identity behavior as
|
|
912
|
+
* `mapKeysToSqlColumns`: names that are already SQL columns (the dynamic
|
|
913
|
+
* dc_/single_/comp_ tables) pass through unchanged.
|
|
914
|
+
*/
|
|
915
|
+
mapColumnNamesToSql(tableObj, names) {
|
|
916
|
+
if (!tableObj || typeof tableObj !== "object") return names;
|
|
917
|
+
const jsToSql = /* @__PURE__ */ new Map();
|
|
918
|
+
for (const [jsName, colDef] of Object.entries(tableObj)) if (colDef && typeof colDef === "object" && "name" in colDef && typeof colDef.name === "string") jsToSql.set(jsName, colDef.name);
|
|
919
|
+
if (jsToSql.size === 0) return names;
|
|
920
|
+
return names.map((n) => jsToSql.get(n) ?? n);
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Remap a raw-SQL result row's KEYS from SQL column names to Drizzle property
|
|
924
|
+
* names, so the raw-SQL transaction insert paths return the same key casing
|
|
925
|
+
* as the non-transactional (Drizzle) insert. Keys only - values are left
|
|
926
|
+
* untouched, so this does not change how JSON/date columns are decoded. For
|
|
927
|
+
* tables whose property names already equal their SQL columns (the dynamic
|
|
928
|
+
* dc_/single_/comp_ tables) every lookup is identity, so existing callers see
|
|
929
|
+
* no change.
|
|
930
|
+
*/
|
|
931
|
+
mapRowKeysToJs(tableObj, row) {
|
|
932
|
+
if (!tableObj || typeof tableObj !== "object" || !row || typeof row !== "object") return row;
|
|
933
|
+
const sqlToJs = /* @__PURE__ */ new Map();
|
|
934
|
+
for (const [jsName, colDef] of Object.entries(tableObj)) if (colDef && typeof colDef === "object" && "name" in colDef && typeof colDef.name === "string") sqlToJs.set(colDef.name, jsName);
|
|
935
|
+
if (sqlToJs.size === 0) return row;
|
|
936
|
+
const out = {};
|
|
937
|
+
for (const [key, value] of Object.entries(row)) out[sqlToJs.get(key) ?? key] = value;
|
|
938
|
+
return out;
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Decode a raw-SQL result row's DATE column values the way a Drizzle query
|
|
942
|
+
* would, so a write answers with the same representation a read of the same
|
|
943
|
+
* column gives.
|
|
944
|
+
*
|
|
945
|
+
* A Drizzle query path runs every value through its column definition; a
|
|
946
|
+
* raw-SQL path does not, so a dialect that stores a timestamp as a number
|
|
947
|
+
* answers a write with that number while every read answers a `Date`. Only
|
|
948
|
+
* date columns are touched, and a value that is already a `Date` is left
|
|
949
|
+
* alone, so a driver that decodes on its own is unaffected.
|
|
950
|
+
*
|
|
951
|
+
* Runs after {@link mapRowKeysToJs}: keys are Drizzle property names by then,
|
|
952
|
+
* which is what the table definition is keyed by.
|
|
953
|
+
*/
|
|
954
|
+
mapDateValuesFromDriver(tableObj, row) {
|
|
955
|
+
if (!tableObj || typeof tableObj !== "object" || !row || typeof row !== "object") return row;
|
|
956
|
+
let decoded;
|
|
957
|
+
for (const [jsName, colDef] of Object.entries(tableObj)) {
|
|
958
|
+
if (!isDateColumn(colDef)) continue;
|
|
959
|
+
const value = row[jsName];
|
|
960
|
+
if (value == null || value instanceof Date) continue;
|
|
961
|
+
decoded ??= { ...row };
|
|
962
|
+
decoded[jsName] = colDef.mapFromDriverValue(value);
|
|
963
|
+
}
|
|
964
|
+
return decoded ?? row;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Encode a row's DATE values the way a Drizzle query would before binding
|
|
968
|
+
* them, so what lands in the column does not depend on the server's timezone.
|
|
969
|
+
*
|
|
970
|
+
* A column declared without a time zone stores a wall clock and records
|
|
971
|
+
* nothing about which zone it belongs to, so both ends have to agree. Drizzle
|
|
972
|
+
* writes UTC and reads UTC. A driver handed a `Date` writes the LOCAL wall
|
|
973
|
+
* clock instead, and the same row then reads back shifted by the offset --
|
|
974
|
+
* on a UTC server the two agree and nothing is visibly wrong, which is why
|
|
975
|
+
* this survives CI.
|
|
976
|
+
*
|
|
977
|
+
* Only date columns are touched. A value that is not a `Date` is left as it
|
|
978
|
+
* is, so a caller that already encoded one is not encoded twice.
|
|
979
|
+
*
|
|
980
|
+
* Keys may be spelled either way at this point, so both the Drizzle property
|
|
981
|
+
* name and the SQL column name resolve.
|
|
982
|
+
*/
|
|
983
|
+
mapDateValuesToDriver(tableObj, data) {
|
|
984
|
+
if (!tableObj || typeof tableObj !== "object" || !data) return data;
|
|
985
|
+
const dateColumns = /* @__PURE__ */ new Map();
|
|
986
|
+
for (const [jsName, colDef] of Object.entries(tableObj)) {
|
|
987
|
+
if (!isDateColumn(colDef)) continue;
|
|
988
|
+
dateColumns.set(jsName, colDef);
|
|
989
|
+
const sqlName = colDef.name;
|
|
990
|
+
if (typeof sqlName === "string") dateColumns.set(sqlName, colDef);
|
|
991
|
+
}
|
|
992
|
+
if (dateColumns.size === 0) return data;
|
|
993
|
+
let encoded;
|
|
994
|
+
for (const [key, value] of Object.entries(data)) {
|
|
995
|
+
if (!(value instanceof Date)) continue;
|
|
996
|
+
const column = dateColumns.get(key);
|
|
997
|
+
if (!column) continue;
|
|
998
|
+
encoded ??= { ...data };
|
|
999
|
+
encoded[key] = column.mapToDriverValue(value);
|
|
1000
|
+
}
|
|
1001
|
+
return encoded ?? data;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Bring a row about to be written through a raw-SQL statement into the shape
|
|
1005
|
+
* a Drizzle query would have bound: SQL column names for keys, encoded values
|
|
1006
|
+
* for date columns.
|
|
1007
|
+
*
|
|
1008
|
+
* The mirror of {@link mapRowFromRawSql}. The raw-SQL write paths exist
|
|
1009
|
+
* because a dialect needs SQL a Drizzle query cannot express, not because
|
|
1010
|
+
* their callers want different values in the table.
|
|
1011
|
+
*/
|
|
1012
|
+
mapRowToRawSql(tableObj, data) {
|
|
1013
|
+
return this.mapDateValuesToDriver(tableObj, this.mapKeysToSqlColumns(tableObj, data));
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Bring a raw-SQL result row into the shape a Drizzle query would have
|
|
1017
|
+
* produced: Drizzle property names for keys, decoded values for date columns.
|
|
1018
|
+
*
|
|
1019
|
+
* The raw-SQL write paths exist because a dialect needs SQL a Drizzle query
|
|
1020
|
+
* cannot express, not because their callers want a different row shape, so
|
|
1021
|
+
* every one of them ends here.
|
|
1022
|
+
*/
|
|
1023
|
+
mapRowFromRawSql(tableObj, row, aliases = []) {
|
|
1024
|
+
const mapped = this.mapDateValuesFromDriver(tableObj, this.mapRowKeysToJs(tableObj, row));
|
|
1025
|
+
return this.applyWallClockAliases(mapped, aliases);
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* The date columns a statement is about to return, and the alias each one's
|
|
1029
|
+
* wall clock is spelled out under.
|
|
1030
|
+
*
|
|
1031
|
+
* A driver turns a timestamp into a `Date` using ITS zone before any of this
|
|
1032
|
+
* code runs, and that conversion is lossy: a wall clock inside a
|
|
1033
|
+
* daylight-saving gap is a local time that does not exist, so the driver
|
|
1034
|
+
* normalizes it and the original is gone. Asking the database for the wall
|
|
1035
|
+
* clock as text alongside the row is the only way to read it exactly.
|
|
1036
|
+
*
|
|
1037
|
+
* `"*"` covers every date column; a projection covers only the ones it asked
|
|
1038
|
+
* for, so a caller still gets back what it requested and nothing more.
|
|
1039
|
+
*/
|
|
1040
|
+
dateWallClockAliases(tableObj, returning) {
|
|
1041
|
+
if (!tableObj || typeof tableObj !== "object" || returning === void 0) return [];
|
|
1042
|
+
const requested = returning === "*" ? void 0 : new Set(returning.map(String));
|
|
1043
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1044
|
+
for (const [jsName, colDef] of Object.entries(tableObj)) {
|
|
1045
|
+
taken.add(jsName);
|
|
1046
|
+
const columnName = colDef.name;
|
|
1047
|
+
if (typeof columnName === "string") taken.add(columnName);
|
|
1048
|
+
}
|
|
1049
|
+
let next = 0;
|
|
1050
|
+
const freeAlias = () => {
|
|
1051
|
+
let candidate = `${WALL_CLOCK_ALIAS_STEM}${next++}`;
|
|
1052
|
+
while (taken.has(candidate)) candidate = `${WALL_CLOCK_ALIAS_STEM}${next++}`;
|
|
1053
|
+
taken.add(candidate);
|
|
1054
|
+
return candidate;
|
|
1055
|
+
};
|
|
1056
|
+
const aliases = [];
|
|
1057
|
+
for (const [jsName, colDef] of Object.entries(tableObj)) {
|
|
1058
|
+
if (!isDateColumn(colDef)) continue;
|
|
1059
|
+
if (carriesTimeZone(colDef)) continue;
|
|
1060
|
+
const sqlName = colDef.name;
|
|
1061
|
+
if (typeof sqlName !== "string") continue;
|
|
1062
|
+
if (requested && !requested.has(sqlName) && !requested.has(jsName)) continue;
|
|
1063
|
+
aliases.push({
|
|
1064
|
+
sqlName,
|
|
1065
|
+
alias: freeAlias(),
|
|
1066
|
+
jsName
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
return aliases;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Replace each date in a row with the wall clock the database spelled out,
|
|
1073
|
+
* read as UTC, and drop the aliases that carried it.
|
|
1074
|
+
*
|
|
1075
|
+
* The statement writes a UTC wall clock, so reading one back as UTC is what
|
|
1076
|
+
* makes a write and a later read agree. Whatever the column actually stored
|
|
1077
|
+
* is what arrives -- a column keeping only whole seconds reports whole
|
|
1078
|
+
* seconds -- because this is the database's own text, not a value
|
|
1079
|
+
* reconstructed from the one that was bound.
|
|
1080
|
+
*/
|
|
1081
|
+
applyWallClockAliases(row, aliases) {
|
|
1082
|
+
if (!row || typeof row !== "object" || aliases.length === 0) return row;
|
|
1083
|
+
const record = row;
|
|
1084
|
+
let resolved;
|
|
1085
|
+
for (const { alias, jsName } of aliases) {
|
|
1086
|
+
if (!(alias in record)) continue;
|
|
1087
|
+
resolved ??= { ...record };
|
|
1088
|
+
const text = resolved[alias];
|
|
1089
|
+
delete resolved[alias];
|
|
1090
|
+
if (typeof text !== "string") continue;
|
|
1091
|
+
const parsed = parseWallClockAsUtc(text);
|
|
1092
|
+
if (parsed !== void 0) resolved[jsName] = parsed;
|
|
1093
|
+
}
|
|
1094
|
+
return resolved ?? row;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Build a Drizzle column projection object (`{ propertyName: column }`) from a
|
|
1098
|
+
* requested column list, used by `select` (columns) and `insert` (returning).
|
|
1099
|
+
* A requested name resolves against either the Drizzle property name
|
|
1100
|
+
* (camelCase) or the SQL column name (snake_case); the projection is keyed by
|
|
1101
|
+
* the property name so the row shape matches a full select. Returns undefined
|
|
1102
|
+
* for `"*"` or when nothing resolves, so callers fall back to all columns.
|
|
1103
|
+
*/
|
|
1104
|
+
buildColumnProjection(tableObj, names) {
|
|
1105
|
+
if (names == null || names === "*" || !tableObj || typeof tableObj !== "object") return;
|
|
1106
|
+
const { projection } = this.resolveColumns(tableObj, names);
|
|
1107
|
+
return Object.keys(projection).length ? projection : void 0;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* The requested column names split into what this table has and what it does not.
|
|
1111
|
+
*
|
|
1112
|
+
* One resolution, reported both ways, because two callers need different halves
|
|
1113
|
+
* of the same answer: a projection drops what it cannot find, while a distinct
|
|
1114
|
+
* count has to REFUSE it. Resolving twice would let the two disagree about what
|
|
1115
|
+
* "found" means — the SQL-name alias below is exactly the kind of detail a
|
|
1116
|
+
* second implementation gets wrong — and the count would then reject a column
|
|
1117
|
+
* the projection happily uses.
|
|
1118
|
+
*
|
|
1119
|
+
* Both spellings resolve: a caller may name the Drizzle property or the SQL
|
|
1120
|
+
* column, and either maps to the same projection key.
|
|
1121
|
+
*/
|
|
1122
|
+
resolveColumns(tableObj, names) {
|
|
1123
|
+
const cols = (0, drizzle_orm.getColumns)(tableObj);
|
|
1124
|
+
const byAnyName = {};
|
|
1125
|
+
for (const [jsName, col] of Object.entries(cols)) {
|
|
1126
|
+
byAnyName[jsName] = {
|
|
1127
|
+
jsName,
|
|
1128
|
+
col
|
|
1129
|
+
};
|
|
1130
|
+
const sqlName = col?.name;
|
|
1131
|
+
if (typeof sqlName === "string") byAnyName[sqlName] = {
|
|
1132
|
+
jsName,
|
|
1133
|
+
col
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
const projection = {};
|
|
1137
|
+
const unresolved = [];
|
|
1138
|
+
for (const name of names) {
|
|
1139
|
+
const hit = byAnyName[name];
|
|
1140
|
+
if (hit) projection[hit.jsName] = hit.col;
|
|
1141
|
+
else unresolved.push(name);
|
|
1142
|
+
}
|
|
1143
|
+
return {
|
|
1144
|
+
projection,
|
|
1145
|
+
unresolved
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Check if the adapter is currently connected.
|
|
1150
|
+
*
|
|
1151
|
+
* @remarks
|
|
1152
|
+
* Default implementation returns false. Subclasses should override
|
|
1153
|
+
* to provide accurate connection status.
|
|
1154
|
+
*
|
|
1155
|
+
* @returns True if connected, false otherwise
|
|
1156
|
+
*/
|
|
1157
|
+
isConnected() {
|
|
1158
|
+
return false;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Get connection pool statistics.
|
|
1162
|
+
*
|
|
1163
|
+
* @remarks
|
|
1164
|
+
* Returns null by default. Subclasses with connection pooling
|
|
1165
|
+
* should override to provide pool statistics.
|
|
1166
|
+
*
|
|
1167
|
+
* @returns Pool statistics or null if not applicable
|
|
1168
|
+
*/
|
|
1169
|
+
getPoolStats() {
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Default query timeout in milliseconds.
|
|
1174
|
+
*
|
|
1175
|
+
* @remarks
|
|
1176
|
+
* This value is used by executeWithTimeout() when no explicit timeout
|
|
1177
|
+
* is provided. Subclasses should set this from their config.
|
|
1178
|
+
*
|
|
1179
|
+
* @default 15000 (15 seconds)
|
|
1180
|
+
*
|
|
1181
|
+
* @protected
|
|
1182
|
+
*/
|
|
1183
|
+
defaultQueryTimeoutMs = 15e3;
|
|
1184
|
+
/**
|
|
1185
|
+
* Execute an async operation with a timeout.
|
|
1186
|
+
*
|
|
1187
|
+
* @remarks
|
|
1188
|
+
* Wraps an async operation with a timeout that aborts if the operation
|
|
1189
|
+
* exceeds the specified duration. Uses Promise.race for clean timeout
|
|
1190
|
+
* handling without memory leaks.
|
|
1191
|
+
*
|
|
1192
|
+
* When the timeout is reached, a DatabaseError with kind 'timeout' is thrown.
|
|
1193
|
+
* Note that this does NOT cancel the underlying database query - it only
|
|
1194
|
+
* prevents the calling code from waiting indefinitely. For true query
|
|
1195
|
+
* cancellation, use database-level statement timeouts (PostgreSQL) or
|
|
1196
|
+
* similar mechanisms.
|
|
1197
|
+
*
|
|
1198
|
+
* @param operation - Async operation to execute
|
|
1199
|
+
* @param timeoutMs - Timeout in milliseconds (defaults to defaultQueryTimeoutMs)
|
|
1200
|
+
* @returns Result of the operation
|
|
1201
|
+
*
|
|
1202
|
+
* @throws {DatabaseError} With kind 'timeout' if operation exceeds timeout
|
|
1203
|
+
*
|
|
1204
|
+
* @example
|
|
1205
|
+
* ```typescript
|
|
1206
|
+
* // Use default timeout
|
|
1207
|
+
* const result = await adapter.executeWithTimeout(
|
|
1208
|
+
* () => adapter.select('users', { limit: 1000 })
|
|
1209
|
+
* );
|
|
1210
|
+
*
|
|
1211
|
+
* // Use custom timeout for specific operation
|
|
1212
|
+
* const result = await adapter.executeWithTimeout(
|
|
1213
|
+
* () => adapter.select('large_table'),
|
|
1214
|
+
* 60000 // 60 seconds for large queries
|
|
1215
|
+
* );
|
|
1216
|
+
* ```
|
|
1217
|
+
*
|
|
1218
|
+
* @public
|
|
1219
|
+
*/
|
|
1220
|
+
async executeWithTimeout(operation, timeoutMs) {
|
|
1221
|
+
const timeout = timeoutMs ?? this.defaultQueryTimeoutMs;
|
|
1222
|
+
if (timeout <= 0) return operation();
|
|
1223
|
+
let timeoutId;
|
|
1224
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1225
|
+
timeoutId = setTimeout(() => {
|
|
1226
|
+
reject(this.createDatabaseError("timeout", `Query execution timed out after ${timeout}ms`));
|
|
1227
|
+
}, timeout);
|
|
1228
|
+
});
|
|
1229
|
+
try {
|
|
1230
|
+
return await Promise.race([operation(), timeoutPromise]);
|
|
1231
|
+
} finally {
|
|
1232
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Set the default query timeout.
|
|
1237
|
+
*
|
|
1238
|
+
* @remarks
|
|
1239
|
+
* This method allows runtime configuration of the default timeout.
|
|
1240
|
+
* Subclasses should call this in their constructor or connect() method
|
|
1241
|
+
* based on their configuration.
|
|
1242
|
+
*
|
|
1243
|
+
* @param timeoutMs - Timeout in milliseconds (0 to disable)
|
|
1244
|
+
*
|
|
1245
|
+
* @protected
|
|
1246
|
+
*/
|
|
1247
|
+
setDefaultQueryTimeout(timeoutMs) {
|
|
1248
|
+
this.defaultQueryTimeoutMs = timeoutMs;
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Get the current default query timeout.
|
|
1252
|
+
*
|
|
1253
|
+
* @returns Current default timeout in milliseconds
|
|
1254
|
+
*
|
|
1255
|
+
* @public
|
|
1256
|
+
*/
|
|
1257
|
+
getDefaultQueryTimeout() {
|
|
1258
|
+
return this.defaultQueryTimeoutMs;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Select multiple records from a table.
|
|
1262
|
+
*
|
|
1263
|
+
* @remarks
|
|
1264
|
+
* Default implementation builds a SELECT query and executes it.
|
|
1265
|
+
* Subclasses can override for optimization or dialect-specific features.
|
|
1266
|
+
*
|
|
1267
|
+
* @param table - Table name
|
|
1268
|
+
* @param options - Select options (filtering, sorting, pagination)
|
|
1269
|
+
* @returns Array of matching records
|
|
1270
|
+
*
|
|
1271
|
+
* @throws {DatabaseError} If query fails
|
|
1272
|
+
*
|
|
1273
|
+
* @example
|
|
1274
|
+
* ```typescript
|
|
1275
|
+
* const users = await adapter.select('users', {
|
|
1276
|
+
* where: { and: [{ column: 'role', op: '=', value: 'admin' }] },
|
|
1277
|
+
* orderBy: [{ column: 'created_at', direction: 'desc' }],
|
|
1278
|
+
* limit: 10
|
|
1279
|
+
* });
|
|
1280
|
+
* ```
|
|
1281
|
+
*/
|
|
1282
|
+
async select(table, options, executor) {
|
|
1283
|
+
const tableObj = this.getTableObject(table);
|
|
1284
|
+
if (tableObj) try {
|
|
1285
|
+
const db = executor ?? this.getDrizzle();
|
|
1286
|
+
const projection = options?.columns?.length ? this.buildColumnProjection(tableObj, options.columns) : void 0;
|
|
1287
|
+
let query = projection ? db.select(projection).from(tableObj) : db.select().from(tableObj);
|
|
1288
|
+
if (options?.where) {
|
|
1289
|
+
const whereCondition = buildDrizzleWhere(tableObj, options.where);
|
|
1290
|
+
if (whereCondition) query = query.where(whereCondition);
|
|
1291
|
+
}
|
|
1292
|
+
if (options?.orderBy?.length) {
|
|
1293
|
+
const orderClauses = buildDrizzleOrderBy((0, drizzle_orm.getColumns)(tableObj), options.orderBy);
|
|
1294
|
+
if (orderClauses.length) query = query.orderBy(...orderClauses);
|
|
1295
|
+
}
|
|
1296
|
+
if (options?.limit !== void 0) query = query.limit(options.limit);
|
|
1297
|
+
if (options?.offset !== void 0) query = query.offset(options.offset);
|
|
1298
|
+
if (options?.forUpdate && !executor) throw this.createDatabaseError("query", "forUpdate requires a transaction executor: a lock request on the pooled connection takes no durable lock and cannot prevent a concurrent write.", void 0);
|
|
1299
|
+
if (options?.forUpdate && this.dialect !== "sqlite") query = query.for("update");
|
|
1300
|
+
return await query;
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
throw this.handleQueryError(error, "select", table);
|
|
1303
|
+
}
|
|
1304
|
+
throw this.createDatabaseError("query", `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`, void 0);
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Select a single record from a table.
|
|
1308
|
+
*
|
|
1309
|
+
* @remarks
|
|
1310
|
+
* Default implementation uses `select()` with limit 1 and returns first result.
|
|
1311
|
+
* Returns null if no matching record is found.
|
|
1312
|
+
*
|
|
1313
|
+
* @param table - Table name
|
|
1314
|
+
* @param options - Select options
|
|
1315
|
+
* @returns First matching record or null
|
|
1316
|
+
*
|
|
1317
|
+
* @throws {DatabaseError} If query fails
|
|
1318
|
+
*
|
|
1319
|
+
* @example
|
|
1320
|
+
* ```typescript
|
|
1321
|
+
* const user = await adapter.selectOne('users', {
|
|
1322
|
+
* where: { and: [{ column: 'email', op: '=', value: 'user@example.com' }] }
|
|
1323
|
+
* });
|
|
1324
|
+
* ```
|
|
1325
|
+
*/
|
|
1326
|
+
/** The registered table object, or a refusal naming what is missing. */
|
|
1327
|
+
requireTableObject(table) {
|
|
1328
|
+
const tableObj = this.getTableObject(table);
|
|
1329
|
+
if (tableObj) return tableObj;
|
|
1330
|
+
throw this.createDatabaseError("query", `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`, void 0);
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* The columns a distinct count groups by, or `undefined` for a row count.
|
|
1334
|
+
*
|
|
1335
|
+
* 🔴 EVERY named column must resolve, not merely one of them. A `distinctOn`
|
|
1336
|
+
* naming no column this table has would fall through to a row count, and one
|
|
1337
|
+
* naming a valid column beside a misspelled or since-removed column would
|
|
1338
|
+
* count distinct over a NARROWER key than the caller asked for. Both answer a
|
|
1339
|
+
* different question than the one asked, silently; the second is the worse of
|
|
1340
|
+
* the two, because a subset key groups more rows together and so UNDERCOUNTS,
|
|
1341
|
+
* which reads as a plausible number rather than as a fault. Refused here,
|
|
1342
|
+
* where the mistake was made, rather than surfacing as a wrong figure.
|
|
1343
|
+
*/
|
|
1344
|
+
countProjection(tableObj, table, options) {
|
|
1345
|
+
const distinctOn = options?.distinctOn;
|
|
1346
|
+
if (!distinctOn?.length) return void 0;
|
|
1347
|
+
const { projection, unresolved } = this.resolveColumns(tableObj, distinctOn);
|
|
1348
|
+
if (unresolved.length > 0) throw this.createDatabaseError("query", `distinctOn names ${unresolved.length === 1 ? "a column" : "columns"} that do not exist on "${table}": ${unresolved.join(", ")}`, void 0);
|
|
1349
|
+
return projection;
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* `COUNT(*)` over a `SELECT DISTINCT` subquery.
|
|
1353
|
+
*
|
|
1354
|
+
* Its own function because it is a genuinely different query from the plain
|
|
1355
|
+
* count rather than a variation on one, and because building both inline made
|
|
1356
|
+
* the public method a chain of decisions the complexity gate objected to
|
|
1357
|
+
* before a reader would have.
|
|
1358
|
+
*/
|
|
1359
|
+
async countDistinctRows(db, tableObj, projection, where) {
|
|
1360
|
+
const inner = db.selectDistinct(projection).from(tableObj).$dynamic();
|
|
1361
|
+
const distinctRows = where ? inner.where(where) : inner;
|
|
1362
|
+
const [row] = await db.select({ value: (0, drizzle_orm.count)() }).from(distinctRows.as("nextly_distinct"));
|
|
1363
|
+
return Number(row?.value ?? 0);
|
|
1364
|
+
}
|
|
1365
|
+
/** `COUNT(*)` over the table itself. */
|
|
1366
|
+
async countAllRows(db, tableObj, where) {
|
|
1367
|
+
const base = db.select({ value: (0, drizzle_orm.count)() }).from(tableObj).$dynamic();
|
|
1368
|
+
const [row] = await (where ? base.where(where) : base);
|
|
1369
|
+
return Number(row?.value ?? 0);
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* How many rows match, or how many DISTINCT combinations of some columns do.
|
|
1373
|
+
*
|
|
1374
|
+
* The data layer had no count at all: collection totals go through
|
|
1375
|
+
* `countEntries`, an access-controlled path built for collection tables, and
|
|
1376
|
+
* nothing could count a system table. A caller wanting one selected the rows
|
|
1377
|
+
* and measured the array, which transfers every row to learn a number and
|
|
1378
|
+
* cannot be bounded without making the number wrong.
|
|
1379
|
+
*
|
|
1380
|
+
* 🔴 `distinctOn` compiles to `COUNT(*)` over a `SELECT DISTINCT` SUBQUERY,
|
|
1381
|
+
* never to `COUNT(DISTINCT a, b)`. The inline form is not portable and fails
|
|
1382
|
+
* in the direction that is hardest to notice -- MySQL accepts it, PostgreSQL
|
|
1383
|
+
* needs a row constructor, and SQLite rejects it outright with "wrong number
|
|
1384
|
+
* of arguments to function count()" -- so a query written against one engine
|
|
1385
|
+
* is a syntax error on another. The subquery is the single form all three
|
|
1386
|
+
* accept, and building it here rather than at each call site is what keeps
|
|
1387
|
+
* the answer identical per dialect.
|
|
1388
|
+
*/
|
|
1389
|
+
async count(table, options, executor) {
|
|
1390
|
+
const tableObj = this.requireTableObject(table);
|
|
1391
|
+
try {
|
|
1392
|
+
const db = executor ?? this.getDrizzle();
|
|
1393
|
+
const where = options?.where ? buildDrizzleWhere(tableObj, options.where) : void 0;
|
|
1394
|
+
const projection = this.countProjection(tableObj, table, options);
|
|
1395
|
+
return projection ? await this.countDistinctRows(db, tableObj, projection, where) : await this.countAllRows(db, tableObj, where);
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
throw this.handleQueryError(error, "count", table);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
async selectOne(table, options, executor) {
|
|
1401
|
+
const results = await this.select(table, {
|
|
1402
|
+
...options,
|
|
1403
|
+
limit: 1
|
|
1404
|
+
}, executor);
|
|
1405
|
+
return results.length > 0 ? results[0] : null;
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Insert a single record into a table.
|
|
1409
|
+
*
|
|
1410
|
+
* @remarks
|
|
1411
|
+
* Default implementation handles databases with and without RETURNING support.
|
|
1412
|
+
* For databases without RETURNING (MySQL), performs INSERT followed by SELECT.
|
|
1413
|
+
*
|
|
1414
|
+
* @param table - Table name
|
|
1415
|
+
* @param data - Record data to insert
|
|
1416
|
+
* @param options - Insert options
|
|
1417
|
+
* @returns Inserted record (with RETURNING columns if specified)
|
|
1418
|
+
*
|
|
1419
|
+
* @throws {DatabaseError} If insert fails
|
|
1420
|
+
*
|
|
1421
|
+
* @example
|
|
1422
|
+
* ```typescript
|
|
1423
|
+
* const user = await adapter.insert('users', {
|
|
1424
|
+
* email: 'user@example.com',
|
|
1425
|
+
* name: 'John Doe'
|
|
1426
|
+
* }, { returning: ['id', 'email', 'created_at'] });
|
|
1427
|
+
* ```
|
|
1428
|
+
*/
|
|
1429
|
+
async insert(table, data, options) {
|
|
1430
|
+
const tableObj = this.getTableObject(table);
|
|
1431
|
+
if (tableObj) try {
|
|
1432
|
+
const mappedData = this.mapDataToColumnNames(tableObj, data);
|
|
1433
|
+
const db = this.getDrizzle();
|
|
1434
|
+
const caps = this.getCapabilities();
|
|
1435
|
+
const returning = options?.returning;
|
|
1436
|
+
const wantsReturning = returning != null && !(Array.isArray(returning) && returning.length === 0);
|
|
1437
|
+
if (caps.supportsReturning && wantsReturning) {
|
|
1438
|
+
const projection = this.buildColumnProjection(tableObj, returning);
|
|
1439
|
+
const insertQuery = db.insert(tableObj).values(mappedData);
|
|
1440
|
+
const result = await (projection ? insertQuery.returning(projection) : insertQuery.returning());
|
|
1441
|
+
return Array.isArray(result) ? result[0] : result;
|
|
1442
|
+
}
|
|
1443
|
+
const result = await db.insert(tableObj).values(mappedData);
|
|
1444
|
+
if (!caps.supportsReturning && wantsReturning && data.id !== void 0) return await this.selectOne(table, {
|
|
1445
|
+
columns: returning === "*" ? void 0 : returning,
|
|
1446
|
+
where: { and: [{
|
|
1447
|
+
column: "id",
|
|
1448
|
+
op: "=",
|
|
1449
|
+
value: data.id
|
|
1450
|
+
}] }
|
|
1451
|
+
});
|
|
1452
|
+
return Array.isArray(result) ? result[0] : result;
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
throw this.handleQueryError(error, "insert", table);
|
|
1455
|
+
}
|
|
1456
|
+
throw this.createDatabaseError("query", `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`, void 0);
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Insert multiple records into a table.
|
|
1460
|
+
*
|
|
1461
|
+
* @remarks
|
|
1462
|
+
* Default implementation performs individual inserts in sequence.
|
|
1463
|
+
* Subclasses can override for bulk insert optimization (e.g., COPY in PostgreSQL).
|
|
1464
|
+
*
|
|
1465
|
+
* @param table - Table name
|
|
1466
|
+
* @param data - Array of records to insert
|
|
1467
|
+
* @param options - Insert options
|
|
1468
|
+
* @returns Inserted records (with RETURNING columns if specified)
|
|
1469
|
+
*
|
|
1470
|
+
* @throws {DatabaseError} If insert fails
|
|
1471
|
+
*
|
|
1472
|
+
* @example
|
|
1473
|
+
* ```typescript
|
|
1474
|
+
* const users = await adapter.insertMany('users', [
|
|
1475
|
+
* { email: 'user1@example.com', name: 'User 1' },
|
|
1476
|
+
* { email: 'user2@example.com', name: 'User 2' }
|
|
1477
|
+
* ], { returning: ['id'] });
|
|
1478
|
+
* ```
|
|
1479
|
+
*/
|
|
1480
|
+
async insertMany(table, data, options) {
|
|
1481
|
+
if (data.length === 0) return [];
|
|
1482
|
+
const results = [];
|
|
1483
|
+
for (const record of data) {
|
|
1484
|
+
const result = await this.insert(table, record, options);
|
|
1485
|
+
results.push(result);
|
|
1486
|
+
}
|
|
1487
|
+
return results;
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Update records in a table.
|
|
1491
|
+
*
|
|
1492
|
+
* @remarks
|
|
1493
|
+
* Default implementation builds an UPDATE query with WHERE clause.
|
|
1494
|
+
* Returns updated records if RETURNING is supported and requested.
|
|
1495
|
+
*
|
|
1496
|
+
* @param table - Table name
|
|
1497
|
+
* @param data - Data to update
|
|
1498
|
+
* @param where - Conditions for records to update
|
|
1499
|
+
* @param options - Update options
|
|
1500
|
+
* @returns Updated records (with RETURNING columns if specified)
|
|
1501
|
+
*
|
|
1502
|
+
* @throws {DatabaseError} If update fails
|
|
1503
|
+
*
|
|
1504
|
+
* @example
|
|
1505
|
+
* ```typescript
|
|
1506
|
+
* const updated = await adapter.update('users',
|
|
1507
|
+
* { status: 'active' },
|
|
1508
|
+
* { and: [{ column: 'id', op: '=', value: userId }] },
|
|
1509
|
+
* { returning: ['id', 'status', 'updated_at'] }
|
|
1510
|
+
* );
|
|
1511
|
+
* ```
|
|
1512
|
+
*/
|
|
1513
|
+
async update(table, data, where, options, executor) {
|
|
1514
|
+
const tableObj = this.getTableObject(table);
|
|
1515
|
+
if (tableObj) try {
|
|
1516
|
+
const db = executor ?? this.getDrizzle();
|
|
1517
|
+
const caps = this.getCapabilities();
|
|
1518
|
+
const mappedData = this.mapDataToColumnNames(tableObj, data);
|
|
1519
|
+
let query = db.update(tableObj).set(mappedData);
|
|
1520
|
+
const whereCondition = buildDrizzleWhere(tableObj, where);
|
|
1521
|
+
if (whereCondition) query = query.where(whereCondition);
|
|
1522
|
+
if (caps.supportsReturning && options?.returning) return await query.returning();
|
|
1523
|
+
await query;
|
|
1524
|
+
if (!caps.supportsReturning && options?.returning) return await this.select(table, { where }, executor);
|
|
1525
|
+
return [];
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
throw this.handleQueryError(error, "update", table);
|
|
1528
|
+
}
|
|
1529
|
+
throw this.createDatabaseError("query", `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`, void 0);
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Update records in a table and report how many rows the statement affected.
|
|
1533
|
+
*
|
|
1534
|
+
* The count is the whole return value, mirroring `delete`. `update` cannot answer this: without
|
|
1535
|
+
* `returning` it discards the driver's count, and WITH `returning` on a dialect that lacks
|
|
1536
|
+
* RETURNING it re-SELECTs using the same WHERE — so a conditional update that just changed a
|
|
1537
|
+
* column named in that WHERE reads back zero rows and a write that landed reports as unmatched.
|
|
1538
|
+
* Reading the driver's own count has no second query to disagree with the first.
|
|
1539
|
+
*
|
|
1540
|
+
* 🔴 MySQL reports CHANGED rows, not matched rows: an UPDATE that matches a row but writes values
|
|
1541
|
+
* identical to what it holds counts zero. A caller using this as a compare-and-set must therefore
|
|
1542
|
+
* include a column the write always moves — a version bump, a timestamp with enough resolution —
|
|
1543
|
+
* so that matched implies changed. Postgres (`rowCount`) and SQLite (`changes`) count matched
|
|
1544
|
+
* rows and do not need the precaution, which is exactly why it cannot be dropped: the dialect
|
|
1545
|
+
* where the distinction exists is the one with no RETURNING to fall back on.
|
|
1546
|
+
*/
|
|
1547
|
+
async updateCount(table, data, where, executor) {
|
|
1548
|
+
const tableObj = this.getTableObject(table);
|
|
1549
|
+
if (tableObj) try {
|
|
1550
|
+
const db = executor ?? this.getDrizzle();
|
|
1551
|
+
const mappedData = this.mapDataToColumnNames(tableObj, data);
|
|
1552
|
+
const statement = db.update(tableObj).set(mappedData);
|
|
1553
|
+
const whereCondition = buildDrizzleWhere(tableObj, where);
|
|
1554
|
+
return affectedRowCount(await (whereCondition ? statement.where(whereCondition) : statement));
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
throw this.handleQueryError(error, "update", table);
|
|
1557
|
+
}
|
|
1558
|
+
throw this.createDatabaseError("query", `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`, void 0);
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Delete records from a table.
|
|
1562
|
+
*
|
|
1563
|
+
* @remarks
|
|
1564
|
+
* Default implementation builds a DELETE query with WHERE clause.
|
|
1565
|
+
* Returns the number of deleted records.
|
|
1566
|
+
*
|
|
1567
|
+
* @param table - Table name
|
|
1568
|
+
* @param where - Conditions for records to delete
|
|
1569
|
+
* @param options - Delete options
|
|
1570
|
+
* @returns Number of deleted records
|
|
1571
|
+
*
|
|
1572
|
+
* @throws {DatabaseError} If delete fails
|
|
1573
|
+
*
|
|
1574
|
+
* @example
|
|
1575
|
+
* ```typescript
|
|
1576
|
+
* const count = await adapter.delete('users', {
|
|
1577
|
+
* and: [{ column: 'status', op: '=', value: 'inactive' }]
|
|
1578
|
+
* });
|
|
1579
|
+
* console.log(`Deleted ${count} users`);
|
|
1580
|
+
* ```
|
|
1581
|
+
*/
|
|
1582
|
+
async delete(table, where, _options, executor) {
|
|
1583
|
+
const tableObj = this.getTableObject(table);
|
|
1584
|
+
if (tableObj) try {
|
|
1585
|
+
let query = (executor ?? this.getDrizzle()).delete(tableObj);
|
|
1586
|
+
const whereCondition = buildDrizzleWhere(tableObj, where);
|
|
1587
|
+
if (whereCondition) query = query.where(whereCondition);
|
|
1588
|
+
return affectedRowCount(await query);
|
|
1589
|
+
} catch (error) {
|
|
1590
|
+
throw this.handleQueryError(error, "delete", table);
|
|
1591
|
+
}
|
|
1592
|
+
throw this.createDatabaseError("query", `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`, void 0);
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Upsert (INSERT or UPDATE) a record.
|
|
1596
|
+
*
|
|
1597
|
+
* @remarks
|
|
1598
|
+
* Default implementation uses dialect-specific ON CONFLICT syntax.
|
|
1599
|
+
* PostgreSQL/SQLite: ON CONFLICT ... DO UPDATE
|
|
1600
|
+
* MySQL: the MySQL adapter overrides this, resolving the conflict by the
|
|
1601
|
+
* conflict columns (see its `upsert`)
|
|
1602
|
+
*
|
|
1603
|
+
* @param table - Table name
|
|
1604
|
+
* @param data - Record data
|
|
1605
|
+
* @param options - Upsert options (must specify conflict columns)
|
|
1606
|
+
* @returns Upserted record (with RETURNING columns if specified)
|
|
1607
|
+
*
|
|
1608
|
+
* @throws {DatabaseError} If upsert fails
|
|
1609
|
+
*
|
|
1610
|
+
* @example
|
|
1611
|
+
* ```typescript
|
|
1612
|
+
* const user = await adapter.upsert('users', {
|
|
1613
|
+
* email: 'user@example.com',
|
|
1614
|
+
* name: 'Updated Name'
|
|
1615
|
+
* }, {
|
|
1616
|
+
* conflictColumns: ['email'],
|
|
1617
|
+
* updateColumns: ['name'],
|
|
1618
|
+
* returning: ['id', 'email', 'name']
|
|
1619
|
+
* });
|
|
1620
|
+
* ```
|
|
1621
|
+
*/
|
|
1622
|
+
async upsert(table, data, options, executor) {
|
|
1623
|
+
const tableObj = this.getTableObject(table);
|
|
1624
|
+
if (tableObj) try {
|
|
1625
|
+
const db = executor ?? this.getDrizzle();
|
|
1626
|
+
const caps = this.getCapabilities();
|
|
1627
|
+
const columns = (0, drizzle_orm.getColumns)(tableObj);
|
|
1628
|
+
const conflictTarget = options.conflictColumns.map((col) => columns[col]).filter(Boolean);
|
|
1629
|
+
const conflictSet = new Set(options.conflictColumns);
|
|
1630
|
+
const updateData = {};
|
|
1631
|
+
const updateColumns = options.updateColumns ?? Object.keys(data);
|
|
1632
|
+
for (const key of updateColumns) if (!conflictSet.has(key) && key in data) updateData[key] = data[key];
|
|
1633
|
+
const keepExisting = Object.keys(updateData).length === 0;
|
|
1634
|
+
this.assertUpsertTarget(table, data, options, keepExisting);
|
|
1635
|
+
let query;
|
|
1636
|
+
if (caps.supportsOnConflict) {
|
|
1637
|
+
const inserted = db.insert(tableObj).values(data);
|
|
1638
|
+
query = keepExisting ? inserted.onConflictDoNothing({ target: conflictTarget }) : inserted.onConflictDoUpdate({
|
|
1639
|
+
target: conflictTarget,
|
|
1640
|
+
set: updateData
|
|
1641
|
+
});
|
|
1642
|
+
} else query = db.insert(tableObj).values(data);
|
|
1643
|
+
if (caps.supportsReturning) {
|
|
1644
|
+
const result = await query.returning();
|
|
1645
|
+
const row = Array.isArray(result) ? result[0] : result;
|
|
1646
|
+
if (row !== void 0) return row;
|
|
1647
|
+
} else await query;
|
|
1648
|
+
const conflictValues = options.conflictColumns.map((column) => [column, data[column]]);
|
|
1649
|
+
if (conflictValues.length > 0 && conflictValues.every(([, value]) => value !== void 0)) return await this.selectOne(table, { where: { and: conflictValues.map(([column, value]) => ({
|
|
1650
|
+
column,
|
|
1651
|
+
op: "=",
|
|
1652
|
+
value
|
|
1653
|
+
})) } }, executor);
|
|
1654
|
+
return data;
|
|
1655
|
+
} catch (error) {
|
|
1656
|
+
throw this.handleQueryError(error, "upsert", table);
|
|
1657
|
+
}
|
|
1658
|
+
throw this.createDatabaseError("query", `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`, void 0);
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Run pending migrations.
|
|
1662
|
+
*
|
|
1663
|
+
* @remarks
|
|
1664
|
+
* Default implementation is a placeholder. Subclasses should implement
|
|
1665
|
+
* migration tracking and execution logic.
|
|
1666
|
+
*
|
|
1667
|
+
* @param migrations - Array of migrations to run
|
|
1668
|
+
* @returns Migration result with applied and pending migrations
|
|
1669
|
+
*
|
|
1670
|
+
* @throws {DatabaseError} If migration fails
|
|
1671
|
+
*/
|
|
1672
|
+
async migrate(_migrations) {
|
|
1673
|
+
throw this.createDatabaseError("query", "migrate() must be implemented by dialect-specific adapter. Use PostgresAdapter, MySqlAdapter, or SqliteAdapter.", void 0);
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Rollback the last migration.
|
|
1677
|
+
*
|
|
1678
|
+
* @remarks
|
|
1679
|
+
* Default implementation is a placeholder. Subclasses should implement
|
|
1680
|
+
* migration rollback logic.
|
|
1681
|
+
*
|
|
1682
|
+
* @returns Migration result after rollback
|
|
1683
|
+
*
|
|
1684
|
+
* @throws {DatabaseError} If rollback fails
|
|
1685
|
+
*/
|
|
1686
|
+
async rollback() {
|
|
1687
|
+
throw this.createDatabaseError("query", "rollback() must be implemented by dialect-specific adapter. Use PostgresAdapter, MySqlAdapter, or SqliteAdapter.", void 0);
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Get migration status.
|
|
1691
|
+
*
|
|
1692
|
+
* @remarks
|
|
1693
|
+
* Default implementation is a placeholder. Subclasses should implement
|
|
1694
|
+
* migration status checking.
|
|
1695
|
+
*
|
|
1696
|
+
* @returns Current migration status
|
|
1697
|
+
*
|
|
1698
|
+
* @throws {DatabaseError} If status check fails
|
|
1699
|
+
*/
|
|
1700
|
+
async getMigrationStatus() {
|
|
1701
|
+
try {
|
|
1702
|
+
const applied = (await this.executeQuery(`SELECT * FROM "__drizzle_migrations" ORDER BY created_at ASC`)).map((r) => ({
|
|
1703
|
+
id: String(r.id),
|
|
1704
|
+
name: r.hash,
|
|
1705
|
+
appliedAt: new Date(r.created_at),
|
|
1706
|
+
checksum: r.hash
|
|
1707
|
+
}));
|
|
1708
|
+
return {
|
|
1709
|
+
applied,
|
|
1710
|
+
pending: [],
|
|
1711
|
+
current: applied.length > 0 ? applied[applied.length - 1].id : null
|
|
1712
|
+
};
|
|
1713
|
+
} catch {
|
|
1714
|
+
return {
|
|
1715
|
+
applied: [],
|
|
1716
|
+
pending: [],
|
|
1717
|
+
current: null
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* Create a new table.
|
|
1723
|
+
*
|
|
1724
|
+
* @remarks
|
|
1725
|
+
* Default implementation is a placeholder. Subclasses should implement
|
|
1726
|
+
* table creation logic.
|
|
1727
|
+
*
|
|
1728
|
+
* @param definition - Table definition
|
|
1729
|
+
* @param options - Creation options
|
|
1730
|
+
*
|
|
1731
|
+
* @throws {DatabaseError} If table creation fails
|
|
1732
|
+
*/
|
|
1733
|
+
async createTable(definition, options) {
|
|
1734
|
+
const columnDefs = definition.columns.map((col) => {
|
|
1735
|
+
let colSql = `${this.escapeIdentifier(col.name)} ${col.type.toUpperCase()}`;
|
|
1736
|
+
if (col.primaryKey) colSql += " PRIMARY KEY";
|
|
1737
|
+
if (col.nullable === false) colSql += " NOT NULL";
|
|
1738
|
+
if (col.unique) colSql += " UNIQUE";
|
|
1739
|
+
if (col.default !== void 0) colSql += ` DEFAULT ${renderDefaultValue(col.default, col.name)}`;
|
|
1740
|
+
return colSql;
|
|
1741
|
+
});
|
|
1742
|
+
const query = `CREATE TABLE ${options?.ifNotExists !== false ? "IF NOT EXISTS " : ""}${this.escapeIdentifier(definition.name)} (\n ${columnDefs.join(",\n ")}\n)`;
|
|
1743
|
+
try {
|
|
1744
|
+
await this.executeQuery(query);
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
throw this.handleQueryError(error, "createTable", definition.name);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Drop a table.
|
|
1751
|
+
*
|
|
1752
|
+
* @remarks
|
|
1753
|
+
* Default implementation is a placeholder. Subclasses should implement
|
|
1754
|
+
* table dropping logic.
|
|
1755
|
+
*
|
|
1756
|
+
* @param tableName - Name of table to drop
|
|
1757
|
+
* @param options - Drop options
|
|
1758
|
+
*
|
|
1759
|
+
* @throws {DatabaseError} If table drop fails
|
|
1760
|
+
*/
|
|
1761
|
+
async dropTable(tableName, options) {
|
|
1762
|
+
const ifExists = options?.ifExists !== false ? "IF EXISTS " : "";
|
|
1763
|
+
const cascade = options?.cascade ? " CASCADE" : "";
|
|
1764
|
+
const query = `DROP TABLE ${ifExists}${this.escapeIdentifier(tableName)}${cascade}`;
|
|
1765
|
+
try {
|
|
1766
|
+
await this.executeQuery(query);
|
|
1767
|
+
} catch (error) {
|
|
1768
|
+
throw this.handleQueryError(error, "dropTable", tableName);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Alter an existing table.
|
|
1773
|
+
*
|
|
1774
|
+
* @remarks
|
|
1775
|
+
* Default implementation is a placeholder. Subclasses should implement
|
|
1776
|
+
* table alteration logic.
|
|
1777
|
+
*
|
|
1778
|
+
* @param tableName - Name of table to alter
|
|
1779
|
+
* @param operations - Alteration operations
|
|
1780
|
+
* @param options - Alter options
|
|
1781
|
+
*
|
|
1782
|
+
* @throws {DatabaseError} If table alteration fails
|
|
1783
|
+
*/
|
|
1784
|
+
async alterTable(tableName, operations, _options) {
|
|
1785
|
+
const quotedTable = this.escapeIdentifier(tableName);
|
|
1786
|
+
for (const op of operations) {
|
|
1787
|
+
let query;
|
|
1788
|
+
switch (op.kind) {
|
|
1789
|
+
case "add_column": {
|
|
1790
|
+
let colDef = `${this.escapeIdentifier(op.column.name)} ${op.column.type.toUpperCase()}`;
|
|
1791
|
+
if (op.column.nullable === false) colDef += " NOT NULL";
|
|
1792
|
+
if (op.column.unique) colDef += " UNIQUE";
|
|
1793
|
+
if (op.column.default !== void 0) {
|
|
1794
|
+
const defaultVal = typeof op.column.default === "object" && op.column.default !== null && "sql" in op.column.default ? op.column.default.sql : typeof op.column.default === "string" ? `'${op.column.default}'` : String(op.column.default);
|
|
1795
|
+
colDef += ` DEFAULT ${defaultVal}`;
|
|
1796
|
+
}
|
|
1797
|
+
query = `ALTER TABLE ${quotedTable} ADD COLUMN ${colDef}`;
|
|
1798
|
+
break;
|
|
1799
|
+
}
|
|
1800
|
+
case "drop_column": {
|
|
1801
|
+
const cascade = op.cascade ? " CASCADE" : "";
|
|
1802
|
+
query = `ALTER TABLE ${quotedTable} DROP COLUMN ${this.escapeIdentifier(op.columnName)}${cascade}`;
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1805
|
+
case "rename_column":
|
|
1806
|
+
query = `ALTER TABLE ${quotedTable} RENAME COLUMN ${this.escapeIdentifier(op.from)} TO ${this.escapeIdentifier(op.to)}`;
|
|
1807
|
+
break;
|
|
1808
|
+
case "modify_column":
|
|
1809
|
+
query = `ALTER TABLE ${quotedTable} ALTER COLUMN ${this.escapeIdentifier(op.column.name)} TYPE ${op.column.type.toUpperCase()}`;
|
|
1810
|
+
break;
|
|
1811
|
+
case "add_constraint": {
|
|
1812
|
+
const colList = (op.constraint.columns ?? []).map((c) => this.escapeIdentifier(c)).join(", ");
|
|
1813
|
+
if (op.constraint.type === "check" && op.constraint.expression) query = `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${this.escapeIdentifier(op.constraint.name)} CHECK (${op.constraint.expression})`;
|
|
1814
|
+
else query = `ALTER TABLE ${quotedTable} ADD CONSTRAINT ${this.escapeIdentifier(op.constraint.name)} ${op.constraint.type.toUpperCase()} (${colList})`;
|
|
1815
|
+
break;
|
|
1816
|
+
}
|
|
1817
|
+
case "drop_constraint": {
|
|
1818
|
+
const cascadeConstraint = op.cascade ? " CASCADE" : "";
|
|
1819
|
+
query = `ALTER TABLE ${quotedTable} DROP CONSTRAINT ${this.escapeIdentifier(op.constraintName)}${cascadeConstraint}`;
|
|
1820
|
+
break;
|
|
1821
|
+
}
|
|
1822
|
+
default: throw this.createDatabaseError("query", `Unsupported alter table operation: ${op.kind}`, void 0);
|
|
1823
|
+
}
|
|
1824
|
+
try {
|
|
1825
|
+
await this.executeQuery(query);
|
|
1826
|
+
} catch (error) {
|
|
1827
|
+
throw this.handleQueryError(error, "alterTable", tableName);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Check if a table exists in the database.
|
|
1833
|
+
*
|
|
1834
|
+
* @remarks
|
|
1835
|
+
* Uses dialect-specific information schema queries to check table existence.
|
|
1836
|
+
* This is useful for development mode auto-sync to determine whether to
|
|
1837
|
+
* CREATE or DROP/CREATE tables.
|
|
1838
|
+
*
|
|
1839
|
+
* @param tableName - Name of table to check
|
|
1840
|
+
* @param schema - Optional schema name (defaults to 'public' for PostgreSQL)
|
|
1841
|
+
* @returns True if table exists, false otherwise
|
|
1842
|
+
*
|
|
1843
|
+
* @throws {DatabaseError} If query fails
|
|
1844
|
+
*
|
|
1845
|
+
* @example
|
|
1846
|
+
* ```typescript
|
|
1847
|
+
* const exists = await adapter.tableExists('users');
|
|
1848
|
+
* if (exists) {
|
|
1849
|
+
* await adapter.dropTable('users');
|
|
1850
|
+
* }
|
|
1851
|
+
* await adapter.createTable(userTableDef);
|
|
1852
|
+
* ```
|
|
1853
|
+
*/
|
|
1854
|
+
async tableExists(tableName, schema) {
|
|
1855
|
+
try {
|
|
1856
|
+
let sql;
|
|
1857
|
+
const params = [];
|
|
1858
|
+
switch (this.dialect) {
|
|
1859
|
+
case "postgresql":
|
|
1860
|
+
sql = `
|
|
1753
1861
|
SELECT EXISTS (
|
|
1754
1862
|
SELECT FROM information_schema.tables
|
|
1755
1863
|
WHERE table_schema = COALESCE($1, (
|
|
@@ -1760,520 +1868,487 @@ var DrizzleAdapter = class {
|
|
|
1760
1868
|
AND table_name = $2
|
|
1761
1869
|
) as exists
|
|
1762
1870
|
`;
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1871
|
+
params.push(schema ?? null, tableName);
|
|
1872
|
+
break;
|
|
1873
|
+
case "mysql":
|
|
1874
|
+
sql = `
|
|
1767
1875
|
SELECT COUNT(*) as count
|
|
1768
1876
|
FROM information_schema.tables
|
|
1769
1877
|
WHERE table_schema = DATABASE()
|
|
1770
1878
|
AND table_name = ?
|
|
1771
1879
|
`;
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1880
|
+
params.push(tableName);
|
|
1881
|
+
break;
|
|
1882
|
+
case "sqlite":
|
|
1883
|
+
sql = `
|
|
1776
1884
|
SELECT COUNT(*) as count
|
|
1777
1885
|
FROM sqlite_master
|
|
1778
1886
|
WHERE type = 'table'
|
|
1779
1887
|
AND name = ?
|
|
1780
1888
|
`;
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
* Useful for detecting orphaned tables or validating schema state.
|
|
1815
|
-
*
|
|
1816
|
-
* @param schema - Optional schema name (defaults to 'public' for PostgreSQL)
|
|
1817
|
-
* @returns Array of table names
|
|
1818
|
-
*
|
|
1819
|
-
* @throws {DatabaseError} If query fails
|
|
1820
|
-
*/
|
|
1821
|
-
async listTables(schema) {
|
|
1822
|
-
try {
|
|
1823
|
-
let sql6;
|
|
1824
|
-
const params = [];
|
|
1825
|
-
switch (this.dialect) {
|
|
1826
|
-
case "postgresql":
|
|
1827
|
-
sql6 = `
|
|
1889
|
+
params.push(tableName);
|
|
1890
|
+
break;
|
|
1891
|
+
default: throw this.createDatabaseError("query", `tableExists not implemented for dialect: ${String(this.dialect)}`, void 0);
|
|
1892
|
+
}
|
|
1893
|
+
const results = await this.executeQuery(sql, params);
|
|
1894
|
+
if (results.length === 0) return false;
|
|
1895
|
+
const row = results[0];
|
|
1896
|
+
if ("exists" in row) return row.exists === true || row.exists === "t" || row.exists === 1;
|
|
1897
|
+
if ("count" in row) return Number(row.count) > 0;
|
|
1898
|
+
return false;
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
throw this.handleQueryError(error, "tableExists", tableName);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Get list of all tables in the database.
|
|
1905
|
+
*
|
|
1906
|
+
* @remarks
|
|
1907
|
+
* Uses dialect-specific information schema queries to list tables.
|
|
1908
|
+
* Useful for detecting orphaned tables or validating schema state.
|
|
1909
|
+
*
|
|
1910
|
+
* @param schema - Optional schema name (defaults to 'public' for PostgreSQL)
|
|
1911
|
+
* @returns Array of table names
|
|
1912
|
+
*
|
|
1913
|
+
* @throws {DatabaseError} If query fails
|
|
1914
|
+
*/
|
|
1915
|
+
async listTables(schema) {
|
|
1916
|
+
try {
|
|
1917
|
+
let sql;
|
|
1918
|
+
const params = [];
|
|
1919
|
+
switch (this.dialect) {
|
|
1920
|
+
case "postgresql":
|
|
1921
|
+
sql = `
|
|
1828
1922
|
SELECT table_name
|
|
1829
1923
|
FROM information_schema.tables
|
|
1830
1924
|
WHERE table_schema = $1
|
|
1831
1925
|
AND table_type = 'BASE TABLE'
|
|
1832
1926
|
ORDER BY table_name
|
|
1833
1927
|
`;
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1928
|
+
params.push(schema ?? "public");
|
|
1929
|
+
break;
|
|
1930
|
+
case "mysql":
|
|
1931
|
+
sql = `
|
|
1838
1932
|
SELECT table_name AS table_name
|
|
1839
1933
|
FROM information_schema.tables
|
|
1840
1934
|
WHERE table_schema = DATABASE()
|
|
1841
1935
|
AND table_type = 'BASE TABLE'
|
|
1842
1936
|
ORDER BY table_name
|
|
1843
1937
|
`;
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1938
|
+
break;
|
|
1939
|
+
case "sqlite":
|
|
1940
|
+
sql = `
|
|
1847
1941
|
SELECT name as table_name
|
|
1848
1942
|
FROM sqlite_master
|
|
1849
1943
|
WHERE type = 'table'
|
|
1850
1944
|
AND name NOT LIKE 'sqlite_%'
|
|
1851
1945
|
ORDER BY name
|
|
1852
1946
|
`;
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
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
|
-
}
|
|
2146
|
-
/**
|
|
2147
|
-
* Classify an error into a DatabaseError.
|
|
2148
|
-
*
|
|
2149
|
-
* @remarks
|
|
2150
|
-
* Subclasses can override this method to add dialect-specific error code classification.
|
|
2151
|
-
*
|
|
2152
|
-
* @param error - Original error
|
|
2153
|
-
* @param sql - SQL statement that caused the error (optional)
|
|
2154
|
-
* @returns DatabaseError instance
|
|
2155
|
-
*
|
|
2156
|
-
* @protected
|
|
2157
|
-
*/
|
|
2158
|
-
classifyError(error, sql6) {
|
|
2159
|
-
if (isDatabaseError(error)) {
|
|
2160
|
-
return error;
|
|
2161
|
-
}
|
|
2162
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2163
|
-
return this.createDatabaseError(
|
|
2164
|
-
"query",
|
|
2165
|
-
sql6 ? `Query failed: ${errorMessage}` : errorMessage,
|
|
2166
|
-
error instanceof Error ? error : void 0
|
|
2167
|
-
);
|
|
2168
|
-
}
|
|
2169
|
-
/**
|
|
2170
|
-
* Handle query errors and convert to DatabaseError.
|
|
2171
|
-
*
|
|
2172
|
-
* @remarks
|
|
2173
|
-
* Protected helper for consistent error handling across CRUD operations.
|
|
2174
|
-
* Delegates to `classifyError` and attaches operation and table context if not present.
|
|
2175
|
-
* Subclasses override `classifyError` to provide dialect-specific error code classification.
|
|
2176
|
-
*
|
|
2177
|
-
* @param error - Original error
|
|
2178
|
-
* @param operation - Operation that failed
|
|
2179
|
-
* @param table - Table name
|
|
2180
|
-
* @returns DatabaseError instance
|
|
2181
|
-
*
|
|
2182
|
-
* @protected
|
|
2183
|
-
*/
|
|
2184
|
-
handleQueryError(error, operation, table) {
|
|
2185
|
-
const dbError = this.classifyError(error);
|
|
2186
|
-
const context = `${operation} operation failed on table '${table}'`;
|
|
2187
|
-
if (!dbError.message.includes(context)) {
|
|
2188
|
-
dbError.message = `${context}: ${dbError.message}`;
|
|
2189
|
-
}
|
|
2190
|
-
if (!dbError.table) {
|
|
2191
|
-
dbError.table = table;
|
|
2192
|
-
}
|
|
2193
|
-
return dbError;
|
|
2194
|
-
}
|
|
1947
|
+
break;
|
|
1948
|
+
default: throw this.createDatabaseError("query", `listTables not implemented for dialect: ${String(this.dialect)}`, void 0);
|
|
1949
|
+
}
|
|
1950
|
+
return (await this.executeQuery(sql, params)).map((row) => row.table_name);
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
throw this.handleQueryError(error, "listTables", "");
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Escape a table or column identifier.
|
|
1957
|
+
*
|
|
1958
|
+
* @remarks
|
|
1959
|
+
* Default uses double quotes (SQL standard).
|
|
1960
|
+
* MySQL adapter should override to use backticks.
|
|
1961
|
+
*
|
|
1962
|
+
* @param identifier - Identifier to escape
|
|
1963
|
+
* @returns Escaped identifier
|
|
1964
|
+
*
|
|
1965
|
+
* @protected
|
|
1966
|
+
*/
|
|
1967
|
+
escapeIdentifier(identifier) {
|
|
1968
|
+
return `"${identifier.replace(/"/g, "\"\"")}"`;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Create a DatabaseError with proper error kind classification.
|
|
1972
|
+
*
|
|
1973
|
+
* @remarks
|
|
1974
|
+
* Protected helper for subclasses to create consistent errors.
|
|
1975
|
+
* Subclasses can override to add dialect-specific error classification.
|
|
1976
|
+
*
|
|
1977
|
+
* @param kind - Error kind
|
|
1978
|
+
* @param message - Error message
|
|
1979
|
+
* @param cause - Original error
|
|
1980
|
+
* @returns DatabaseError instance
|
|
1981
|
+
*
|
|
1982
|
+
* @protected
|
|
1983
|
+
*/
|
|
1984
|
+
createDatabaseError(kind, message, cause) {
|
|
1985
|
+
return require_types.createDatabaseError({
|
|
1986
|
+
kind,
|
|
1987
|
+
message,
|
|
1988
|
+
cause
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Helper to create transaction context CRUD forwarding methods.
|
|
1993
|
+
*
|
|
1994
|
+
* @param txDb - Thunk returning the transaction-bound Drizzle executor
|
|
1995
|
+
* @returns Object providing forwarded CRUD and getDrizzle methods
|
|
1996
|
+
*
|
|
1997
|
+
* @protected
|
|
1998
|
+
*/
|
|
1999
|
+
createTransactionForwarders(txDb) {
|
|
2000
|
+
return createTransactionForwarders(this, txDb);
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* The Drizzle handles one transaction hands out; see
|
|
2004
|
+
* `transactionDrizzleHandles`. `build` makes an instance on the
|
|
2005
|
+
* transaction's own connection, with the relations config when given one.
|
|
2006
|
+
*
|
|
2007
|
+
* @protected
|
|
2008
|
+
*/
|
|
2009
|
+
transactionDrizzleHandles(build) {
|
|
2010
|
+
return transactionDrizzleHandles(build);
|
|
2011
|
+
}
|
|
2012
|
+
/**
|
|
2013
|
+
* A transaction context's `update`, for each adapter to bind.
|
|
2014
|
+
*
|
|
2015
|
+
* Not forwarded to the pooled `update` the way `select` and `delete` are:
|
|
2016
|
+
* that one goes through the Drizzle query builder, which writes the columns
|
|
2017
|
+
* the runtime MODEL declares, and a transaction's update must reach the
|
|
2018
|
+
* columns the physical TABLE has — the localization transition window
|
|
2019
|
+
* writes a column the model has already moved to a companion table that
|
|
2020
|
+
* does not exist yet. The INSERT half of every transaction context already
|
|
2021
|
+
* builds its own statement for that reason; `update-statement.ts` is the
|
|
2022
|
+
* UPDATE half, spelled once for the three adapters, and this runs it: on
|
|
2023
|
+
* the transaction executor, classified with the operation and table named
|
|
2024
|
+
* as the pooled `update` classifies its failures, and read back through
|
|
2025
|
+
* `select` on the same executor when the caller asked for rows.
|
|
2026
|
+
*
|
|
2027
|
+
* That read is by the rows' IDENTITY where the dialect can report it: on
|
|
2028
|
+
* PostgreSQL and SQLite the statement carries `RETURNING <primary key>`, and
|
|
2029
|
+
* the read-back asks for exactly those rows — so a row another transaction
|
|
2030
|
+
* adds under the same predicate meanwhile is not among them, and an update
|
|
2031
|
+
* whose own write falsifies its predicate still reads its rows back. MySQL
|
|
2032
|
+
* has no RETURNING, so there the read re-runs the predicate, as the pooled
|
|
2033
|
+
* `update` always has on that dialect; a table with no primary key in its
|
|
2034
|
+
* model reads back the same way.
|
|
2035
|
+
*
|
|
2036
|
+
* @param txDb - thunk returning the transaction-bound Drizzle instance
|
|
2037
|
+
* @param run - how this dialect runs a statement on that instance, and the
|
|
2038
|
+
* rows it returns when the statement carries RETURNING: better-sqlite3
|
|
2039
|
+
* answers synchronously, the pooled drivers do not
|
|
2040
|
+
* @param bindUnmodeled - how a value binds when the model declares no
|
|
2041
|
+
* column for it; a declared column binds through its own encoder
|
|
2042
|
+
* @returns the context's `update` method
|
|
2043
|
+
*/
|
|
2044
|
+
transactionUpdate(txDb, run, bindUnmodeled) {
|
|
2045
|
+
return async (table, data, where, options) => {
|
|
2046
|
+
const wantsRows = this.updateReturnsRows(options?.returning);
|
|
2047
|
+
const tableObj = this.resolvedTableObject(table);
|
|
2048
|
+
const identity = wantsRows && this.getCapabilities().supportsReturning ? this.identityColumns(tableObj) : [];
|
|
2049
|
+
let reported;
|
|
2050
|
+
try {
|
|
2051
|
+
reported = await run(this.buildTransactionUpdate(tableObj, table, data, where, {
|
|
2052
|
+
bindUnmodeled,
|
|
2053
|
+
returning: identity.map((key) => drizzle_orm.sql`${drizzle_orm.sql.identifier(key.sqlName)}`)
|
|
2054
|
+
}), identity.length > 0);
|
|
2055
|
+
} catch (error) {
|
|
2056
|
+
throw this.handleQueryError(error, "update", table);
|
|
2057
|
+
}
|
|
2058
|
+
if (!wantsRows) return [];
|
|
2059
|
+
if (identity.length === 0 || !Array.isArray(reported)) return this.select(table, { where }, txDb());
|
|
2060
|
+
const rows = [];
|
|
2061
|
+
for (let at = 0; at < reported.length; at += IDENTITY_READ_BACK_CHUNK) rows.push(...await this.select(table, { where: rowsByIdentity(identity, reported.slice(at, at + IDENTITY_READ_BACK_CHUNK)) }, txDb()));
|
|
2062
|
+
return rows;
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* The columns that identify a row of this table, under both spellings: the
|
|
2067
|
+
* SQL name RETURNING reports and the Drizzle property name the query
|
|
2068
|
+
* builder's WHERE is addressed by. Columns flagged primary first; failing
|
|
2069
|
+
* that, the table-level primary key the dialect's table config carries
|
|
2070
|
+
* (`primaryKey({ columns })` leaves each column's flag false), which each
|
|
2071
|
+
* adapter reads through its own dialect. Empty when the model declares
|
|
2072
|
+
* neither, and the read-back falls back to the caller's predicate.
|
|
2073
|
+
*/
|
|
2074
|
+
identityColumns(tableObj) {
|
|
2075
|
+
const columns = Object.entries((0, drizzle_orm.getColumns)(tableObj));
|
|
2076
|
+
const flagged = columns.filter(([, c]) => c.primary === true);
|
|
2077
|
+
return (flagged.length > 0 ? flagged : columns.filter(([, c]) => this.compositePrimaryKey(tableObj).includes(c))).flatMap(([jsName, c]) => typeof c.name === "string" ? [{
|
|
2078
|
+
jsName,
|
|
2079
|
+
sqlName: c.name
|
|
2080
|
+
}] : []);
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* How a value binds to a column the model does not declare, on a driver
|
|
2084
|
+
* that binds scalars, dates and binary natively: a structured value goes
|
|
2085
|
+
* as JSON text, since what node-postgres and mysql2 make of a bare object
|
|
2086
|
+
* or array is not JSON. For the adapters to hand `transactionUpdate`.
|
|
2087
|
+
*/
|
|
2088
|
+
bindUnmodeledStructuredAsJson(value) {
|
|
2089
|
+
return bindStructuredAsJson(value);
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Refuse an upsert target every dialect would not answer alike: a conflict
|
|
2093
|
+
* column named twice, which SQLite rejects as no constraint's target and
|
|
2094
|
+
* MySQL would accept; and, when nothing is to be updated, a conflict column
|
|
2095
|
+
* left for the database to fill, since the row that stands is then read
|
|
2096
|
+
* back by a value the caller never gave. Shared with the dialects that
|
|
2097
|
+
* override `upsert`, so the refusals are one rule.
|
|
2098
|
+
*/
|
|
2099
|
+
assertUpsertTarget(table, data, options, keepExisting) {
|
|
2100
|
+
const columns = options.conflictColumns;
|
|
2101
|
+
if (new Set(columns).size !== columns.length) throw require_types.createDatabaseError({
|
|
2102
|
+
kind: "query",
|
|
2103
|
+
message: `upsert on ${table} names a conflict column more than once (${columns.join(", ")}).`
|
|
2104
|
+
});
|
|
2105
|
+
if (keepExisting && columns.some((column) => data[column] === void 0)) throw require_types.createDatabaseError({
|
|
2106
|
+
kind: "query",
|
|
2107
|
+
message: `upsert on ${table} needs a value for every conflict column (${columns.join(", ")}).`
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* The columns of a table-level `primaryKey({ columns })`, as the dialect's
|
|
2112
|
+
* table config reports them. The base class cannot read that config without
|
|
2113
|
+
* naming a dialect, so each adapter answers for its own; a dialect that does
|
|
2114
|
+
* not answer reports none.
|
|
2115
|
+
*/
|
|
2116
|
+
compositePrimaryKey(_tableObj) {
|
|
2117
|
+
return [];
|
|
2118
|
+
}
|
|
2119
|
+
/** The registered table object, or the error every CRUD method throws without one. */
|
|
2120
|
+
resolvedTableObject(table) {
|
|
2121
|
+
const tableObj = this.getTableObject(table);
|
|
2122
|
+
if (!tableObj || typeof tableObj !== "object") throw this.createDatabaseError("query", `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`, void 0);
|
|
2123
|
+
return tableObj;
|
|
2124
|
+
}
|
|
2125
|
+
/**
|
|
2126
|
+
* The statement `transactionUpdate` runs.
|
|
2127
|
+
*
|
|
2128
|
+
* @throws when nothing would be written — every key `undefined`, or none at
|
|
2129
|
+
* all. The query builder refused that too ("No values to set"), and a
|
|
2130
|
+
* patch that names nothing is a caller's mistake rather than a write of
|
|
2131
|
+
* nothing.
|
|
2132
|
+
*/
|
|
2133
|
+
buildTransactionUpdate(tableObj, table, data, where, binding) {
|
|
2134
|
+
const statement = buildUpdateStatement({
|
|
2135
|
+
table,
|
|
2136
|
+
tableObj,
|
|
2137
|
+
data,
|
|
2138
|
+
where,
|
|
2139
|
+
bindUnmodeled: binding.bindUnmodeled,
|
|
2140
|
+
returning: binding.returning
|
|
2141
|
+
});
|
|
2142
|
+
if (!statement) throw this.createDatabaseError("query", `No values to set: the update of "${table}" names no column with a defined value.`, void 0);
|
|
2143
|
+
return statement;
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Whether a transaction's `update` reads the rows it changed back: the
|
|
2147
|
+
* caller named columns, or `*`. An empty list, like no option at all, asks
|
|
2148
|
+
* for nothing and `update` answers `[]`, as it always has. The rows come
|
|
2149
|
+
* from a read of the update's own WHERE on its own transaction, so they are
|
|
2150
|
+
* the model's view of the row, decoded as every read is decoded — which is
|
|
2151
|
+
* what the query-builder update returned for them, and why the named list
|
|
2152
|
+
* selects nothing narrower.
|
|
2153
|
+
*/
|
|
2154
|
+
updateReturnsRows(returning) {
|
|
2155
|
+
return returning !== void 0 && !(Array.isArray(returning) && returning.length === 0);
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* The driver's own error inside `error`: the first in its `cause` chain that
|
|
2159
|
+
* `isDriverError` recognises, or `error` itself when none does.
|
|
2160
|
+
*
|
|
2161
|
+
* A query that runs through Drizzle fails with Drizzle's `DrizzleQueryError`,
|
|
2162
|
+
* whose message is the SQL statement and which carries no driver code; the
|
|
2163
|
+
* driver's error, the only object that knows what actually went wrong, is
|
|
2164
|
+
* its `cause`. A dialect's {@link classifyError} classifies from what this
|
|
2165
|
+
* returns, so a failure reads the same whether or not Drizzle wrapped it.
|
|
2166
|
+
*
|
|
2167
|
+
* The walk is bounded, because an error chain can be made cyclic and a
|
|
2168
|
+
* classifier that can hang is worse than one that gives up. Falling back to
|
|
2169
|
+
* `error` keeps an error that carries no code anywhere classified as before.
|
|
2170
|
+
*
|
|
2171
|
+
* @param error - The error as it was caught
|
|
2172
|
+
* @param isDriverError - Whether a link in the chain is the driver's error
|
|
2173
|
+
* @returns The driver's error, or `error` when no link is
|
|
2174
|
+
*/
|
|
2175
|
+
driverErrorIn(error, isDriverError) {
|
|
2176
|
+
let cursor = error;
|
|
2177
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
2178
|
+
if (cursor === null || typeof cursor !== "object") break;
|
|
2179
|
+
const candidate = cursor;
|
|
2180
|
+
if (isDriverError(candidate)) return candidate;
|
|
2181
|
+
cursor = candidate.cause;
|
|
2182
|
+
}
|
|
2183
|
+
return error ?? {};
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Classify an error into a DatabaseError.
|
|
2187
|
+
*
|
|
2188
|
+
* @remarks
|
|
2189
|
+
* Subclasses can override this method to add dialect-specific error code classification.
|
|
2190
|
+
*
|
|
2191
|
+
* @param error - Original error
|
|
2192
|
+
* @param sql - SQL statement that caused the error (optional)
|
|
2193
|
+
* @returns DatabaseError instance
|
|
2194
|
+
*
|
|
2195
|
+
* @protected
|
|
2196
|
+
*/
|
|
2197
|
+
classifyError(error, sql) {
|
|
2198
|
+
if (require_types.isDatabaseError(error)) return error;
|
|
2199
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2200
|
+
return this.createDatabaseError("query", sql ? `Query failed: ${errorMessage}` : errorMessage, error instanceof Error ? error : void 0);
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Handle query errors and convert to DatabaseError.
|
|
2204
|
+
*
|
|
2205
|
+
* @remarks
|
|
2206
|
+
* Protected helper for consistent error handling across CRUD operations.
|
|
2207
|
+
* Delegates to `classifyError` and attaches operation and table context if not present.
|
|
2208
|
+
* Subclasses override `classifyError` to provide dialect-specific error code classification.
|
|
2209
|
+
*
|
|
2210
|
+
* @param error - Original error
|
|
2211
|
+
* @param operation - Operation that failed
|
|
2212
|
+
* @param table - Table name
|
|
2213
|
+
* @returns DatabaseError instance
|
|
2214
|
+
*
|
|
2215
|
+
* @protected
|
|
2216
|
+
*/
|
|
2217
|
+
handleQueryError(error, operation, table) {
|
|
2218
|
+
const dbError = this.classifyError(error);
|
|
2219
|
+
const context = `${operation} operation failed on table '${table}'`;
|
|
2220
|
+
if (!dbError.message.includes(context)) dbError.message = `${context}: ${dbError.message}`;
|
|
2221
|
+
if (!dbError.table) dbError.table = table;
|
|
2222
|
+
return dbError;
|
|
2223
|
+
}
|
|
2195
2224
|
};
|
|
2196
|
-
|
|
2225
|
+
/**
|
|
2226
|
+
* How many reported rows one read-back statement names. Well under SQLite's
|
|
2227
|
+
* expression-depth limit of a thousand and its default variable limit, and
|
|
2228
|
+
* under PostgreSQL's parameter limit, with a composite key of several columns.
|
|
2229
|
+
*/
|
|
2230
|
+
const IDENTITY_READ_BACK_CHUNK = 200;
|
|
2231
|
+
/**
|
|
2232
|
+
* The predicate that names exactly the rows a statement reported, addressed
|
|
2233
|
+
* by the Drizzle property names the where builder resolves: for a single-
|
|
2234
|
+
* column key one `IN` over the reported values; for a composite key one `and`
|
|
2235
|
+
* of every key column per row, `or`-ed together, so the key is matched as a
|
|
2236
|
+
* whole rather than column by column.
|
|
2237
|
+
*/
|
|
2197
2238
|
function rowsByIdentity(identity, rows) {
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
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
|
-
};
|
|
2239
|
+
if (identity.length === 1) {
|
|
2240
|
+
const [key] = identity;
|
|
2241
|
+
return { and: [{
|
|
2242
|
+
column: key.jsName,
|
|
2243
|
+
op: "IN",
|
|
2244
|
+
value: rows.map((row) => row[key.sqlName])
|
|
2245
|
+
}] };
|
|
2246
|
+
}
|
|
2247
|
+
return { or: rows.map((row) => ({ and: identity.map((key) => ({
|
|
2248
|
+
column: key.jsName,
|
|
2249
|
+
op: "=",
|
|
2250
|
+
value: row[key.sqlName]
|
|
2251
|
+
})) })) };
|
|
2223
2252
|
}
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2253
|
+
/**
|
|
2254
|
+
* Render a `ColumnDefinition.default` as a SQL fragment
|
|
2255
|
+
* for embedding in a `DEFAULT` clause. DDL is not protected by Drizzle's
|
|
2256
|
+
* prepared statements — these expressions are concatenated into raw SQL —
|
|
2257
|
+
* so we treat the default as untrusted input even though it currently
|
|
2258
|
+
* flows from server-side schema definitions: an admin-defined dynamic
|
|
2259
|
+
* collection would otherwise turn into RCE-on-DB instead of just CMS
|
|
2260
|
+
* admin.
|
|
2261
|
+
*
|
|
2262
|
+
* Rules:
|
|
2263
|
+
*
|
|
2264
|
+
* - **null / number / boolean** → rendered as a SQL literal.
|
|
2265
|
+
* - **string** → quoted; embedded `'` doubled; `;`, `\`, control
|
|
2266
|
+
* characters, and null bytes rejected so they can't break out of
|
|
2267
|
+
* the surrounding quote pair into adjacent DDL.
|
|
2268
|
+
* - **`{ sql: "…" }`** → only the explicit allowlist of named
|
|
2269
|
+
* functions below is honored (case-insensitive). Anything else is
|
|
2270
|
+
* a hard error: the previous code embedded the raw `sql` string
|
|
2271
|
+
* verbatim, which is the SQL injection primitive the audit flagged.
|
|
2272
|
+
*/
|
|
2273
|
+
const ALLOWED_DEFAULT_SQL_EXPRESSIONS = /* @__PURE__ */ new Set([
|
|
2274
|
+
"current_timestamp",
|
|
2275
|
+
"now()",
|
|
2276
|
+
"gen_random_uuid()",
|
|
2277
|
+
"uuid_generate_v4()",
|
|
2278
|
+
"current_date",
|
|
2279
|
+
"current_time"
|
|
2231
2280
|
]);
|
|
2232
2281
|
function renderDefaultValue(value, columnName) {
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
return `'${value.replace(/'/g, "''")}'`;
|
|
2252
|
-
}
|
|
2253
|
-
if (typeof value === "object" && value !== null && "sql" in value) {
|
|
2254
|
-
const raw = value.sql;
|
|
2255
|
-
if (typeof raw !== "string") {
|
|
2256
|
-
throw new Error(
|
|
2257
|
-
`DEFAULT for column "${columnName}" has a { sql } expression that is not a string.`
|
|
2258
|
-
);
|
|
2259
|
-
}
|
|
2260
|
-
const normalized = raw.trim().toLowerCase();
|
|
2261
|
-
if (!ALLOWED_DEFAULT_SQL_EXPRESSIONS.has(normalized)) {
|
|
2262
|
-
throw new Error(
|
|
2263
|
-
`DEFAULT for column "${columnName}" uses a SQL expression "${raw}" that is not on the allowlist. Allowed: ${[...ALLOWED_DEFAULT_SQL_EXPRESSIONS].join(", ")}.`
|
|
2264
|
-
);
|
|
2265
|
-
}
|
|
2266
|
-
return raw.trim();
|
|
2267
|
-
}
|
|
2268
|
-
throw new Error(
|
|
2269
|
-
`DEFAULT for column "${columnName}" has an unsupported type (${typeof value}).`
|
|
2270
|
-
);
|
|
2282
|
+
if (value === null) return "NULL";
|
|
2283
|
+
if (typeof value === "number") {
|
|
2284
|
+
if (!Number.isFinite(value)) throw new Error(`DEFAULT for column "${columnName}" must be a finite number (got ${value}).`);
|
|
2285
|
+
return String(value);
|
|
2286
|
+
}
|
|
2287
|
+
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
|
2288
|
+
if (typeof value === "string") {
|
|
2289
|
+
if (/[;\\\n\r\0]/.test(value)) throw new Error(`DEFAULT string for column "${columnName}" contains characters that are not allowed in DDL (no semicolons, backslashes, control chars, or null bytes).`);
|
|
2290
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
2291
|
+
}
|
|
2292
|
+
if (typeof value === "object" && value !== null && "sql" in value) {
|
|
2293
|
+
const raw = value.sql;
|
|
2294
|
+
if (typeof raw !== "string") throw new Error(`DEFAULT for column "${columnName}" has a { sql } expression that is not a string.`);
|
|
2295
|
+
const normalized = raw.trim().toLowerCase();
|
|
2296
|
+
if (!ALLOWED_DEFAULT_SQL_EXPRESSIONS.has(normalized)) throw new Error(`DEFAULT for column "${columnName}" uses a SQL expression "${raw}" that is not on the allowlist. Allowed: ${[...ALLOWED_DEFAULT_SQL_EXPRESSIONS].join(", ")}.`);
|
|
2297
|
+
return raw.trim();
|
|
2298
|
+
}
|
|
2299
|
+
throw new Error(`DEFAULT for column "${columnName}" has an unsupported type (${typeof value}).`);
|
|
2271
2300
|
}
|
|
2272
|
-
|
|
2273
2301
|
// src/index.ts
|
|
2274
|
-
|
|
2275
|
-
|
|
2302
|
+
/**
|
|
2303
|
+
* @nextly/adapter-drizzle
|
|
2304
|
+
*
|
|
2305
|
+
* Shared Drizzle ORM adapter logic for Nextly database adapters.
|
|
2306
|
+
* This package provides the base adapter class and utilities used by
|
|
2307
|
+
* dialect-specific adapters (PostgreSQL, MySQL, SQLite).
|
|
2308
|
+
*
|
|
2309
|
+
* @remarks
|
|
2310
|
+
* This is the main entry point that exports only the core adapter class.
|
|
2311
|
+
* Specialized utilities are available via subpath exports for optimal tree-shaking.
|
|
2312
|
+
*
|
|
2313
|
+
* ## Subpath Exports
|
|
2314
|
+
*
|
|
2315
|
+
* **Types:**
|
|
2316
|
+
* ```typescript
|
|
2317
|
+
* import type {
|
|
2318
|
+
* DatabaseCapabilities,
|
|
2319
|
+
* TransactionContext,
|
|
2320
|
+
* WhereClause,
|
|
2321
|
+
* SelectOptions,
|
|
2322
|
+
* } from '@nextly/adapter-drizzle/types';
|
|
2323
|
+
* ```
|
|
2324
|
+
*
|
|
2325
|
+
* **Query Builder:**
|
|
2326
|
+
* ```typescript
|
|
2327
|
+
* import { QueryBuilder } from '@nextly/adapter-drizzle/query-builder';
|
|
2328
|
+
* ```
|
|
2329
|
+
*
|
|
2330
|
+
* **Migrations:**
|
|
2331
|
+
* ```typescript
|
|
2332
|
+
* import {
|
|
2333
|
+
* calculateChecksum,
|
|
2334
|
+
* sortMigrations,
|
|
2335
|
+
* migrationHelpers,
|
|
2336
|
+
* } from '@nextly/adapter-drizzle/migrations';
|
|
2337
|
+
* ```
|
|
2338
|
+
*
|
|
2339
|
+
* **Schema Utilities:**
|
|
2340
|
+
* ```typescript
|
|
2341
|
+
* import { schemaUtils } from '@nextly/adapter-drizzle/schema';
|
|
2342
|
+
* ```
|
|
2343
|
+
*
|
|
2344
|
+
* @packageDocumentation
|
|
2345
|
+
*/
|
|
2346
|
+
/**
|
|
2347
|
+
* Package version
|
|
2348
|
+
* @public
|
|
2349
|
+
*/
|
|
2350
|
+
const version = "0.1.0";
|
|
2276
2351
|
exports.DrizzleAdapter = DrizzleAdapter;
|
|
2277
2352
|
exports.version = version;
|
|
2278
|
-
|
|
2353
|
+
|
|
2279
2354
|
//# sourceMappingURL=index.cjs.map
|