@atscript/db-mysql 0.1.39 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/index-B1N7X623.d.mts +6 -0
- package/dist/index-O193LJVT.d.cts +6 -0
- package/dist/index.cjs +266 -307
- package/dist/index.d.cts +272 -0
- package/dist/index.d.mts +272 -0
- package/dist/index.mjs +240 -258
- package/dist/plugin-3jLlPI4f.mjs +93 -0
- package/dist/plugin-C53PKZHf.cjs +98 -0
- package/dist/plugin.cjs +3 -111
- package/dist/plugin.d.cts +2 -0
- package/dist/plugin.d.mts +2 -0
- package/dist/plugin.mjs +2 -87
- package/package.json +23 -33
- package/LICENSE +0 -21
- package/dist/index.d.ts +0 -271
- package/dist/plugin.d.ts +0 -5
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
import { t as MysqlPlugin } from "./plugin-3jLlPI4f.mjs";
|
|
1
2
|
import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
|
|
2
3
|
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, refActionToSql, toSqlValue } from "@atscript/db-sql-tools";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
//#region packages/db-mysql/src/sql-builder.ts
|
|
4
|
+
//#region src/sql-builder.ts
|
|
5
|
+
/** Escapes a MySQL identifier by doubling backticks. */
|
|
6
6
|
function esc(name) {
|
|
7
7
|
return name.replace(/`/g, "``");
|
|
8
8
|
}
|
|
9
|
+
/** Backtick-quotes a single identifier. */
|
|
9
10
|
function qi(name) {
|
|
10
11
|
return `\`${esc(name)}\``;
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Backtick-quotes a table name, handling `schema.table` format.
|
|
15
|
+
* Input is a raw name like `mydb.users` or just `users`.
|
|
16
|
+
*/
|
|
12
17
|
function quoteTableName(name) {
|
|
13
18
|
const dot = name.indexOf(".");
|
|
14
19
|
if (dot >= 0) return `${qi(name.slice(0, dot))}.${qi(name.slice(dot + 1))}`;
|
|
@@ -24,7 +29,7 @@ const mysqlDialect = {
|
|
|
24
29
|
unlimitedLimit: "18446744073709551615",
|
|
25
30
|
toValue: toSqlValue,
|
|
26
31
|
toParam(value) {
|
|
27
|
-
if (value ===
|
|
32
|
+
if (value === void 0) return null;
|
|
28
33
|
return typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
29
34
|
},
|
|
30
35
|
regex(quotedCol, value) {
|
|
@@ -36,27 +41,51 @@ const mysqlDialect = {
|
|
|
36
41
|
},
|
|
37
42
|
createViewPrefix: "CREATE OR REPLACE VIEW"
|
|
38
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* Builds an INSERT statement.
|
|
46
|
+
*/
|
|
39
47
|
function buildInsert$1(table, data) {
|
|
40
48
|
return buildInsert(mysqlDialect, table, data);
|
|
41
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Builds a SELECT statement with optional sort, limit, offset, projection.
|
|
52
|
+
*/
|
|
42
53
|
function buildSelect$1(table, where, controls) {
|
|
43
54
|
return buildSelect(mysqlDialect, table, where, controls);
|
|
44
55
|
}
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Builds an UPDATE ... SET ... WHERE statement with optional LIMIT.
|
|
58
|
+
*/
|
|
59
|
+
function buildUpdate$1(table, data, where, limit, ops) {
|
|
60
|
+
return buildUpdate(mysqlDialect, table, data, where, limit, ops);
|
|
47
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Builds a DELETE ... WHERE statement with optional LIMIT.
|
|
64
|
+
*/
|
|
48
65
|
function buildDelete$1(table, where, limit) {
|
|
49
66
|
return buildDelete(mysqlDialect, table, where, limit);
|
|
50
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Builds a CREATE OR REPLACE VIEW statement from a view plan and column mappings.
|
|
70
|
+
*/
|
|
51
71
|
function buildCreateView$1(viewName, plan, columns, resolveFieldRef) {
|
|
52
72
|
return buildCreateView(mysqlDialect, viewName, plan, columns, resolveFieldRef);
|
|
53
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
76
|
+
*/
|
|
54
77
|
function buildAggregateSelect$1(table, where, controls) {
|
|
55
78
|
return buildAggregateSelect(mysqlDialect, table, where, controls);
|
|
56
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Builds a COUNT query for the number of distinct groups.
|
|
82
|
+
*/
|
|
57
83
|
function buildAggregateCount$1(table, where, controls) {
|
|
58
84
|
return buildAggregateCount(mysqlDialect, table, where, controls);
|
|
59
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Maps portable collation values to MySQL collation names.
|
|
88
|
+
*/
|
|
60
89
|
function collationToMysql(collation) {
|
|
61
90
|
switch (collation) {
|
|
62
91
|
case "binary": return "utf8mb4_bin";
|
|
@@ -74,7 +103,8 @@ function collationToMysql(collation) {
|
|
|
74
103
|
* For FK fields, delegates to the target PK's type via `field.fkTargetField`
|
|
75
104
|
* so the FK column type always matches the referenced column.
|
|
76
105
|
*/
|
|
77
|
-
/** Maps integer primitive tags to MySQL integer types. */
|
|
106
|
+
/** Maps integer primitive tags to MySQL integer types. */
|
|
107
|
+
function intTypeFromTags(tags, unsigned) {
|
|
78
108
|
if (tags?.has("int8")) return unsigned ? "TINYINT UNSIGNED" : "TINYINT";
|
|
79
109
|
if (tags?.has("uint8") || tags?.has("byte")) return "TINYINT UNSIGNED";
|
|
80
110
|
if (tags?.has("int16")) return unsigned ? "SMALLINT UNSIGNED" : "SMALLINT";
|
|
@@ -94,36 +124,36 @@ function mysqlTypeFromField(field) {
|
|
|
94
124
|
const unsigned = metadata?.has("db.mysql.unsigned") ?? false;
|
|
95
125
|
const precision = metadata?.get("db.column.precision");
|
|
96
126
|
switch (field.designType) {
|
|
97
|
-
case "number":
|
|
127
|
+
case "number":
|
|
98
128
|
if (precision) return `DECIMAL(${precision.precision},${precision.scale})`;
|
|
99
129
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") return unsigned ? "BIGINT UNSIGNED" : "BIGINT";
|
|
100
130
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return "TIMESTAMP";
|
|
101
131
|
if (tags?.has("int")) return intTypeFromTags(tags, unsigned);
|
|
102
132
|
return "DOUBLE";
|
|
103
|
-
}
|
|
104
133
|
case "integer": return intTypeFromTags(tags, unsigned);
|
|
105
|
-
case "decimal":
|
|
134
|
+
case "decimal":
|
|
106
135
|
if (precision) return `DECIMAL(${precision.precision},${precision.scale})`;
|
|
107
136
|
return "DECIMAL(10,2)";
|
|
108
|
-
}
|
|
109
137
|
case "boolean": return "TINYINT(1)";
|
|
110
138
|
case "string": {
|
|
111
139
|
if (tags?.has("char")) return "CHAR(1)";
|
|
112
|
-
const maxLen = metadata?.get("expect.maxLength")?.length;
|
|
113
|
-
if (maxLen !==
|
|
114
|
-
if (maxLen !==
|
|
140
|
+
const maxLen = (metadata?.get("expect.maxLength"))?.length;
|
|
141
|
+
if (maxLen !== void 0 && maxLen <= 65535) return `VARCHAR(${maxLen})`;
|
|
142
|
+
if (maxLen !== void 0 && maxLen > 65535) return "LONGTEXT";
|
|
115
143
|
if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
|
|
116
144
|
return "TEXT";
|
|
117
145
|
}
|
|
118
146
|
case "json":
|
|
119
147
|
case "object":
|
|
120
148
|
case "array": return "JSON";
|
|
121
|
-
default:
|
|
149
|
+
default:
|
|
122
150
|
if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
|
|
123
151
|
return "TEXT";
|
|
124
|
-
}
|
|
125
152
|
}
|
|
126
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Builds a CREATE TABLE IF NOT EXISTS statement with MySQL options.
|
|
156
|
+
*/
|
|
127
157
|
function buildCreateTable(table, fields, foreignKeys, options) {
|
|
128
158
|
const colDefs = [];
|
|
129
159
|
const primaryKeys = fields.filter((f) => f.isPrimaryKey);
|
|
@@ -134,13 +164,13 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
134
164
|
if (options?.incrementFields?.has(field.physicalName)) def += " AUTO_INCREMENT";
|
|
135
165
|
if (!field.optional && !field.isPrimaryKey && !options?.incrementFields?.has(field.physicalName)) def += " NOT NULL";
|
|
136
166
|
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
137
|
-
else if (field.defaultValue?.kind === "fn") {
|
|
167
|
+
else if (field.defaultValue?.kind === "fn") {
|
|
138
168
|
if (field.defaultValue.fn === "uuid") def += " DEFAULT (UUID())";
|
|
139
|
-
else if (field.defaultValue.fn === "now") def += " DEFAULT CURRENT_TIMESTAMP";
|
|
169
|
+
else if (field.defaultValue.fn === "now") def += " DEFAULT CURRENT_TIMESTAMP";
|
|
140
170
|
}
|
|
141
171
|
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
142
172
|
if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
|
|
143
|
-
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
173
|
+
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
144
174
|
const onUpdate = options?.onUpdateFields?.get(field.physicalName);
|
|
145
175
|
if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
|
|
146
176
|
colDefs.push(def);
|
|
@@ -168,28 +198,23 @@ else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
|
168
198
|
const charset = options?.charset ?? "utf8mb4";
|
|
169
199
|
const collation = options?.collation ?? "utf8mb4_unicode_ci";
|
|
170
200
|
sql += ` ENGINE=${engine} DEFAULT CHARSET=${charset} COLLATE=${collation}`;
|
|
171
|
-
if (options?.autoIncrementStart !==
|
|
201
|
+
if (options?.autoIncrementStart !== void 0) sql += ` AUTO_INCREMENT=${options.autoIncrementStart}`;
|
|
172
202
|
return sql;
|
|
173
203
|
}
|
|
174
|
-
|
|
175
204
|
//#endregion
|
|
176
|
-
//#region
|
|
205
|
+
//#region src/filter-builder.ts
|
|
206
|
+
/**
|
|
207
|
+
* Translates a uniqu filter expression into a parameterized MySQL WHERE clause.
|
|
208
|
+
*
|
|
209
|
+
* @returns `{ sql, params }` — the WHERE clause (without "WHERE") and bound params.
|
|
210
|
+
* Returns `{ sql: '1=1', params: [] }` for empty/null filters.
|
|
211
|
+
*/
|
|
177
212
|
function buildWhere(filter) {
|
|
178
213
|
return buildWhere$1(mysqlDialect, filter);
|
|
179
214
|
}
|
|
180
|
-
|
|
181
215
|
//#endregion
|
|
182
|
-
//#region
|
|
183
|
-
|
|
184
|
-
if (key in obj) Object.defineProperty(obj, key, {
|
|
185
|
-
value,
|
|
186
|
-
enumerable: true,
|
|
187
|
-
configurable: true,
|
|
188
|
-
writable: true
|
|
189
|
-
});
|
|
190
|
-
else obj[key] = value;
|
|
191
|
-
return obj;
|
|
192
|
-
}
|
|
216
|
+
//#region src/mysql-adapter.ts
|
|
217
|
+
/** Parses a MySQL UTC datetime string ('YYYY-MM-DD HH:MM:SS') to epoch ms. Returns the original value if parsing fails. */
|
|
193
218
|
function utcDatetimeToEpochMs(value) {
|
|
194
219
|
if (typeof value === "number") return value;
|
|
195
220
|
if (value instanceof Date) return value.getTime();
|
|
@@ -199,14 +224,50 @@ function utcDatetimeToEpochMs(value) {
|
|
|
199
224
|
}
|
|
200
225
|
return value;
|
|
201
226
|
}
|
|
202
|
-
/** Formats epoch ms as 'YYYY-MM-DD HH:MM:SS' in UTC for MySQL TIMESTAMP columns. */
|
|
227
|
+
/** Formats epoch ms as 'YYYY-MM-DD HH:MM:SS' in UTC for MySQL TIMESTAMP columns. */
|
|
228
|
+
function epochMsToUtcDatetime(ms) {
|
|
203
229
|
const d = new Date(ms);
|
|
204
230
|
return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")} ${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}:${String(d.getUTCSeconds()).padStart(2, "0")}`;
|
|
205
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* MySQL adapter for {@link AtscriptDbTable}.
|
|
234
|
+
*
|
|
235
|
+
* Accepts any {@link TMysqlDriver} implementation — the actual MySQL driver
|
|
236
|
+
* is fully swappable (mysql2/promise pool, custom implementations, etc.).
|
|
237
|
+
*
|
|
238
|
+
* Usage:
|
|
239
|
+
* ```typescript
|
|
240
|
+
* import { Mysql2Driver, MysqlAdapter } from '@atscript/db-mysql'
|
|
241
|
+
* import { DbSpace } from '@atscript/db'
|
|
242
|
+
*
|
|
243
|
+
* const driver = new Mysql2Driver('mysql://root@localhost:3306/mydb')
|
|
244
|
+
* const space = new DbSpace(() => new MysqlAdapter(driver))
|
|
245
|
+
* const users = space.getTable(UsersType)
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
206
248
|
var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
207
|
-
|
|
249
|
+
supportsColumnModify = true;
|
|
250
|
+
static NATIVE_DEFAULT_FNS = new Set(["now", "increment"]);
|
|
251
|
+
_engine = "InnoDB";
|
|
252
|
+
_charset = "utf8mb4";
|
|
253
|
+
_collation = "utf8mb4_unicode_ci";
|
|
254
|
+
_autoIncrementStart;
|
|
255
|
+
_incrementFields = /* @__PURE__ */ new Set();
|
|
256
|
+
_onUpdateFields = /* @__PURE__ */ new Map();
|
|
257
|
+
/** Whether the connected MySQL instance supports native VECTOR type (MySQL 9.0+). */
|
|
258
|
+
_supportsVector;
|
|
259
|
+
/** Vector fields: physical field name → { dimensions, similarity, indexName }. */
|
|
260
|
+
_vectorFields = /* @__PURE__ */ new Map();
|
|
261
|
+
/** Default similarity thresholds per vector field (from @db.search.vector.threshold). */
|
|
262
|
+
_vectorThresholds = /* @__PURE__ */ new Map();
|
|
263
|
+
/** Schema name for INFORMATION_SCHEMA queries (null falls through to DATABASE()). */
|
|
264
|
+
get _schema() {
|
|
208
265
|
return this._table.schema ?? null;
|
|
209
266
|
}
|
|
267
|
+
constructor(driver) {
|
|
268
|
+
super();
|
|
269
|
+
this.driver = driver;
|
|
270
|
+
}
|
|
210
271
|
async _beginTransaction() {
|
|
211
272
|
const conn = await this.driver.getConnection();
|
|
212
273
|
await conn.exec("START TRANSACTION");
|
|
@@ -234,11 +295,12 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
234
295
|
/**
|
|
235
296
|
* Returns the active executor: dedicated connection if inside a transaction,
|
|
236
297
|
* otherwise the pool-based driver.
|
|
237
|
-
*/
|
|
238
|
-
|
|
239
|
-
return
|
|
298
|
+
*/
|
|
299
|
+
_exec() {
|
|
300
|
+
return this._getTransactionState() ?? this.driver;
|
|
240
301
|
}
|
|
241
|
-
/** MySQL InnoDB enforces FK constraints natively. */
|
|
302
|
+
/** MySQL InnoDB enforces FK constraints natively. */
|
|
303
|
+
supportsNativeForeignKeys() {
|
|
242
304
|
return true;
|
|
243
305
|
}
|
|
244
306
|
prepareId(id, _fieldType) {
|
|
@@ -250,8 +312,8 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
250
312
|
nativeDefaultFns() {
|
|
251
313
|
return MysqlAdapter.NATIVE_DEFAULT_FNS;
|
|
252
314
|
}
|
|
253
|
-
onBeforeFlatten(
|
|
254
|
-
const meta =
|
|
315
|
+
onBeforeFlatten(_type) {
|
|
316
|
+
const meta = _type.metadata;
|
|
255
317
|
const engine = meta.get("db.mysql.engine");
|
|
256
318
|
if (engine) this._engine = engine;
|
|
257
319
|
const charset = meta.get("db.mysql.charset");
|
|
@@ -276,7 +338,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
276
338
|
indexName
|
|
277
339
|
});
|
|
278
340
|
const threshold = metadata.get("db.search.vector.threshold");
|
|
279
|
-
if (threshold !==
|
|
341
|
+
if (threshold !== void 0) this._vectorThresholds.set(indexName, threshold);
|
|
280
342
|
}
|
|
281
343
|
}
|
|
282
344
|
getDesiredTableOptions() {
|
|
@@ -320,18 +382,15 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
320
382
|
const tableName = this.resolveTableName();
|
|
321
383
|
const clauses = [];
|
|
322
384
|
for (const change of changes) switch (change.key) {
|
|
323
|
-
case "engine":
|
|
385
|
+
case "engine":
|
|
324
386
|
clauses.push(`ENGINE = ${change.newValue}`);
|
|
325
387
|
break;
|
|
326
|
-
|
|
327
|
-
case "charset": {
|
|
388
|
+
case "charset":
|
|
328
389
|
clauses.push(`CHARACTER SET = ${change.newValue}`);
|
|
329
390
|
break;
|
|
330
|
-
|
|
331
|
-
case "collation": {
|
|
391
|
+
case "collation":
|
|
332
392
|
clauses.push(`COLLATE = ${change.newValue}`);
|
|
333
393
|
break;
|
|
334
|
-
}
|
|
335
394
|
}
|
|
336
395
|
if (clauses.length > 0) {
|
|
337
396
|
const ddl = `ALTER TABLE ${quoteTableName(tableName)} ${clauses.join(", ")}`;
|
|
@@ -343,12 +402,12 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
343
402
|
* Returns a value formatter for TIMESTAMP-mapped fields.
|
|
344
403
|
* Number fields with @db.default.now map to MySQL TIMESTAMP — the formatter
|
|
345
404
|
* converts epoch ms to a UTC datetime string for the wire protocol.
|
|
346
|
-
*/
|
|
405
|
+
*/
|
|
406
|
+
formatValue(field) {
|
|
347
407
|
if (field.designType === "number" && field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return {
|
|
348
408
|
toStorage: (value) => typeof value === "number" ? epochMsToUtcDatetime(value) : value,
|
|
349
409
|
fromStorage: utcDatetimeToEpochMs
|
|
350
410
|
};
|
|
351
|
-
return undefined;
|
|
352
411
|
}
|
|
353
412
|
/**
|
|
354
413
|
* Wraps an async write operation to catch MySQL constraint errors
|
|
@@ -358,24 +417,18 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
358
417
|
* - 1062 = ER_DUP_ENTRY (unique constraint violation)
|
|
359
418
|
* - 1451 = ER_ROW_IS_REFERENCED_2 (FK violation on delete)
|
|
360
419
|
* - 1452 = ER_NO_REFERENCED_ROW_2 (FK violation on insert/update)
|
|
361
|
-
*/
|
|
420
|
+
*/
|
|
421
|
+
async _wrapConstraintError(fn) {
|
|
362
422
|
try {
|
|
363
423
|
return await fn();
|
|
364
424
|
} catch (error) {
|
|
365
425
|
if (error && typeof error === "object" && "errno" in error) {
|
|
366
426
|
const err = error;
|
|
367
|
-
if (err.errno === 1062) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
message: err.sqlMessage ?? err.message
|
|
373
|
-
}]);
|
|
374
|
-
}
|
|
375
|
-
if (err.errno === 1451 || err.errno === 1452) {
|
|
376
|
-
const errors = this._mapFkError(err.message);
|
|
377
|
-
throw new DbError("FK_VIOLATION", errors);
|
|
378
|
-
}
|
|
427
|
+
if (err.errno === 1062) throw new DbError("CONFLICT", [{
|
|
428
|
+
path: (err.message?.match(/for key '(?:\w+\.)?(\w+)'/))?.[1] ?? "",
|
|
429
|
+
message: err.sqlMessage ?? err.message
|
|
430
|
+
}]);
|
|
431
|
+
if (err.errno === 1451 || err.errno === 1452) throw new DbError("FK_VIOLATION", this._mapFkError(err.message));
|
|
379
432
|
}
|
|
380
433
|
throw error;
|
|
381
434
|
}
|
|
@@ -384,9 +437,8 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
384
437
|
const fkMatch = message.match(/FOREIGN KEY \(`(\w+)`\)/);
|
|
385
438
|
if (fkMatch) {
|
|
386
439
|
const physicalCol = fkMatch[1];
|
|
387
|
-
const field = this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol);
|
|
388
440
|
return [{
|
|
389
|
-
path:
|
|
441
|
+
path: this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol)?.path ?? physicalCol,
|
|
390
442
|
message
|
|
391
443
|
}];
|
|
392
444
|
}
|
|
@@ -453,28 +505,25 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
453
505
|
}
|
|
454
506
|
async count(query) {
|
|
455
507
|
const where = buildWhere(query.filter);
|
|
456
|
-
const
|
|
457
|
-
const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${where.sql}`;
|
|
508
|
+
const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(this.resolveTableName())} WHERE ${where.sql}`;
|
|
458
509
|
this._log(sql, where.params);
|
|
459
|
-
|
|
460
|
-
return row?.cnt ?? 0;
|
|
510
|
+
return (await this._exec().get(sql, where.params))?.cnt ?? 0;
|
|
461
511
|
}
|
|
462
512
|
async aggregate(query) {
|
|
463
513
|
const where = buildWhere(query.filter);
|
|
464
514
|
const tableName = this.resolveTableName();
|
|
465
515
|
if (query.controls.$count) {
|
|
466
|
-
const { sql
|
|
467
|
-
this._log(sql
|
|
468
|
-
|
|
469
|
-
return [{ count: row?.count ?? 0 }];
|
|
516
|
+
const { sql, params } = buildAggregateCount$1(tableName, where, query.controls);
|
|
517
|
+
this._log(sql, params);
|
|
518
|
+
return [{ count: (await this._exec().get(sql, params))?.count ?? 0 }];
|
|
470
519
|
}
|
|
471
520
|
const { sql, params } = buildAggregateSelect$1(tableName, where, query.controls);
|
|
472
521
|
this._log(sql, params);
|
|
473
522
|
return this._exec().all(sql, params);
|
|
474
523
|
}
|
|
475
|
-
async updateOne(filter, data) {
|
|
524
|
+
async updateOne(filter, data, ops) {
|
|
476
525
|
const where = buildWhere(filter);
|
|
477
|
-
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, 1);
|
|
526
|
+
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, 1, ops);
|
|
478
527
|
this._log(sql, params);
|
|
479
528
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
480
529
|
return {
|
|
@@ -482,9 +531,9 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
482
531
|
modifiedCount: result.changedRows
|
|
483
532
|
};
|
|
484
533
|
}
|
|
485
|
-
async updateMany(filter, data) {
|
|
534
|
+
async updateMany(filter, data, ops) {
|
|
486
535
|
const where = buildWhere(filter);
|
|
487
|
-
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where);
|
|
536
|
+
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, void 0, ops);
|
|
488
537
|
this._log(sql, params);
|
|
489
538
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
490
539
|
return {
|
|
@@ -516,18 +565,16 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
516
565
|
const where = buildWhere(filter);
|
|
517
566
|
const { sql, params } = buildDelete$1(this.resolveTableName(), where, 1);
|
|
518
567
|
this._log(sql, params);
|
|
519
|
-
|
|
520
|
-
return { deletedCount: result.affectedRows };
|
|
568
|
+
return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
|
|
521
569
|
}
|
|
522
570
|
async deleteMany(filter) {
|
|
523
571
|
const where = buildWhere(filter);
|
|
524
572
|
const { sql, params } = buildDelete$1(this.resolveTableName(), where);
|
|
525
573
|
this._log(sql, params);
|
|
526
|
-
|
|
527
|
-
return { deletedCount: result.affectedRows };
|
|
574
|
+
return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
|
|
528
575
|
}
|
|
529
576
|
async ensureTable() {
|
|
530
|
-
if (this._supportsVector ===
|
|
577
|
+
if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
|
|
531
578
|
if (this._table instanceof AtscriptDbView) return this._ensureView();
|
|
532
579
|
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
|
|
533
580
|
engine: this._engine,
|
|
@@ -551,11 +598,10 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
551
598
|
}
|
|
552
599
|
async getExistingColumnsForTable(tableName) {
|
|
553
600
|
const schema = this._schema;
|
|
554
|
-
|
|
601
|
+
return (await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT
|
|
555
602
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
556
603
|
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
557
|
-
ORDER BY ORDINAL_POSITION`, [tableName, schema])
|
|
558
|
-
return rows.map((r) => ({
|
|
604
|
+
ORDER BY ORDINAL_POSITION`, [tableName, schema])).map((r) => ({
|
|
559
605
|
name: r.COLUMN_NAME,
|
|
560
606
|
type: r.COLUMN_TYPE.toUpperCase(),
|
|
561
607
|
notnull: r.IS_NULLABLE === "NO",
|
|
@@ -578,7 +624,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
578
624
|
let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
|
|
579
625
|
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
580
626
|
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
581
|
-
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
627
|
+
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
582
628
|
if (field.collate) {
|
|
583
629
|
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
584
630
|
ddl += ` COLLATE ${nativeCollate ?? collationToMysql(field.collate)}`;
|
|
@@ -606,8 +652,8 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
|
|
|
606
652
|
let ddl = `ALTER TABLE ${quoteTableName(tableName)} MODIFY COLUMN ${qi(field.physicalName)} ${sqlType}`;
|
|
607
653
|
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
608
654
|
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
609
|
-
else if (field.defaultValue?.kind === "fn") ddl += ` DEFAULT ${field.defaultValue.fn === "now" ? "CURRENT_TIMESTAMP" : field.defaultValue.fn === "uuid" ? "(UUID())" : `(${field.defaultValue.fn}())`}`;
|
|
610
|
-
else ddl += " DEFAULT NULL";
|
|
655
|
+
else if (field.defaultValue?.kind === "fn") ddl += ` DEFAULT ${field.defaultValue.fn === "now" ? "CURRENT_TIMESTAMP" : field.defaultValue.fn === "uuid" ? "(UUID())" : `(${field.defaultValue.fn}())`}`;
|
|
656
|
+
else ddl += " DEFAULT NULL";
|
|
611
657
|
this._log(ddl);
|
|
612
658
|
await this._exec().exec(ddl);
|
|
613
659
|
}
|
|
@@ -717,10 +763,7 @@ else ddl += " DEFAULT NULL";
|
|
|
717
763
|
const fulltext = index.type === "fulltext" ? "FULLTEXT " : "";
|
|
718
764
|
const isFulltext = index.type === "fulltext";
|
|
719
765
|
const cols = index.fields.map((f) => {
|
|
720
|
-
|
|
721
|
-
const prefix = !isFulltext && stringFields.has(f.name) ? "(255)" : "";
|
|
722
|
-
const order = isFulltext ? "" : ` ${f.sort === "desc" ? "DESC" : "ASC"}`;
|
|
723
|
-
return `${col}${prefix}${order}`;
|
|
766
|
+
return `${qi(f.name)}${!isFulltext && stringFields.has(f.name) ? "(255)" : ""}${isFulltext ? "" : ` ${f.sort === "desc" ? "DESC" : "ASC"}`}`;
|
|
724
767
|
}).join(", ");
|
|
725
768
|
const sql = `CREATE ${fulltext}${unique}INDEX ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${cols})`;
|
|
726
769
|
this._log(sql);
|
|
@@ -735,19 +778,19 @@ else ddl += " DEFAULT NULL";
|
|
|
735
778
|
}
|
|
736
779
|
async syncForeignKeys() {
|
|
737
780
|
const existingByName = await this._getExistingFkConstraints();
|
|
738
|
-
const desiredFkKeys = new Set();
|
|
739
|
-
for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].
|
|
781
|
+
const desiredFkKeys = /* @__PURE__ */ new Set();
|
|
782
|
+
for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].toSorted().join(","));
|
|
740
783
|
for (const [constraintName, columns] of existingByName) {
|
|
741
|
-
const key = columns.
|
|
784
|
+
const key = columns.toSorted().join(",");
|
|
742
785
|
if (!desiredFkKeys.has(key)) {
|
|
743
786
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP FOREIGN KEY ${qi(constraintName)}`;
|
|
744
787
|
this._log(ddl);
|
|
745
788
|
await this._exec().exec(ddl);
|
|
746
789
|
}
|
|
747
790
|
}
|
|
748
|
-
const existingKeys = new Set([...existingByName.values()].map((cols) => cols.
|
|
791
|
+
const existingKeys = new Set([...existingByName.values()].map((cols) => cols.toSorted().join(",")));
|
|
749
792
|
for (const fk of this._table.foreignKeys.values()) {
|
|
750
|
-
const key = [...fk.fields].
|
|
793
|
+
const key = [...fk.fields].toSorted().join(",");
|
|
751
794
|
if (!existingKeys.has(key)) {
|
|
752
795
|
const localCols = fk.fields.map((f) => qi(f)).join(", ");
|
|
753
796
|
const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
|
|
@@ -764,7 +807,7 @@ else ddl += " DEFAULT NULL";
|
|
|
764
807
|
const keySet = new Set(fkFieldKeys);
|
|
765
808
|
const existingByName = await this._getExistingFkConstraints();
|
|
766
809
|
for (const [constraintName, cols] of existingByName) {
|
|
767
|
-
const key = cols.
|
|
810
|
+
const key = cols.toSorted().join(",");
|
|
768
811
|
if (keySet.has(key)) {
|
|
769
812
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP FOREIGN KEY ${qi(constraintName)}`;
|
|
770
813
|
this._log(ddl);
|
|
@@ -772,12 +815,13 @@ else ddl += " DEFAULT NULL";
|
|
|
772
815
|
}
|
|
773
816
|
}
|
|
774
817
|
}
|
|
775
|
-
/** Queries INFORMATION_SCHEMA for existing FK constraints, grouped by constraint name → column names. */
|
|
818
|
+
/** Queries INFORMATION_SCHEMA for existing FK constraints, grouped by constraint name → column names. */
|
|
819
|
+
async _getExistingFkConstraints() {
|
|
776
820
|
const rows = await this._exec().all(`SELECT kcu.CONSTRAINT_NAME, kcu.COLUMN_NAME
|
|
777
821
|
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
|
778
822
|
WHERE kcu.TABLE_NAME = ? AND kcu.TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
779
823
|
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL`, [this._table.tableName, this._schema]);
|
|
780
|
-
const byName = new Map();
|
|
824
|
+
const byName = /* @__PURE__ */ new Map();
|
|
781
825
|
for (const row of rows) {
|
|
782
826
|
let cols = byName.get(row.CONSTRAINT_NAME);
|
|
783
827
|
if (!cols) {
|
|
@@ -819,8 +863,7 @@ else ddl += " DEFAULT NULL";
|
|
|
819
863
|
const countPromise = (async () => {
|
|
820
864
|
const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${combinedWhere.sql}`;
|
|
821
865
|
this._log(sql, combinedWhere.params);
|
|
822
|
-
|
|
823
|
-
return row?.cnt ?? 0;
|
|
866
|
+
return (await this._exec().get(sql, combinedWhere.params))?.cnt ?? 0;
|
|
824
867
|
})();
|
|
825
868
|
const [data, count] = await Promise.all([selectPromise, countPromise]);
|
|
826
869
|
return {
|
|
@@ -843,14 +886,14 @@ else ddl += " DEFAULT NULL";
|
|
|
843
886
|
for (const index of this._table.indexes.values()) if (index.type === "fulltext") {
|
|
844
887
|
if (!indexName || index.key === indexName) return index;
|
|
845
888
|
}
|
|
846
|
-
return undefined;
|
|
847
889
|
}
|
|
848
890
|
/**
|
|
849
891
|
* Detects native VECTOR type support by inspecting the server version.
|
|
850
892
|
* MySQL 9.0+ supports the VECTOR column type natively.
|
|
851
893
|
* Caches the result for the lifetime of this adapter instance.
|
|
852
|
-
*/
|
|
853
|
-
|
|
894
|
+
*/
|
|
895
|
+
async _detectVectorSupport() {
|
|
896
|
+
if (this._supportsVector !== void 0) return this._supportsVector;
|
|
854
897
|
try {
|
|
855
898
|
const row = await this.driver.get("SELECT VERSION() as v", []);
|
|
856
899
|
if (row?.v) {
|
|
@@ -885,7 +928,8 @@ else ddl += " DEFAULT NULL";
|
|
|
885
928
|
count: countRow?.cnt ?? 0
|
|
886
929
|
};
|
|
887
930
|
}
|
|
888
|
-
/** Resolves vector field and computes shared context for vector search SQL builders. */
|
|
931
|
+
/** Resolves vector field and computes shared context for vector search SQL builders. */
|
|
932
|
+
_prepareVectorSearch(vector, query, indexName) {
|
|
889
933
|
let field;
|
|
890
934
|
let vec;
|
|
891
935
|
if (indexName) {
|
|
@@ -923,13 +967,13 @@ else ddl += " DEFAULT NULL";
|
|
|
923
967
|
const inner = `SELECT *, ${ctx.distanceFn}(${qi(ctx.field)}, STRING_TO_VECTOR(?)) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${ctx.where.sql}`;
|
|
924
968
|
const params = [ctx.vectorStr, ...ctx.where.params];
|
|
925
969
|
let sql = `SELECT * FROM (${inner}) _v`;
|
|
926
|
-
if (ctx.threshold !==
|
|
970
|
+
if (ctx.threshold !== void 0) {
|
|
927
971
|
sql += ` WHERE _distance <= ?`;
|
|
928
972
|
params.push(2 * (1 - ctx.threshold));
|
|
929
973
|
}
|
|
930
974
|
sql += ` ORDER BY _distance ASC`;
|
|
931
|
-
if (ctx.controls.$skip) sql += ` LIMIT ${ctx.controls.$limit || 1e3} OFFSET ${ctx.controls.$skip}`;
|
|
932
|
-
else sql += ` LIMIT ${ctx.controls.$limit || 20}`;
|
|
975
|
+
if (ctx.controls.$skip) sql += ` LIMIT ${Number(ctx.controls.$limit) || 1e3} OFFSET ${Number(ctx.controls.$skip)}`;
|
|
976
|
+
else sql += ` LIMIT ${Number(ctx.controls.$limit) || 20}`;
|
|
933
977
|
return {
|
|
934
978
|
sql,
|
|
935
979
|
params
|
|
@@ -940,7 +984,7 @@ else sql += ` LIMIT ${ctx.controls.$limit || 20}`;
|
|
|
940
984
|
const inner = `SELECT ${ctx.distanceFn}(${qi(ctx.field)}, STRING_TO_VECTOR(?)) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${ctx.where.sql}`;
|
|
941
985
|
const params = [ctx.vectorStr, ...ctx.where.params];
|
|
942
986
|
let sql = `SELECT COUNT(*) AS cnt FROM (${inner}) _v`;
|
|
943
|
-
if (ctx.threshold !==
|
|
987
|
+
if (ctx.threshold !== void 0) {
|
|
944
988
|
sql += ` WHERE _distance <= ?`;
|
|
945
989
|
params.push(2 * (1 - ctx.threshold));
|
|
946
990
|
}
|
|
@@ -949,16 +993,13 @@ else sql += ` LIMIT ${ctx.controls.$limit || 20}`;
|
|
|
949
993
|
params
|
|
950
994
|
};
|
|
951
995
|
}
|
|
952
|
-
/** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
|
|
996
|
+
/** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
|
|
997
|
+
_resolveVectorThreshold(controls, indexName) {
|
|
953
998
|
const queryThreshold = controls.$threshold;
|
|
954
|
-
if (queryThreshold !==
|
|
999
|
+
if (queryThreshold !== void 0) return queryThreshold;
|
|
955
1000
|
return this._vectorThresholds.get(indexName);
|
|
956
1001
|
}
|
|
957
|
-
constructor(driver) {
|
|
958
|
-
super(), _define_property$1(this, "driver", void 0), _define_property$1(this, "supportsColumnModify", void 0), _define_property$1(this, "_engine", void 0), _define_property$1(this, "_charset", void 0), _define_property$1(this, "_collation", void 0), _define_property$1(this, "_autoIncrementStart", void 0), _define_property$1(this, "_incrementFields", void 0), _define_property$1(this, "_onUpdateFields", void 0), _define_property$1(this, "_supportsVector", void 0), _define_property$1(this, "_vectorFields", void 0), _define_property$1(this, "_vectorThresholds", void 0), this.driver = driver, this.supportsColumnModify = true, this._engine = "InnoDB", this._charset = "utf8mb4", this._collation = "utf8mb4_unicode_ci", this._incrementFields = new Set(), this._onUpdateFields = new Map(), this._vectorFields = new Map(), this._vectorThresholds = new Map();
|
|
959
|
-
}
|
|
960
1002
|
};
|
|
961
|
-
_define_property$1(MysqlAdapter, "NATIVE_DEFAULT_FNS", new Set(["now", "increment"]));
|
|
962
1003
|
/**
|
|
963
1004
|
* Normalizes MySQL INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT values
|
|
964
1005
|
* to match the format produced by `serializeDefaultValue()`.
|
|
@@ -967,47 +1008,41 @@ _define_property$1(MysqlAdapter, "NATIVE_DEFAULT_FNS", new Set(["now", "incremen
|
|
|
967
1008
|
* `uuid()`), but the diff engine compares against serialized form (`fn:now`,
|
|
968
1009
|
* `fn:uuid`). Without normalization, every table with function defaults
|
|
969
1010
|
* produces phantom ALTER diffs on re-plan.
|
|
970
|
-
*/
|
|
971
|
-
|
|
1011
|
+
*/
|
|
1012
|
+
function normalizeMysqlDefault(value) {
|
|
1013
|
+
if (value === null) return;
|
|
972
1014
|
const lower = value.toLowerCase();
|
|
973
1015
|
if (lower === "current_timestamp" || lower === "current_timestamp()") return "fn:now";
|
|
974
1016
|
if (lower === "uuid()") return "fn:uuid";
|
|
975
1017
|
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1).replace(/''/g, "'");
|
|
976
1018
|
return value;
|
|
977
1019
|
}
|
|
978
|
-
/** Maps generic similarity metric to MySQL 9+ distance function name. */
|
|
1020
|
+
/** Maps generic similarity metric to MySQL 9+ distance function name. */
|
|
1021
|
+
function similarityToMysqlFn(similarity) {
|
|
979
1022
|
switch (similarity) {
|
|
980
1023
|
case "euclidean": return "VEC_DISTANCE_EUCLIDEAN";
|
|
981
1024
|
case "dotProduct": return "VEC_DISTANCE_DOT";
|
|
982
1025
|
default: return "VEC_DISTANCE_COSINE";
|
|
983
1026
|
}
|
|
984
1027
|
}
|
|
985
|
-
/** Formats a number[] vector as MySQL's STRING_TO_VECTOR input: '[1.0, 2.0, ...]'. */
|
|
1028
|
+
/** Formats a number[] vector as MySQL's STRING_TO_VECTOR input: '[1.0, 2.0, ...]'. */
|
|
1029
|
+
function vectorToString(vector) {
|
|
986
1030
|
return `[${vector.join(",")}]`;
|
|
987
1031
|
}
|
|
988
|
-
|
|
989
1032
|
//#endregion
|
|
990
|
-
//#region
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
value,
|
|
994
|
-
enumerable: true,
|
|
995
|
-
configurable: true,
|
|
996
|
-
writable: true
|
|
997
|
-
});
|
|
998
|
-
else obj[key] = value;
|
|
999
|
-
return obj;
|
|
1000
|
-
}
|
|
1001
|
-
/** mysql2 rejects `undefined` in bind arrays — coerce to `null`. */ function sanitizeParams(params) {
|
|
1033
|
+
//#region src/mysql2-driver.ts
|
|
1034
|
+
/** mysql2 rejects `undefined` in bind arrays — coerce to `null`. */
|
|
1035
|
+
function sanitizeParams(params) {
|
|
1002
1036
|
if (!params) return [];
|
|
1003
|
-
return params.map((v) => v ===
|
|
1037
|
+
return params.map((v) => v === void 0 ? null : v);
|
|
1004
1038
|
}
|
|
1005
1039
|
/**
|
|
1006
1040
|
* Custom type-casting for mysql2 result columns to maintain cross-adapter consistency.
|
|
1007
1041
|
*
|
|
1008
1042
|
* - TIMESTAMP/DATETIME → epoch milliseconds (number) instead of Date objects
|
|
1009
1043
|
* - DECIMAL/NEWDECIMAL → number instead of string
|
|
1010
|
-
*/
|
|
1044
|
+
*/
|
|
1045
|
+
function atscriptTypeCast(field, next) {
|
|
1011
1046
|
if (field.type === "TIMESTAMP" || field.type === "DATETIME") {
|
|
1012
1047
|
const str = field.string();
|
|
1013
1048
|
if (str === null) return null;
|
|
@@ -1019,13 +1054,66 @@ else obj[key] = value;
|
|
|
1019
1054
|
}
|
|
1020
1055
|
return next();
|
|
1021
1056
|
}
|
|
1057
|
+
/**
|
|
1058
|
+
* {@link TMysqlDriver} implementation backed by `mysql2/promise`.
|
|
1059
|
+
*
|
|
1060
|
+
* Accepts a connection URI string, a `PoolOptions` object, or a pre-created
|
|
1061
|
+
* `Pool` instance from `mysql2/promise`.
|
|
1062
|
+
*
|
|
1063
|
+
* ```typescript
|
|
1064
|
+
* import { Mysql2Driver } from '@atscript/db-mysql'
|
|
1065
|
+
*
|
|
1066
|
+
* // Connection URI
|
|
1067
|
+
* const driver = new Mysql2Driver('mysql://root:pass@localhost:3306/mydb')
|
|
1068
|
+
*
|
|
1069
|
+
* // Pool options
|
|
1070
|
+
* const driver = new Mysql2Driver({
|
|
1071
|
+
* host: 'localhost',
|
|
1072
|
+
* user: 'root',
|
|
1073
|
+
* database: 'mydb',
|
|
1074
|
+
* waitForConnections: true,
|
|
1075
|
+
* connectionLimit: 10,
|
|
1076
|
+
* })
|
|
1077
|
+
*
|
|
1078
|
+
* // Pre-created pool
|
|
1079
|
+
* import mysql from 'mysql2/promise'
|
|
1080
|
+
* const pool = mysql.createPool({ host: 'localhost', database: 'mydb' })
|
|
1081
|
+
* const driver = new Mysql2Driver(pool)
|
|
1082
|
+
* ```
|
|
1083
|
+
*
|
|
1084
|
+
* Requires `mysql2` to be installed:
|
|
1085
|
+
* ```bash
|
|
1086
|
+
* pnpm add mysql2
|
|
1087
|
+
* ```
|
|
1088
|
+
*/
|
|
1022
1089
|
var Mysql2Driver = class {
|
|
1090
|
+
pool;
|
|
1091
|
+
poolInit;
|
|
1092
|
+
constructor(poolOrConfig) {
|
|
1093
|
+
if (typeof poolOrConfig === "object" && "execute" in poolOrConfig) this.pool = poolOrConfig;
|
|
1094
|
+
else this.poolInit = import("mysql2/promise").then((mysql) => {
|
|
1095
|
+
if (typeof poolOrConfig === "string") this.pool = mysql.createPool({
|
|
1096
|
+
uri: poolOrConfig,
|
|
1097
|
+
timezone: "+00:00",
|
|
1098
|
+
supportBigNumbers: true,
|
|
1099
|
+
bigNumberStrings: false,
|
|
1100
|
+
typeCast: atscriptTypeCast
|
|
1101
|
+
});
|
|
1102
|
+
else this.pool = mysql.createPool({
|
|
1103
|
+
...poolOrConfig,
|
|
1104
|
+
timezone: "+00:00",
|
|
1105
|
+
supportBigNumbers: true,
|
|
1106
|
+
bigNumberStrings: false,
|
|
1107
|
+
typeCast: atscriptTypeCast
|
|
1108
|
+
});
|
|
1109
|
+
return this.pool;
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1023
1112
|
getPool() {
|
|
1024
1113
|
return this.pool || this.poolInit;
|
|
1025
1114
|
}
|
|
1026
1115
|
async run(sql, params) {
|
|
1027
|
-
const
|
|
1028
|
-
const [result] = await pool.query(sql, sanitizeParams(params));
|
|
1116
|
+
const [result] = await (await this.getPool()).query(sql, sanitizeParams(params));
|
|
1029
1117
|
const header = result;
|
|
1030
1118
|
return {
|
|
1031
1119
|
affectedRows: header.affectedRows ?? 0,
|
|
@@ -1034,22 +1122,18 @@ var Mysql2Driver = class {
|
|
|
1034
1122
|
};
|
|
1035
1123
|
}
|
|
1036
1124
|
async all(sql, params) {
|
|
1037
|
-
const
|
|
1038
|
-
const [rows] = await pool.query(sql, sanitizeParams(params));
|
|
1125
|
+
const [rows] = await (await this.getPool()).query(sql, sanitizeParams(params));
|
|
1039
1126
|
return rows;
|
|
1040
1127
|
}
|
|
1041
1128
|
async get(sql, params) {
|
|
1042
|
-
const
|
|
1043
|
-
const [rows] = await pool.query(sql, sanitizeParams(params));
|
|
1129
|
+
const [rows] = await (await this.getPool()).query(sql, sanitizeParams(params));
|
|
1044
1130
|
return rows[0] ?? null;
|
|
1045
1131
|
}
|
|
1046
1132
|
async exec(sql) {
|
|
1047
|
-
|
|
1048
|
-
await pool.query(sql);
|
|
1133
|
+
await (await this.getPool()).query(sql);
|
|
1049
1134
|
}
|
|
1050
1135
|
async getConnection() {
|
|
1051
|
-
const
|
|
1052
|
-
const conn = await pool.getConnection();
|
|
1136
|
+
const conn = await (await this.getPool()).getConnection();
|
|
1053
1137
|
return {
|
|
1054
1138
|
async run(sql, params) {
|
|
1055
1139
|
const [result] = await conn.query(sql, sanitizeParams(params));
|
|
@@ -1077,119 +1161,18 @@ var Mysql2Driver = class {
|
|
|
1077
1161
|
};
|
|
1078
1162
|
}
|
|
1079
1163
|
async close() {
|
|
1080
|
-
|
|
1081
|
-
await pool.end();
|
|
1082
|
-
}
|
|
1083
|
-
constructor(poolOrConfig) {
|
|
1084
|
-
_define_property(this, "pool", void 0);
|
|
1085
|
-
_define_property(this, "poolInit", void 0);
|
|
1086
|
-
if (typeof poolOrConfig === "object" && "execute" in poolOrConfig) this.pool = poolOrConfig;
|
|
1087
|
-
else this.poolInit = import("mysql2/promise").then((mysql) => {
|
|
1088
|
-
if (typeof poolOrConfig === "string") this.pool = mysql.createPool({
|
|
1089
|
-
uri: poolOrConfig,
|
|
1090
|
-
timezone: "+00:00",
|
|
1091
|
-
supportBigNumbers: true,
|
|
1092
|
-
bigNumberStrings: false,
|
|
1093
|
-
typeCast: atscriptTypeCast
|
|
1094
|
-
});
|
|
1095
|
-
else this.pool = mysql.createPool({
|
|
1096
|
-
...poolOrConfig,
|
|
1097
|
-
timezone: "+00:00",
|
|
1098
|
-
supportBigNumbers: true,
|
|
1099
|
-
bigNumberStrings: false,
|
|
1100
|
-
typeCast: atscriptTypeCast
|
|
1101
|
-
});
|
|
1102
|
-
return this.pool;
|
|
1103
|
-
});
|
|
1164
|
+
await (await this.getPool()).end();
|
|
1104
1165
|
}
|
|
1105
1166
|
};
|
|
1106
|
-
|
|
1107
1167
|
//#endregion
|
|
1108
|
-
//#region
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
type: "string",
|
|
1117
|
-
values: [
|
|
1118
|
-
"InnoDB",
|
|
1119
|
-
"MyISAM",
|
|
1120
|
-
"MEMORY",
|
|
1121
|
-
"CSV",
|
|
1122
|
-
"ARCHIVE"
|
|
1123
|
-
],
|
|
1124
|
-
description: "MySQL storage engine name."
|
|
1125
|
-
}
|
|
1126
|
-
}),
|
|
1127
|
-
charset: new AnnotationSpec({
|
|
1128
|
-
description: "Specifies the character set for the table or column.\n\n**Default:** `\"utf8mb4\"`\n\n```atscript\n@db.mysql.charset \"latin1\"\nexport interface Legacy { ... }\n```",
|
|
1129
|
-
nodeType: ["interface", "prop"],
|
|
1130
|
-
multiple: false,
|
|
1131
|
-
argument: {
|
|
1132
|
-
name: "charset",
|
|
1133
|
-
type: "string",
|
|
1134
|
-
values: [
|
|
1135
|
-
"utf8mb4",
|
|
1136
|
-
"utf8",
|
|
1137
|
-
"latin1",
|
|
1138
|
-
"ascii",
|
|
1139
|
-
"binary"
|
|
1140
|
-
],
|
|
1141
|
-
description: "MySQL character set name."
|
|
1142
|
-
}
|
|
1143
|
-
}),
|
|
1144
|
-
collate: new AnnotationSpec({
|
|
1145
|
-
description: "Specifies a native MySQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.mysql.collate \"utf8mb4_turkish_ci\"\nname: string\n```",
|
|
1146
|
-
nodeType: ["interface", "prop"],
|
|
1147
|
-
multiple: false,
|
|
1148
|
-
argument: {
|
|
1149
|
-
name: "collation",
|
|
1150
|
-
type: "string",
|
|
1151
|
-
description: "Native MySQL collation name (e.g., \"utf8mb4_turkish_ci\")."
|
|
1152
|
-
}
|
|
1153
|
-
}),
|
|
1154
|
-
unsigned: new AnnotationSpec({
|
|
1155
|
-
description: "Adds the UNSIGNED modifier to an integer column.\n\n```atscript\n@db.mysql.unsigned\nage: number.int\n```",
|
|
1156
|
-
nodeType: ["prop"],
|
|
1157
|
-
multiple: false
|
|
1158
|
-
}),
|
|
1159
|
-
type: new AnnotationSpec({
|
|
1160
|
-
description: "Overrides the native MySQL column type.\n\n```atscript\n@db.mysql.type \"MEDIUMTEXT\"\nbio: string\n```",
|
|
1161
|
-
nodeType: ["prop"],
|
|
1162
|
-
multiple: false,
|
|
1163
|
-
argument: {
|
|
1164
|
-
name: "type",
|
|
1165
|
-
type: "string",
|
|
1166
|
-
description: "Native MySQL column type (e.g., \"MEDIUMTEXT\", \"TINYTEXT\")."
|
|
1167
|
-
}
|
|
1168
|
-
}),
|
|
1169
|
-
onUpdate: new AnnotationSpec({
|
|
1170
|
-
description: "Sets the MySQL ON UPDATE clause for a column.\n\n```atscript\n@db.mysql.onUpdate \"CURRENT_TIMESTAMP\"\nupdatedAt: number.timestamp\n```",
|
|
1171
|
-
nodeType: ["prop"],
|
|
1172
|
-
multiple: false,
|
|
1173
|
-
argument: {
|
|
1174
|
-
name: "expression",
|
|
1175
|
-
type: "string",
|
|
1176
|
-
values: ["CURRENT_TIMESTAMP"],
|
|
1177
|
-
description: "Expression to evaluate on row update."
|
|
1178
|
-
}
|
|
1179
|
-
})
|
|
1180
|
-
};
|
|
1181
|
-
|
|
1182
|
-
//#endregion
|
|
1183
|
-
//#region packages/db-mysql/src/plugin/index.ts
|
|
1184
|
-
const MysqlPlugin = () => ({
|
|
1185
|
-
name: "mysql",
|
|
1186
|
-
config() {
|
|
1187
|
-
return { annotations: { db: { mysql: annotations } } };
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
//#endregion
|
|
1192
|
-
//#region packages/db-mysql/src/index.ts
|
|
1168
|
+
//#region src/index.ts
|
|
1169
|
+
/**
|
|
1170
|
+
* Creates a {@link DbSpace} backed by a MySQL connection pool.
|
|
1171
|
+
*
|
|
1172
|
+
* @param uri - MySQL connection URI (e.g., `mysql://root@localhost:3306/mydb`)
|
|
1173
|
+
* @param options - Additional pool options passed to mysql2.
|
|
1174
|
+
* @returns A `DbSpace` that creates `MysqlAdapter` instances per table.
|
|
1175
|
+
*/
|
|
1193
1176
|
function createAdapter(uri, options) {
|
|
1194
1177
|
const driver = new Mysql2Driver({
|
|
1195
1178
|
uri,
|
|
@@ -1197,6 +1180,5 @@ function createAdapter(uri, options) {
|
|
|
1197
1180
|
});
|
|
1198
1181
|
return new DbSpace(() => new MysqlAdapter(driver));
|
|
1199
1182
|
}
|
|
1200
|
-
|
|
1201
1183
|
//#endregion
|
|
1202
|
-
export { Mysql2Driver, MysqlAdapter, MysqlPlugin, buildWhere, createAdapter };
|
|
1184
|
+
export { Mysql2Driver, MysqlAdapter, MysqlPlugin, buildWhere, createAdapter };
|