@atscript/db-mysql 0.1.38

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/index.cjs ADDED
@@ -0,0 +1,1230 @@
1
+ "use strict";
2
+ //#region rolldown:runtime
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+
24
+ //#endregion
25
+ const __atscript_db = __toESM(require("@atscript/db"));
26
+ const __atscript_db_sql_tools = __toESM(require("@atscript/db-sql-tools"));
27
+ const __atscript_core = __toESM(require("@atscript/core"));
28
+
29
+ //#region packages/db-mysql/src/sql-builder.ts
30
+ function esc(name) {
31
+ return name.replace(/`/g, "``");
32
+ }
33
+ function qi(name) {
34
+ return `\`${esc(name)}\``;
35
+ }
36
+ function quoteTableName(name) {
37
+ const dot = name.indexOf(".");
38
+ if (dot >= 0) return `${qi(name.slice(0, dot))}.${qi(name.slice(dot + 1))}`;
39
+ return qi(name);
40
+ }
41
+ const mysqlDialect = {
42
+ quoteIdentifier(name) {
43
+ return qi(name);
44
+ },
45
+ quoteTable(name) {
46
+ return quoteTableName(name);
47
+ },
48
+ unlimitedLimit: "18446744073709551615",
49
+ toValue: __atscript_db_sql_tools.toSqlValue,
50
+ toParam(value) {
51
+ if (value === undefined) return null;
52
+ return typeof value === "boolean" ? value ? 1 : 0 : value;
53
+ },
54
+ regex(quotedCol, value) {
55
+ const pattern = value instanceof RegExp ? value.source : String(value);
56
+ return {
57
+ sql: `${quotedCol} REGEXP ?`,
58
+ params: [pattern]
59
+ };
60
+ },
61
+ createViewPrefix: "CREATE OR REPLACE VIEW"
62
+ };
63
+ function buildInsert(table, data) {
64
+ return (0, __atscript_db_sql_tools.buildInsert)(mysqlDialect, table, data);
65
+ }
66
+ function buildSelect(table, where, controls) {
67
+ return (0, __atscript_db_sql_tools.buildSelect)(mysqlDialect, table, where, controls);
68
+ }
69
+ function buildUpdate(table, data, where, limit) {
70
+ return (0, __atscript_db_sql_tools.buildUpdate)(mysqlDialect, table, data, where, limit);
71
+ }
72
+ function buildDelete(table, where, limit) {
73
+ return (0, __atscript_db_sql_tools.buildDelete)(mysqlDialect, table, where, limit);
74
+ }
75
+ function buildCreateView(viewName, plan, columns, resolveFieldRef) {
76
+ return (0, __atscript_db_sql_tools.buildCreateView)(mysqlDialect, viewName, plan, columns, resolveFieldRef);
77
+ }
78
+ function buildAggregateSelect(table, where, controls) {
79
+ return (0, __atscript_db_sql_tools.buildAggregateSelect)(mysqlDialect, table, where, controls);
80
+ }
81
+ function buildAggregateCount(table, where, controls) {
82
+ return (0, __atscript_db_sql_tools.buildAggregateCount)(mysqlDialect, table, where, controls);
83
+ }
84
+ function collationToMysql(collation) {
85
+ switch (collation) {
86
+ case "binary": return "utf8mb4_bin";
87
+ case "nocase": return "utf8mb4_general_ci";
88
+ case "unicode": return "utf8mb4_unicode_ci";
89
+ default: return "utf8mb4_unicode_ci";
90
+ }
91
+ }
92
+ /**
93
+ * Maps an Atscript field descriptor to a MySQL column type.
94
+ *
95
+ * Reads `designType`, primitive tags (via `type.type.tags`), and annotations
96
+ * from field metadata to produce the most specific MySQL type.
97
+ *
98
+ * For FK fields, delegates to the target PK's type via `field.fkTargetField`
99
+ * so the FK column type always matches the referenced column.
100
+ */
101
+ /** Maps integer primitive tags to MySQL integer types. */ function intTypeFromTags(tags, unsigned) {
102
+ if (tags?.has("int8")) return unsigned ? "TINYINT UNSIGNED" : "TINYINT";
103
+ if (tags?.has("uint8") || tags?.has("byte")) return "TINYINT UNSIGNED";
104
+ if (tags?.has("int16")) return unsigned ? "SMALLINT UNSIGNED" : "SMALLINT";
105
+ if (tags?.has("uint16") || tags?.has("port")) return "SMALLINT UNSIGNED";
106
+ if (tags?.has("int32")) return unsigned ? "INT UNSIGNED" : "INT";
107
+ if (tags?.has("uint32")) return "INT UNSIGNED";
108
+ if (tags?.has("int64")) return unsigned ? "BIGINT UNSIGNED" : "BIGINT";
109
+ if (tags?.has("uint64")) return "BIGINT UNSIGNED";
110
+ return unsigned ? "INT UNSIGNED" : "INT";
111
+ }
112
+ function mysqlTypeFromField(field) {
113
+ if (field.fkTargetField) return mysqlTypeFromField(field.fkTargetField);
114
+ const tags = field.type?.type?.tags;
115
+ const metadata = field.type?.metadata;
116
+ const mysqlTypeOverride = metadata?.get("db.mysql.type");
117
+ if (mysqlTypeOverride) return mysqlTypeOverride;
118
+ const unsigned = metadata?.has("db.mysql.unsigned") ?? false;
119
+ const precision = metadata?.get("db.column.precision");
120
+ switch (field.designType) {
121
+ case "number": {
122
+ if (precision) return `DECIMAL(${precision.precision},${precision.scale})`;
123
+ if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") return unsigned ? "BIGINT UNSIGNED" : "BIGINT";
124
+ if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return "TIMESTAMP";
125
+ if (tags?.has("int")) return intTypeFromTags(tags, unsigned);
126
+ return "DOUBLE";
127
+ }
128
+ case "integer": return intTypeFromTags(tags, unsigned);
129
+ case "decimal": {
130
+ if (precision) return `DECIMAL(${precision.precision},${precision.scale})`;
131
+ return "DECIMAL(10,2)";
132
+ }
133
+ case "boolean": return "TINYINT(1)";
134
+ case "string": {
135
+ if (tags?.has("char")) return "CHAR(1)";
136
+ const maxLen = metadata?.get("expect.maxLength")?.length;
137
+ if (maxLen !== undefined && maxLen <= 65535) return `VARCHAR(${maxLen})`;
138
+ if (maxLen !== undefined && maxLen > 65535) return "LONGTEXT";
139
+ if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
140
+ return "TEXT";
141
+ }
142
+ case "json":
143
+ case "object":
144
+ case "array": return "JSON";
145
+ default: {
146
+ if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
147
+ return "TEXT";
148
+ }
149
+ }
150
+ }
151
+ function buildCreateTable(table, fields, foreignKeys, options) {
152
+ const colDefs = [];
153
+ const primaryKeys = fields.filter((f) => f.isPrimaryKey);
154
+ for (const field of fields) {
155
+ if (field.ignored) continue;
156
+ const sqlType = mysqlTypeFromField(field);
157
+ let def = `${qi(field.physicalName)} ${sqlType}`;
158
+ if (options?.incrementFields?.has(field.physicalName)) def += " AUTO_INCREMENT";
159
+ if (!field.optional && !field.isPrimaryKey && !options?.incrementFields?.has(field.physicalName)) def += " NOT NULL";
160
+ if (field.defaultValue?.kind === "value") def += ` DEFAULT ${(0, __atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
161
+ else if (field.defaultValue?.kind === "fn") {
162
+ if (field.defaultValue.fn === "uuid") def += " DEFAULT (UUID())";
163
+ else if (field.defaultValue.fn === "now") def += " DEFAULT CURRENT_TIMESTAMP";
164
+ }
165
+ const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
166
+ if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
167
+ else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
168
+ const onUpdate = options?.onUpdateFields?.get(field.physicalName);
169
+ if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
170
+ colDefs.push(def);
171
+ }
172
+ if (primaryKeys.length === 1) {
173
+ const pkCol = qi(primaryKeys[0].physicalName);
174
+ for (let i = 0; i < colDefs.length; i++) if (colDefs[i].startsWith(pkCol)) {
175
+ colDefs[i] += " PRIMARY KEY";
176
+ break;
177
+ }
178
+ } else if (primaryKeys.length > 1) {
179
+ const pkCols = primaryKeys.map((pk) => qi(pk.physicalName)).join(", ");
180
+ colDefs.push(`PRIMARY KEY (${pkCols})`);
181
+ }
182
+ if (foreignKeys) for (const fk of foreignKeys.values()) {
183
+ const localCols = fk.fields.map((f) => qi(f)).join(", ");
184
+ const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
185
+ let constraint = `FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
186
+ if (fk.onDelete) constraint += ` ON DELETE ${(0, __atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
187
+ if (fk.onUpdate) constraint += ` ON UPDATE ${(0, __atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
188
+ colDefs.push(constraint);
189
+ }
190
+ let sql = `CREATE TABLE IF NOT EXISTS ${quoteTableName(table)} (${colDefs.join(", ")})`;
191
+ const engine = options?.engine ?? "InnoDB";
192
+ const charset = options?.charset ?? "utf8mb4";
193
+ const collation = options?.collation ?? "utf8mb4_unicode_ci";
194
+ sql += ` ENGINE=${engine} DEFAULT CHARSET=${charset} COLLATE=${collation}`;
195
+ if (options?.autoIncrementStart !== undefined) sql += ` AUTO_INCREMENT=${options.autoIncrementStart}`;
196
+ return sql;
197
+ }
198
+
199
+ //#endregion
200
+ //#region packages/db-mysql/src/filter-builder.ts
201
+ function buildWhere(filter) {
202
+ return (0, __atscript_db_sql_tools.buildWhere)(mysqlDialect, filter);
203
+ }
204
+
205
+ //#endregion
206
+ //#region packages/db-mysql/src/mysql-adapter.ts
207
+ function _define_property$1(obj, key, value) {
208
+ if (key in obj) Object.defineProperty(obj, key, {
209
+ value,
210
+ enumerable: true,
211
+ configurable: true,
212
+ writable: true
213
+ });
214
+ else obj[key] = value;
215
+ return obj;
216
+ }
217
+ function utcDatetimeToEpochMs(value) {
218
+ if (typeof value === "number") return value;
219
+ if (value instanceof Date) return value.getTime();
220
+ if (typeof value === "string") {
221
+ const ms = Date.UTC(+value.slice(0, 4), +value.slice(5, 7) - 1, +value.slice(8, 10), +value.slice(11, 13), +value.slice(14, 16), +value.slice(17, 19));
222
+ return Number.isNaN(ms) ? value : ms;
223
+ }
224
+ return value;
225
+ }
226
+ /** Formats epoch ms as 'YYYY-MM-DD HH:MM:SS' in UTC for MySQL TIMESTAMP columns. */ function epochMsToUtcDatetime(ms) {
227
+ const d = new Date(ms);
228
+ 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")}`;
229
+ }
230
+ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
231
+ /** Schema name for INFORMATION_SCHEMA queries (null falls through to DATABASE()). */ get _schema() {
232
+ return this._table.schema ?? null;
233
+ }
234
+ async _beginTransaction() {
235
+ const conn = await this.driver.getConnection();
236
+ await conn.exec("START TRANSACTION");
237
+ this._log("START TRANSACTION");
238
+ return conn;
239
+ }
240
+ async _commitTransaction(state) {
241
+ const conn = state;
242
+ try {
243
+ this._log("COMMIT");
244
+ await conn.exec("COMMIT");
245
+ } finally {
246
+ conn.release();
247
+ }
248
+ }
249
+ async _rollbackTransaction(state) {
250
+ const conn = state;
251
+ try {
252
+ this._log("ROLLBACK");
253
+ await conn.exec("ROLLBACK");
254
+ } finally {
255
+ conn.release();
256
+ }
257
+ }
258
+ /**
259
+ * Returns the active executor: dedicated connection if inside a transaction,
260
+ * otherwise the pool-based driver.
261
+ */ _exec() {
262
+ const txState = this._getTransactionState();
263
+ return txState ?? this.driver;
264
+ }
265
+ /** MySQL InnoDB enforces FK constraints natively. */ supportsNativeForeignKeys() {
266
+ return true;
267
+ }
268
+ prepareId(id, _fieldType) {
269
+ return id;
270
+ }
271
+ supportsNativeValueDefaults() {
272
+ return true;
273
+ }
274
+ nativeDefaultFns() {
275
+ return MysqlAdapter.NATIVE_DEFAULT_FNS;
276
+ }
277
+ onBeforeFlatten(type) {
278
+ const meta = type.metadata;
279
+ const engine = meta.get("db.mysql.engine");
280
+ if (engine) this._engine = engine;
281
+ const charset = meta.get("db.mysql.charset");
282
+ if (charset) this._charset = charset;
283
+ const collate = meta.get("db.mysql.collate");
284
+ if (collate) this._collation = collate;
285
+ }
286
+ onFieldScanned(field, _type, metadata) {
287
+ if (metadata.has("db.default.increment")) {
288
+ this._incrementFields.add(field);
289
+ const startVal = metadata.get("db.default.increment");
290
+ if (typeof startVal === "number") this._autoIncrementStart = startVal;
291
+ }
292
+ const onUpdate = metadata.get("db.mysql.onUpdate");
293
+ if (onUpdate) this._onUpdateFields.set(field, onUpdate);
294
+ const vectorMeta = metadata.get("db.search.vector");
295
+ if (vectorMeta) {
296
+ const indexName = vectorMeta.indexName || field;
297
+ this._vectorFields.set(field, {
298
+ dimensions: vectorMeta.dimensions,
299
+ similarity: vectorMeta.similarity || "cosine",
300
+ indexName
301
+ });
302
+ const threshold = metadata.get("db.search.vector.threshold");
303
+ if (threshold !== undefined) this._vectorThresholds.set(indexName, threshold);
304
+ }
305
+ }
306
+ getDesiredTableOptions() {
307
+ return [
308
+ {
309
+ key: "engine",
310
+ value: this._engine
311
+ },
312
+ {
313
+ key: "charset",
314
+ value: this._charset
315
+ },
316
+ {
317
+ key: "collation",
318
+ value: this._collation
319
+ }
320
+ ];
321
+ }
322
+ async getExistingTableOptions() {
323
+ const row = await this._exec().get(`SELECT ENGINE, TABLE_COLLATION
324
+ FROM INFORMATION_SCHEMA.TABLES
325
+ WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [this._table.tableName, this._schema]);
326
+ if (!row) return [];
327
+ const charset = row.TABLE_COLLATION?.split("_")[0] ?? "utf8mb4";
328
+ return [
329
+ {
330
+ key: "engine",
331
+ value: row.ENGINE ?? "InnoDB"
332
+ },
333
+ {
334
+ key: "charset",
335
+ value: charset
336
+ },
337
+ {
338
+ key: "collation",
339
+ value: row.TABLE_COLLATION ?? "utf8mb4_unicode_ci"
340
+ }
341
+ ];
342
+ }
343
+ async applyTableOptions(changes) {
344
+ const tableName = this.resolveTableName();
345
+ const clauses = [];
346
+ for (const change of changes) switch (change.key) {
347
+ case "engine": {
348
+ clauses.push(`ENGINE = ${change.newValue}`);
349
+ break;
350
+ }
351
+ case "charset": {
352
+ clauses.push(`CHARACTER SET = ${change.newValue}`);
353
+ break;
354
+ }
355
+ case "collation": {
356
+ clauses.push(`COLLATE = ${change.newValue}`);
357
+ break;
358
+ }
359
+ }
360
+ if (clauses.length > 0) {
361
+ const ddl = `ALTER TABLE ${quoteTableName(tableName)} ${clauses.join(", ")}`;
362
+ this._log(ddl);
363
+ await this._exec().exec(ddl);
364
+ }
365
+ }
366
+ /**
367
+ * Returns a value formatter for TIMESTAMP-mapped fields.
368
+ * Number fields with @db.default.now map to MySQL TIMESTAMP — the formatter
369
+ * converts epoch ms to a UTC datetime string for the wire protocol.
370
+ */ formatValue(field) {
371
+ if (field.designType === "number" && field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return {
372
+ toStorage: (value) => typeof value === "number" ? epochMsToUtcDatetime(value) : value,
373
+ fromStorage: utcDatetimeToEpochMs
374
+ };
375
+ return undefined;
376
+ }
377
+ /**
378
+ * Wraps an async write operation to catch MySQL constraint errors
379
+ * and rethrow as structured `DbError`.
380
+ *
381
+ * MySQL uses numeric error codes:
382
+ * - 1062 = ER_DUP_ENTRY (unique constraint violation)
383
+ * - 1451 = ER_ROW_IS_REFERENCED_2 (FK violation on delete)
384
+ * - 1452 = ER_NO_REFERENCED_ROW_2 (FK violation on insert/update)
385
+ */ async _wrapConstraintError(fn) {
386
+ try {
387
+ return await fn();
388
+ } catch (error) {
389
+ if (error && typeof error === "object" && "errno" in error) {
390
+ const err = error;
391
+ if (err.errno === 1062) {
392
+ const match = err.message?.match(/for key '(?:\w+\.)?(\w+)'/);
393
+ const field = match?.[1] ?? "";
394
+ throw new __atscript_db.DbError("CONFLICT", [{
395
+ path: field,
396
+ message: err.sqlMessage ?? err.message
397
+ }]);
398
+ }
399
+ if (err.errno === 1451 || err.errno === 1452) {
400
+ const errors = this._mapFkError(err.message);
401
+ throw new __atscript_db.DbError("FK_VIOLATION", errors);
402
+ }
403
+ }
404
+ throw error;
405
+ }
406
+ }
407
+ _mapFkError(message) {
408
+ const fkMatch = message.match(/FOREIGN KEY \(`(\w+)`\)/);
409
+ if (fkMatch) {
410
+ const physicalCol = fkMatch[1];
411
+ const field = this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol);
412
+ return [{
413
+ path: field?.path ?? physicalCol,
414
+ message
415
+ }];
416
+ }
417
+ return [{
418
+ path: "",
419
+ message
420
+ }];
421
+ }
422
+ async insertOne(data) {
423
+ const { sql, params } = buildInsert(this.resolveTableName(), data);
424
+ this._log(sql, params);
425
+ const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
426
+ return { insertedId: this._resolveInsertedId(data, result.insertId) };
427
+ }
428
+ async insertMany(data) {
429
+ if (data.length === 0) return {
430
+ insertedCount: 0,
431
+ insertedIds: []
432
+ };
433
+ return this.withTransaction(async () => {
434
+ const tableName = this.resolveTableName();
435
+ const keys = Object.keys(data[0]);
436
+ const colsClause = keys.map((k) => qi(k)).join(", ");
437
+ const paramsPerRow = keys.length;
438
+ const maxRowsPerBatch = paramsPerRow > 0 ? Math.floor(6e4 / paramsPerRow) : data.length;
439
+ const allIds = [];
440
+ const rowPlaceholderClause = `(${keys.map(() => "?").join(", ")})`;
441
+ for (let offset = 0; offset < data.length; offset += maxRowsPerBatch) {
442
+ const batchEnd = Math.min(offset + maxRowsPerBatch, data.length);
443
+ const batchSize = batchEnd - offset;
444
+ const params = [];
445
+ for (let i = offset; i < batchEnd; i++) {
446
+ const row = data[i];
447
+ for (const k of keys) params.push(mysqlDialect.toValue(row[k]));
448
+ }
449
+ const valuesClause = Array(batchSize).fill(rowPlaceholderClause).join(", ");
450
+ const sql = `INSERT INTO ${quoteTableName(tableName)} (${colsClause}) VALUES ${valuesClause}`;
451
+ this._log(sql, params);
452
+ const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
453
+ const firstId = Number(result.insertId);
454
+ for (let i = 0; i < batchSize; i++) allIds.push(this._resolveInsertedId(data[offset + i], firstId > 0 ? firstId + i : 0));
455
+ }
456
+ return {
457
+ insertedCount: allIds.length,
458
+ insertedIds: allIds
459
+ };
460
+ });
461
+ }
462
+ async findOne(query) {
463
+ const where = buildWhere(query.filter);
464
+ const controls = {
465
+ ...query.controls,
466
+ $limit: 1
467
+ };
468
+ const { sql, params } = buildSelect(this.resolveTableName(), where, controls);
469
+ this._log(sql, params);
470
+ return this._exec().get(sql, params);
471
+ }
472
+ async findMany(query) {
473
+ const where = buildWhere(query.filter);
474
+ const { sql, params } = buildSelect(this.resolveTableName(), where, query.controls);
475
+ this._log(sql, params);
476
+ return this._exec().all(sql, params);
477
+ }
478
+ async count(query) {
479
+ const where = buildWhere(query.filter);
480
+ const tableName = this.resolveTableName();
481
+ const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${where.sql}`;
482
+ this._log(sql, where.params);
483
+ const row = await this._exec().get(sql, where.params);
484
+ return row?.cnt ?? 0;
485
+ }
486
+ async aggregate(query) {
487
+ const where = buildWhere(query.filter);
488
+ const tableName = this.resolveTableName();
489
+ if (query.controls.$count) {
490
+ const { sql: sql$1, params: params$1 } = buildAggregateCount(tableName, where, query.controls);
491
+ this._log(sql$1, params$1);
492
+ const row = await this._exec().get(sql$1, params$1);
493
+ return [{ count: row?.count ?? 0 }];
494
+ }
495
+ const { sql, params } = buildAggregateSelect(tableName, where, query.controls);
496
+ this._log(sql, params);
497
+ return this._exec().all(sql, params);
498
+ }
499
+ async updateOne(filter, data) {
500
+ const where = buildWhere(filter);
501
+ const { sql, params } = buildUpdate(this.resolveTableName(), data, where, 1);
502
+ this._log(sql, params);
503
+ const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
504
+ return {
505
+ matchedCount: result.affectedRows,
506
+ modifiedCount: result.changedRows
507
+ };
508
+ }
509
+ async updateMany(filter, data) {
510
+ const where = buildWhere(filter);
511
+ const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
512
+ this._log(sql, params);
513
+ const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
514
+ return {
515
+ matchedCount: result.affectedRows,
516
+ modifiedCount: result.changedRows
517
+ };
518
+ }
519
+ async replaceOne(filter, data) {
520
+ const where = buildWhere(filter);
521
+ const { sql, params } = buildUpdate(this.resolveTableName(), data, where, 1);
522
+ this._log(sql, params);
523
+ const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
524
+ return {
525
+ matchedCount: result.affectedRows,
526
+ modifiedCount: result.changedRows
527
+ };
528
+ }
529
+ async replaceMany(filter, data) {
530
+ const where = buildWhere(filter);
531
+ const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
532
+ this._log(sql, params);
533
+ const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
534
+ return {
535
+ matchedCount: result.affectedRows,
536
+ modifiedCount: result.changedRows
537
+ };
538
+ }
539
+ async deleteOne(filter) {
540
+ const where = buildWhere(filter);
541
+ const { sql, params } = buildDelete(this.resolveTableName(), where, 1);
542
+ this._log(sql, params);
543
+ const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
544
+ return { deletedCount: result.affectedRows };
545
+ }
546
+ async deleteMany(filter) {
547
+ const where = buildWhere(filter);
548
+ const { sql, params } = buildDelete(this.resolveTableName(), where);
549
+ this._log(sql, params);
550
+ const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
551
+ return { deletedCount: result.affectedRows };
552
+ }
553
+ async ensureTable() {
554
+ if (this._supportsVector === undefined && this._vectorFields.size > 0) await this._detectVectorSupport();
555
+ if (this._table instanceof __atscript_db.AtscriptDbView) return this._ensureView();
556
+ const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
557
+ engine: this._engine,
558
+ charset: this._charset,
559
+ collation: this._collation,
560
+ autoIncrementStart: this._autoIncrementStart,
561
+ incrementFields: this._incrementFields,
562
+ onUpdateFields: this._onUpdateFields
563
+ });
564
+ this._log(sql);
565
+ await this._exec().exec(sql);
566
+ }
567
+ async _ensureView() {
568
+ const view = this._table;
569
+ const sql = buildCreateView(this.resolveTableName(), view.viewPlan, view.getViewColumnMappings(), (ref) => view.resolveFieldRef(ref, qi));
570
+ this._log(sql);
571
+ await this._exec().exec(sql);
572
+ }
573
+ async getExistingColumns() {
574
+ return this.getExistingColumnsForTable(this._table.tableName);
575
+ }
576
+ async getExistingColumnsForTable(tableName) {
577
+ const schema = this._schema;
578
+ const rows = await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT
579
+ FROM INFORMATION_SCHEMA.COLUMNS
580
+ WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
581
+ ORDER BY ORDINAL_POSITION`, [tableName, schema]);
582
+ return rows.map((r) => ({
583
+ name: r.COLUMN_NAME,
584
+ type: r.COLUMN_TYPE.toUpperCase(),
585
+ notnull: r.IS_NULLABLE === "NO",
586
+ pk: r.COLUMN_KEY === "PRI",
587
+ dflt_value: normalizeMysqlDefault(r.COLUMN_DEFAULT)
588
+ }));
589
+ }
590
+ async syncColumns(diff) {
591
+ const tableName = this.resolveTableName();
592
+ const added = [];
593
+ const renamed = [];
594
+ for (const { field, oldName } of diff.renamed ?? []) {
595
+ const ddl = `ALTER TABLE ${quoteTableName(tableName)} RENAME COLUMN ${qi(oldName)} TO ${qi(field.physicalName)}`;
596
+ this._log(ddl);
597
+ await this._exec().exec(ddl);
598
+ renamed.push(field.physicalName);
599
+ }
600
+ for (const field of diff.added) {
601
+ const sqlType = this.typeMapper(field);
602
+ let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
603
+ if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
604
+ if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, __atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
605
+ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscript_db_sql_tools.defaultValueForType)(field.designType)}`;
606
+ if (field.collate) {
607
+ const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
608
+ ddl += ` COLLATE ${nativeCollate ?? collationToMysql(field.collate)}`;
609
+ }
610
+ this._log(ddl);
611
+ await this._exec().exec(ddl);
612
+ added.push(field.physicalName);
613
+ }
614
+ for (const { field } of diff.typeChanged ?? []) {
615
+ const sqlType = this.typeMapper(field);
616
+ let ddl = `ALTER TABLE ${quoteTableName(tableName)} MODIFY COLUMN ${qi(field.physicalName)} ${sqlType}`;
617
+ if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
618
+ this._log(ddl);
619
+ await this._exec().exec(ddl);
620
+ }
621
+ for (const { field } of diff.nullableChanged ?? []) {
622
+ const sqlType = this.typeMapper(field);
623
+ const nullability = field.optional ? "NULL" : "NOT NULL";
624
+ const ddl = `ALTER TABLE ${quoteTableName(tableName)} MODIFY COLUMN ${qi(field.physicalName)} ${sqlType} ${nullability}`;
625
+ this._log(ddl);
626
+ await this._exec().exec(ddl);
627
+ }
628
+ for (const { field } of diff.defaultChanged ?? []) {
629
+ const sqlType = this.typeMapper(field);
630
+ let ddl = `ALTER TABLE ${quoteTableName(tableName)} MODIFY COLUMN ${qi(field.physicalName)} ${sqlType}`;
631
+ if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
632
+ if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, __atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
633
+ else if (field.defaultValue?.kind === "fn") ddl += ` DEFAULT ${field.defaultValue.fn === "now" ? "CURRENT_TIMESTAMP" : field.defaultValue.fn === "uuid" ? "(UUID())" : `(${field.defaultValue.fn}())`}`;
634
+ else ddl += " DEFAULT NULL";
635
+ this._log(ddl);
636
+ await this._exec().exec(ddl);
637
+ }
638
+ return {
639
+ added,
640
+ renamed
641
+ };
642
+ }
643
+ async recreateTable() {
644
+ const tableName = this.resolveTableName();
645
+ const tempName = `${this._table.tableName}__tmp_${Date.now()}`;
646
+ await this._exec().exec("SET FOREIGN_KEY_CHECKS = 0");
647
+ try {
648
+ const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, {
649
+ engine: this._engine,
650
+ charset: this._charset,
651
+ collation: this._collation,
652
+ autoIncrementStart: this._autoIncrementStart,
653
+ incrementFields: this._incrementFields,
654
+ onUpdateFields: this._onUpdateFields
655
+ });
656
+ this._log(createSql);
657
+ await this._exec().exec(createSql);
658
+ const oldCols = (await this.getExistingColumns()).map((c) => c.name);
659
+ const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
660
+ const oldColSet = new Set(oldCols);
661
+ const commonCols = newCols.filter((c) => oldColSet.has(c));
662
+ if (commonCols.length > 0) {
663
+ const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
664
+ const colNames = commonCols.map((c) => qi(c)).join(", ");
665
+ const selectExprs = commonCols.map((c) => {
666
+ const field = fieldsByName.get(c);
667
+ if (field && !field.optional && !field.isPrimaryKey) {
668
+ const fallback = field.defaultValue?.kind === "value" ? (0, __atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value) : (0, __atscript_db_sql_tools.defaultValueForType)(field.designType);
669
+ return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
670
+ }
671
+ return qi(c);
672
+ }).join(", ");
673
+ const copySql = `INSERT INTO ${qi(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
674
+ this._log(copySql);
675
+ await this._exec().exec(copySql);
676
+ }
677
+ await this._exec().exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)}`);
678
+ await this._exec().exec(`RENAME TABLE ${qi(tempName)} TO ${quoteTableName(tableName)}`);
679
+ } finally {
680
+ await this._exec().exec("SET FOREIGN_KEY_CHECKS = 1");
681
+ }
682
+ }
683
+ async dropTable() {
684
+ const ddl = `DROP TABLE IF EXISTS ${quoteTableName(this.resolveTableName())}`;
685
+ this._log(ddl);
686
+ const conn = await this.driver.getConnection();
687
+ await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
688
+ try {
689
+ await conn.exec(ddl);
690
+ } finally {
691
+ await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
692
+ conn.release();
693
+ }
694
+ }
695
+ async dropColumns(columns) {
696
+ const tableName = this.resolveTableName();
697
+ const drops = columns.map((col) => `DROP COLUMN ${qi(col)}`).join(", ");
698
+ const ddl = `ALTER TABLE ${quoteTableName(tableName)} ${drops}`;
699
+ this._log(ddl);
700
+ await this._exec().exec(ddl);
701
+ }
702
+ async dropTableByName(tableName) {
703
+ const ddl = `DROP TABLE IF EXISTS ${quoteTableName(tableName)}`;
704
+ this._log(ddl);
705
+ const conn = await this.driver.getConnection();
706
+ await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
707
+ try {
708
+ await conn.exec(ddl);
709
+ } finally {
710
+ await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
711
+ conn.release();
712
+ }
713
+ }
714
+ async dropViewByName(viewName) {
715
+ const ddl = `DROP VIEW IF EXISTS ${quoteTableName(viewName)}`;
716
+ this._log(ddl);
717
+ await this._exec().exec(ddl);
718
+ }
719
+ async renameTable(oldName) {
720
+ const newName = this.resolveTableName();
721
+ const ddl = `RENAME TABLE ${quoteTableName(oldName)} TO ${quoteTableName(newName)}`;
722
+ this._log(ddl);
723
+ await this._exec().exec(ddl);
724
+ }
725
+ typeMapper(field) {
726
+ if (this._vectorFields.has(field.path)) {
727
+ const vec = this._vectorFields.get(field.path);
728
+ return this._supportsVector ? `VECTOR(${vec.dimensions})` : "JSON";
729
+ }
730
+ return mysqlTypeFromField(field);
731
+ }
732
+ async syncIndexes() {
733
+ const tableName = this._table.tableName;
734
+ const schema = this._schema;
735
+ const stringFields = new Set(this._table.fieldDescriptors.filter((f) => f.designType === "string").map((f) => f.physicalName));
736
+ await this.syncIndexesWithDiff({
737
+ listExisting: async () => this._exec().all(`SELECT DISTINCT INDEX_NAME as name FROM INFORMATION_SCHEMA.STATISTICS
738
+ WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [tableName, schema]),
739
+ createIndex: async (index) => {
740
+ const unique = index.type === "unique" ? "UNIQUE " : "";
741
+ const fulltext = index.type === "fulltext" ? "FULLTEXT " : "";
742
+ const isFulltext = index.type === "fulltext";
743
+ const cols = index.fields.map((f) => {
744
+ const col = qi(f.name);
745
+ const prefix = !isFulltext && stringFields.has(f.name) ? "(255)" : "";
746
+ const order = isFulltext ? "" : ` ${f.sort === "desc" ? "DESC" : "ASC"}`;
747
+ return `${col}${prefix}${order}`;
748
+ }).join(", ");
749
+ const sql = `CREATE ${fulltext}${unique}INDEX ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${cols})`;
750
+ this._log(sql);
751
+ await this._exec().exec(sql);
752
+ },
753
+ dropIndex: async (name) => {
754
+ const sql = `DROP INDEX ${qi(name)} ON ${quoteTableName(this.resolveTableName())}`;
755
+ this._log(sql);
756
+ await this._exec().exec(sql);
757
+ }
758
+ });
759
+ }
760
+ async syncForeignKeys() {
761
+ const existingByName = await this._getExistingFkConstraints();
762
+ const desiredFkKeys = new Set();
763
+ for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].sort().join(","));
764
+ for (const [constraintName, columns] of existingByName) {
765
+ const key = columns.sort().join(",");
766
+ if (!desiredFkKeys.has(key)) {
767
+ const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP FOREIGN KEY ${qi(constraintName)}`;
768
+ this._log(ddl);
769
+ await this._exec().exec(ddl);
770
+ }
771
+ }
772
+ const existingKeys = new Set([...existingByName.values()].map((cols) => cols.sort().join(",")));
773
+ for (const fk of this._table.foreignKeys.values()) {
774
+ const key = [...fk.fields].sort().join(",");
775
+ if (!existingKeys.has(key)) {
776
+ const localCols = fk.fields.map((f) => qi(f)).join(", ");
777
+ const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
778
+ let ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ADD FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
779
+ if (fk.onDelete) ddl += ` ON DELETE ${(0, __atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
780
+ if (fk.onUpdate) ddl += ` ON UPDATE ${(0, __atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
781
+ this._log(ddl);
782
+ await this._exec().exec(ddl);
783
+ }
784
+ }
785
+ }
786
+ async dropForeignKeys(fkFieldKeys) {
787
+ if (fkFieldKeys.length === 0) return;
788
+ const keySet = new Set(fkFieldKeys);
789
+ const existingByName = await this._getExistingFkConstraints();
790
+ for (const [constraintName, cols] of existingByName) {
791
+ const key = cols.sort().join(",");
792
+ if (keySet.has(key)) {
793
+ const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP FOREIGN KEY ${qi(constraintName)}`;
794
+ this._log(ddl);
795
+ await this._exec().exec(ddl);
796
+ }
797
+ }
798
+ }
799
+ /** Queries INFORMATION_SCHEMA for existing FK constraints, grouped by constraint name → column names. */ async _getExistingFkConstraints() {
800
+ const rows = await this._exec().all(`SELECT kcu.CONSTRAINT_NAME, kcu.COLUMN_NAME
801
+ FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
802
+ WHERE kcu.TABLE_NAME = ? AND kcu.TABLE_SCHEMA = COALESCE(?, DATABASE())
803
+ AND kcu.REFERENCED_TABLE_NAME IS NOT NULL`, [this._table.tableName, this._schema]);
804
+ const byName = new Map();
805
+ for (const row of rows) {
806
+ let cols = byName.get(row.CONSTRAINT_NAME);
807
+ if (!cols) {
808
+ cols = [];
809
+ byName.set(row.CONSTRAINT_NAME, cols);
810
+ }
811
+ cols.push(row.COLUMN_NAME);
812
+ }
813
+ return byName;
814
+ }
815
+ getSearchIndexes() {
816
+ const indexes = [];
817
+ for (const index of this._table.indexes.values()) if (index.type === "fulltext") indexes.push({
818
+ name: index.key,
819
+ description: `FULLTEXT index on ${index.fields.map((f) => f.name).join(", ")}`,
820
+ type: "text"
821
+ });
822
+ for (const [field, vec] of this._vectorFields) indexes.push({
823
+ name: vec.indexName,
824
+ description: `VECTOR(${vec.dimensions}) on ${field}, ${vec.similarity}`,
825
+ type: "vector"
826
+ });
827
+ return indexes;
828
+ }
829
+ async search(text, query, indexName) {
830
+ const combinedWhere = this._buildSearchWhere(text, query, indexName);
831
+ const { sql, params } = buildSelect(this.resolveTableName(), combinedWhere, query.controls);
832
+ this._log(sql, params);
833
+ return this._exec().all(sql, params);
834
+ }
835
+ async searchWithCount(text, query, indexName) {
836
+ const combinedWhere = this._buildSearchWhere(text, query, indexName);
837
+ const tableName = this.resolveTableName();
838
+ const selectPromise = (async () => {
839
+ const { sql, params } = buildSelect(tableName, combinedWhere, query.controls);
840
+ this._log(sql, params);
841
+ return this._exec().all(sql, params);
842
+ })();
843
+ const countPromise = (async () => {
844
+ const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${combinedWhere.sql}`;
845
+ this._log(sql, combinedWhere.params);
846
+ const row = await this._exec().get(sql, combinedWhere.params);
847
+ return row?.cnt ?? 0;
848
+ })();
849
+ const [data, count] = await Promise.all([selectPromise, countPromise]);
850
+ return {
851
+ data,
852
+ count
853
+ };
854
+ }
855
+ _buildSearchWhere(text, query, indexName) {
856
+ const fulltextIndex = this._getFulltextIndex(indexName);
857
+ if (!fulltextIndex) throw new Error("No FULLTEXT index found for search");
858
+ const matchCols = fulltextIndex.fields.map((f) => qi(f.name)).join(", ");
859
+ const where = buildWhere(query.filter);
860
+ const matchClause = `MATCH(${matchCols}) AGAINST(? IN NATURAL LANGUAGE MODE)`;
861
+ return {
862
+ sql: where.sql === "1=1" ? matchClause : `${where.sql} AND ${matchClause}`,
863
+ params: [...where.params, text]
864
+ };
865
+ }
866
+ _getFulltextIndex(indexName) {
867
+ for (const index of this._table.indexes.values()) if (index.type === "fulltext") {
868
+ if (!indexName || index.key === indexName) return index;
869
+ }
870
+ return undefined;
871
+ }
872
+ /**
873
+ * Detects native VECTOR type support by inspecting the server version.
874
+ * MySQL 9.0+ supports the VECTOR column type natively.
875
+ * Caches the result for the lifetime of this adapter instance.
876
+ */ async _detectVectorSupport() {
877
+ if (this._supportsVector !== undefined) return this._supportsVector;
878
+ try {
879
+ const row = await this.driver.get("SELECT VERSION() as v", []);
880
+ if (row?.v) {
881
+ const major = Number.parseInt(row.v, 10);
882
+ this._supportsVector = !Number.isNaN(major) && major >= 9;
883
+ } else this._supportsVector = false;
884
+ } catch {
885
+ this._supportsVector = false;
886
+ }
887
+ return this._supportsVector;
888
+ }
889
+ isVectorSearchable() {
890
+ return this._supportsVector === true && this._vectorFields.size > 0;
891
+ }
892
+ async vectorSearch(vector, query, indexName) {
893
+ await this._detectVectorSupport();
894
+ if (!this._supportsVector) throw new Error("Vector search requires MySQL 9.0+");
895
+ const { sql, params } = this._buildVectorSearchQuery(vector, query, indexName);
896
+ this._log(sql, params);
897
+ return this._exec().all(sql, params);
898
+ }
899
+ async vectorSearchWithCount(vector, query, indexName) {
900
+ await this._detectVectorSupport();
901
+ if (!this._supportsVector) throw new Error("Vector search requires MySQL 9.0+");
902
+ const { sql, params } = this._buildVectorSearchQuery(vector, query, indexName);
903
+ const { sql: countSql, params: countParams } = this._buildVectorSearchCountQuery(vector, query, indexName);
904
+ this._log(sql, params);
905
+ this._log(countSql, countParams);
906
+ const [data, countRow] = await Promise.all([this._exec().all(sql, params), this._exec().get(countSql, countParams)]);
907
+ return {
908
+ data,
909
+ count: countRow?.cnt ?? 0
910
+ };
911
+ }
912
+ /** Resolves vector field and computes shared context for vector search SQL builders. */ _prepareVectorSearch(vector, query, indexName) {
913
+ let field;
914
+ let vec;
915
+ if (indexName) {
916
+ let found = false;
917
+ for (const [f, v] of this._vectorFields) if (v.indexName === indexName) {
918
+ field = f;
919
+ vec = v;
920
+ found = true;
921
+ break;
922
+ }
923
+ if (!found) throw new Error(`Vector index "${indexName}" not found`);
924
+ } else {
925
+ const first = this._vectorFields.entries().next();
926
+ if (first.done) throw new Error("No vector fields defined");
927
+ field = first.value[0];
928
+ vec = first.value[1];
929
+ }
930
+ const distanceFn = similarityToMysqlFn(vec.similarity);
931
+ const where = buildWhere(query.filter);
932
+ const controls = query.controls || {};
933
+ const threshold = this._resolveVectorThreshold(controls, vec.indexName);
934
+ return {
935
+ field,
936
+ vec,
937
+ distanceFn,
938
+ where,
939
+ controls,
940
+ threshold,
941
+ tableName: this.resolveTableName(),
942
+ vectorStr: vectorToString(vector)
943
+ };
944
+ }
945
+ _buildVectorSearchQuery(vector, query, indexName) {
946
+ const ctx = this._prepareVectorSearch(vector, query, indexName);
947
+ const inner = `SELECT *, ${ctx.distanceFn}(${qi(ctx.field)}, STRING_TO_VECTOR(?)) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${ctx.where.sql}`;
948
+ const params = [ctx.vectorStr, ...ctx.where.params];
949
+ let sql = `SELECT * FROM (${inner}) _v`;
950
+ if (ctx.threshold !== undefined) {
951
+ sql += ` WHERE _distance <= ?`;
952
+ params.push(2 * (1 - ctx.threshold));
953
+ }
954
+ sql += ` ORDER BY _distance ASC`;
955
+ if (ctx.controls.$skip) sql += ` LIMIT ${ctx.controls.$limit || 1e3} OFFSET ${ctx.controls.$skip}`;
956
+ else sql += ` LIMIT ${ctx.controls.$limit || 20}`;
957
+ return {
958
+ sql,
959
+ params
960
+ };
961
+ }
962
+ _buildVectorSearchCountQuery(vector, query, indexName) {
963
+ const ctx = this._prepareVectorSearch(vector, query, indexName);
964
+ const inner = `SELECT ${ctx.distanceFn}(${qi(ctx.field)}, STRING_TO_VECTOR(?)) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${ctx.where.sql}`;
965
+ const params = [ctx.vectorStr, ...ctx.where.params];
966
+ let sql = `SELECT COUNT(*) AS cnt FROM (${inner}) _v`;
967
+ if (ctx.threshold !== undefined) {
968
+ sql += ` WHERE _distance <= ?`;
969
+ params.push(2 * (1 - ctx.threshold));
970
+ }
971
+ return {
972
+ sql,
973
+ params
974
+ };
975
+ }
976
+ /** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */ _resolveVectorThreshold(controls, indexName) {
977
+ const queryThreshold = controls.$threshold;
978
+ if (queryThreshold !== undefined) return queryThreshold;
979
+ return this._vectorThresholds.get(indexName);
980
+ }
981
+ constructor(driver) {
982
+ 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();
983
+ }
984
+ };
985
+ _define_property$1(MysqlAdapter, "NATIVE_DEFAULT_FNS", new Set(["now", "increment"]));
986
+ /**
987
+ * Normalizes MySQL INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT values
988
+ * to match the format produced by `serializeDefaultValue()`.
989
+ *
990
+ * MySQL stores expression defaults as raw SQL (e.g., `CURRENT_TIMESTAMP`,
991
+ * `uuid()`), but the diff engine compares against serialized form (`fn:now`,
992
+ * `fn:uuid`). Without normalization, every table with function defaults
993
+ * produces phantom ALTER diffs on re-plan.
994
+ */ function normalizeMysqlDefault(value) {
995
+ if (value === null) return undefined;
996
+ const lower = value.toLowerCase();
997
+ if (lower === "current_timestamp" || lower === "current_timestamp()") return "fn:now";
998
+ if (lower === "uuid()") return "fn:uuid";
999
+ if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1).replace(/''/g, "'");
1000
+ return value;
1001
+ }
1002
+ /** Maps generic similarity metric to MySQL 9+ distance function name. */ function similarityToMysqlFn(similarity) {
1003
+ switch (similarity) {
1004
+ case "euclidean": return "VEC_DISTANCE_EUCLIDEAN";
1005
+ case "dotProduct": return "VEC_DISTANCE_DOT";
1006
+ default: return "VEC_DISTANCE_COSINE";
1007
+ }
1008
+ }
1009
+ /** Formats a number[] vector as MySQL's STRING_TO_VECTOR input: '[1.0, 2.0, ...]'. */ function vectorToString(vector) {
1010
+ return `[${vector.join(",")}]`;
1011
+ }
1012
+
1013
+ //#endregion
1014
+ //#region packages/db-mysql/src/mysql2-driver.ts
1015
+ function _define_property(obj, key, value) {
1016
+ if (key in obj) Object.defineProperty(obj, key, {
1017
+ value,
1018
+ enumerable: true,
1019
+ configurable: true,
1020
+ writable: true
1021
+ });
1022
+ else obj[key] = value;
1023
+ return obj;
1024
+ }
1025
+ /** mysql2 rejects `undefined` in bind arrays — coerce to `null`. */ function sanitizeParams(params) {
1026
+ if (!params) return [];
1027
+ return params.map((v) => v === undefined ? null : v);
1028
+ }
1029
+ /**
1030
+ * Custom type-casting for mysql2 result columns to maintain cross-adapter consistency.
1031
+ *
1032
+ * - TIMESTAMP/DATETIME → epoch milliseconds (number) instead of Date objects
1033
+ * - DECIMAL/NEWDECIMAL → number instead of string
1034
+ */ function atscriptTypeCast(field, next) {
1035
+ if (field.type === "TIMESTAMP" || field.type === "DATETIME") {
1036
+ const str = field.string();
1037
+ if (str === null) return null;
1038
+ return utcDatetimeToEpochMs(str);
1039
+ }
1040
+ if (field.type === "NEWDECIMAL" || field.type === "DECIMAL") {
1041
+ const str = field.string();
1042
+ return str === null ? null : Number(str);
1043
+ }
1044
+ return next();
1045
+ }
1046
+ var Mysql2Driver = class {
1047
+ getPool() {
1048
+ return this.pool || this.poolInit;
1049
+ }
1050
+ async run(sql, params) {
1051
+ const pool = await this.getPool();
1052
+ const [result] = await pool.query(sql, sanitizeParams(params));
1053
+ const header = result;
1054
+ return {
1055
+ affectedRows: header.affectedRows ?? 0,
1056
+ insertId: header.insertId ?? 0,
1057
+ changedRows: header.changedRows ?? 0
1058
+ };
1059
+ }
1060
+ async all(sql, params) {
1061
+ const pool = await this.getPool();
1062
+ const [rows] = await pool.query(sql, sanitizeParams(params));
1063
+ return rows;
1064
+ }
1065
+ async get(sql, params) {
1066
+ const pool = await this.getPool();
1067
+ const [rows] = await pool.query(sql, sanitizeParams(params));
1068
+ return rows[0] ?? null;
1069
+ }
1070
+ async exec(sql) {
1071
+ const pool = await this.getPool();
1072
+ await pool.query(sql);
1073
+ }
1074
+ async getConnection() {
1075
+ const pool = await this.getPool();
1076
+ const conn = await pool.getConnection();
1077
+ return {
1078
+ async run(sql, params) {
1079
+ const [result] = await conn.query(sql, sanitizeParams(params));
1080
+ const header = result;
1081
+ return {
1082
+ affectedRows: header.affectedRows ?? 0,
1083
+ insertId: header.insertId ?? 0,
1084
+ changedRows: header.changedRows ?? 0
1085
+ };
1086
+ },
1087
+ async all(sql, params) {
1088
+ const [rows] = await conn.query(sql, sanitizeParams(params));
1089
+ return rows;
1090
+ },
1091
+ async get(sql, params) {
1092
+ const [rows] = await conn.query(sql, sanitizeParams(params));
1093
+ return rows[0] ?? null;
1094
+ },
1095
+ async exec(sql) {
1096
+ await conn.query(sql);
1097
+ },
1098
+ release() {
1099
+ conn.release();
1100
+ }
1101
+ };
1102
+ }
1103
+ async close() {
1104
+ const pool = await this.getPool();
1105
+ await pool.end();
1106
+ }
1107
+ constructor(poolOrConfig) {
1108
+ _define_property(this, "pool", void 0);
1109
+ _define_property(this, "poolInit", void 0);
1110
+ if (typeof poolOrConfig === "object" && "execute" in poolOrConfig) this.pool = poolOrConfig;
1111
+ else this.poolInit = import("mysql2/promise").then((mysql) => {
1112
+ if (typeof poolOrConfig === "string") this.pool = mysql.createPool({
1113
+ uri: poolOrConfig,
1114
+ timezone: "+00:00",
1115
+ supportBigNumbers: true,
1116
+ bigNumberStrings: false,
1117
+ typeCast: atscriptTypeCast
1118
+ });
1119
+ else this.pool = mysql.createPool({
1120
+ ...poolOrConfig,
1121
+ timezone: "+00:00",
1122
+ supportBigNumbers: true,
1123
+ bigNumberStrings: false,
1124
+ typeCast: atscriptTypeCast
1125
+ });
1126
+ return this.pool;
1127
+ });
1128
+ }
1129
+ };
1130
+
1131
+ //#endregion
1132
+ //#region packages/db-mysql/src/plugin/annotations.ts
1133
+ const annotations = {
1134
+ engine: new __atscript_core.AnnotationSpec({
1135
+ description: "Specifies the MySQL storage engine.\n\n**Default:** `\"InnoDB\"`\n\n```atscript\n@db.mysql.engine \"MyISAM\"\nexport interface Logs { ... }\n```",
1136
+ nodeType: ["interface"],
1137
+ multiple: false,
1138
+ argument: {
1139
+ name: "engine",
1140
+ type: "string",
1141
+ values: [
1142
+ "InnoDB",
1143
+ "MyISAM",
1144
+ "MEMORY",
1145
+ "CSV",
1146
+ "ARCHIVE"
1147
+ ],
1148
+ description: "MySQL storage engine name."
1149
+ }
1150
+ }),
1151
+ charset: new __atscript_core.AnnotationSpec({
1152
+ 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```",
1153
+ nodeType: ["interface", "prop"],
1154
+ multiple: false,
1155
+ argument: {
1156
+ name: "charset",
1157
+ type: "string",
1158
+ values: [
1159
+ "utf8mb4",
1160
+ "utf8",
1161
+ "latin1",
1162
+ "ascii",
1163
+ "binary"
1164
+ ],
1165
+ description: "MySQL character set name."
1166
+ }
1167
+ }),
1168
+ collate: new __atscript_core.AnnotationSpec({
1169
+ description: "Specifies a native MySQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.mysql.collate \"utf8mb4_turkish_ci\"\nname: string\n```",
1170
+ nodeType: ["interface", "prop"],
1171
+ multiple: false,
1172
+ argument: {
1173
+ name: "collation",
1174
+ type: "string",
1175
+ description: "Native MySQL collation name (e.g., \"utf8mb4_turkish_ci\")."
1176
+ }
1177
+ }),
1178
+ unsigned: new __atscript_core.AnnotationSpec({
1179
+ description: "Adds the UNSIGNED modifier to an integer column.\n\n```atscript\n@db.mysql.unsigned\nage: number.int\n```",
1180
+ nodeType: ["prop"],
1181
+ multiple: false
1182
+ }),
1183
+ type: new __atscript_core.AnnotationSpec({
1184
+ description: "Overrides the native MySQL column type.\n\n```atscript\n@db.mysql.type \"MEDIUMTEXT\"\nbio: string\n```",
1185
+ nodeType: ["prop"],
1186
+ multiple: false,
1187
+ argument: {
1188
+ name: "type",
1189
+ type: "string",
1190
+ description: "Native MySQL column type (e.g., \"MEDIUMTEXT\", \"TINYTEXT\")."
1191
+ }
1192
+ }),
1193
+ onUpdate: new __atscript_core.AnnotationSpec({
1194
+ description: "Sets the MySQL ON UPDATE clause for a column.\n\n```atscript\n@db.mysql.onUpdate \"CURRENT_TIMESTAMP\"\nupdatedAt: number.timestamp\n```",
1195
+ nodeType: ["prop"],
1196
+ multiple: false,
1197
+ argument: {
1198
+ name: "expression",
1199
+ type: "string",
1200
+ values: ["CURRENT_TIMESTAMP"],
1201
+ description: "Expression to evaluate on row update."
1202
+ }
1203
+ })
1204
+ };
1205
+
1206
+ //#endregion
1207
+ //#region packages/db-mysql/src/plugin/index.ts
1208
+ const MysqlPlugin = () => ({
1209
+ name: "mysql",
1210
+ config() {
1211
+ return { annotations: { db: { mysql: annotations } } };
1212
+ }
1213
+ });
1214
+
1215
+ //#endregion
1216
+ //#region packages/db-mysql/src/index.ts
1217
+ function createAdapter(uri, options) {
1218
+ const driver = new Mysql2Driver({
1219
+ uri,
1220
+ ...options
1221
+ });
1222
+ return new __atscript_db.DbSpace(() => new MysqlAdapter(driver));
1223
+ }
1224
+
1225
+ //#endregion
1226
+ exports.Mysql2Driver = Mysql2Driver
1227
+ exports.MysqlAdapter = MysqlAdapter
1228
+ exports.MysqlPlugin = MysqlPlugin
1229
+ exports.buildWhere = buildWhere
1230
+ exports.createAdapter = createAdapter