@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.cjs
CHANGED
|
@@ -1,38 +1,20 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_plugin = require("./plugin-C53PKZHf.cjs");
|
|
3
|
+
let _atscript_db = require("@atscript/db");
|
|
4
|
+
let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
|
|
5
|
+
//#region src/sql-builder.ts
|
|
6
|
+
/** Escapes a MySQL identifier by doubling backticks. */
|
|
30
7
|
function esc(name) {
|
|
31
8
|
return name.replace(/`/g, "``");
|
|
32
9
|
}
|
|
10
|
+
/** Backtick-quotes a single identifier. */
|
|
33
11
|
function qi(name) {
|
|
34
12
|
return `\`${esc(name)}\``;
|
|
35
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Backtick-quotes a table name, handling `schema.table` format.
|
|
16
|
+
* Input is a raw name like `mydb.users` or just `users`.
|
|
17
|
+
*/
|
|
36
18
|
function quoteTableName(name) {
|
|
37
19
|
const dot = name.indexOf(".");
|
|
38
20
|
if (dot >= 0) return `${qi(name.slice(0, dot))}.${qi(name.slice(dot + 1))}`;
|
|
@@ -46,9 +28,9 @@ const mysqlDialect = {
|
|
|
46
28
|
return quoteTableName(name);
|
|
47
29
|
},
|
|
48
30
|
unlimitedLimit: "18446744073709551615",
|
|
49
|
-
toValue:
|
|
31
|
+
toValue: _atscript_db_sql_tools.toSqlValue,
|
|
50
32
|
toParam(value) {
|
|
51
|
-
if (value ===
|
|
33
|
+
if (value === void 0) return null;
|
|
52
34
|
return typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
53
35
|
},
|
|
54
36
|
regex(quotedCol, value) {
|
|
@@ -60,27 +42,51 @@ const mysqlDialect = {
|
|
|
60
42
|
},
|
|
61
43
|
createViewPrefix: "CREATE OR REPLACE VIEW"
|
|
62
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Builds an INSERT statement.
|
|
47
|
+
*/
|
|
63
48
|
function buildInsert(table, data) {
|
|
64
|
-
return (0,
|
|
49
|
+
return (0, _atscript_db_sql_tools.buildInsert)(mysqlDialect, table, data);
|
|
65
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Builds a SELECT statement with optional sort, limit, offset, projection.
|
|
53
|
+
*/
|
|
66
54
|
function buildSelect(table, where, controls) {
|
|
67
|
-
return (0,
|
|
55
|
+
return (0, _atscript_db_sql_tools.buildSelect)(mysqlDialect, table, where, controls);
|
|
68
56
|
}
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Builds an UPDATE ... SET ... WHERE statement with optional LIMIT.
|
|
59
|
+
*/
|
|
60
|
+
function buildUpdate(table, data, where, limit, ops) {
|
|
61
|
+
return (0, _atscript_db_sql_tools.buildUpdate)(mysqlDialect, table, data, where, limit, ops);
|
|
71
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Builds a DELETE ... WHERE statement with optional LIMIT.
|
|
65
|
+
*/
|
|
72
66
|
function buildDelete(table, where, limit) {
|
|
73
|
-
return (0,
|
|
67
|
+
return (0, _atscript_db_sql_tools.buildDelete)(mysqlDialect, table, where, limit);
|
|
74
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Builds a CREATE OR REPLACE VIEW statement from a view plan and column mappings.
|
|
71
|
+
*/
|
|
75
72
|
function buildCreateView(viewName, plan, columns, resolveFieldRef) {
|
|
76
|
-
return (0,
|
|
73
|
+
return (0, _atscript_db_sql_tools.buildCreateView)(mysqlDialect, viewName, plan, columns, resolveFieldRef);
|
|
77
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Builds a SELECT ... GROUP BY statement with aggregate functions.
|
|
77
|
+
*/
|
|
78
78
|
function buildAggregateSelect(table, where, controls) {
|
|
79
|
-
return (0,
|
|
79
|
+
return (0, _atscript_db_sql_tools.buildAggregateSelect)(mysqlDialect, table, where, controls);
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Builds a COUNT query for the number of distinct groups.
|
|
83
|
+
*/
|
|
81
84
|
function buildAggregateCount(table, where, controls) {
|
|
82
|
-
return (0,
|
|
85
|
+
return (0, _atscript_db_sql_tools.buildAggregateCount)(mysqlDialect, table, where, controls);
|
|
83
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Maps portable collation values to MySQL collation names.
|
|
89
|
+
*/
|
|
84
90
|
function collationToMysql(collation) {
|
|
85
91
|
switch (collation) {
|
|
86
92
|
case "binary": return "utf8mb4_bin";
|
|
@@ -98,7 +104,8 @@ function collationToMysql(collation) {
|
|
|
98
104
|
* For FK fields, delegates to the target PK's type via `field.fkTargetField`
|
|
99
105
|
* so the FK column type always matches the referenced column.
|
|
100
106
|
*/
|
|
101
|
-
/** Maps integer primitive tags to MySQL integer types. */
|
|
107
|
+
/** Maps integer primitive tags to MySQL integer types. */
|
|
108
|
+
function intTypeFromTags(tags, unsigned) {
|
|
102
109
|
if (tags?.has("int8")) return unsigned ? "TINYINT UNSIGNED" : "TINYINT";
|
|
103
110
|
if (tags?.has("uint8") || tags?.has("byte")) return "TINYINT UNSIGNED";
|
|
104
111
|
if (tags?.has("int16")) return unsigned ? "SMALLINT UNSIGNED" : "SMALLINT";
|
|
@@ -118,36 +125,36 @@ function mysqlTypeFromField(field) {
|
|
|
118
125
|
const unsigned = metadata?.has("db.mysql.unsigned") ?? false;
|
|
119
126
|
const precision = metadata?.get("db.column.precision");
|
|
120
127
|
switch (field.designType) {
|
|
121
|
-
case "number":
|
|
128
|
+
case "number":
|
|
122
129
|
if (precision) return `DECIMAL(${precision.precision},${precision.scale})`;
|
|
123
130
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") return unsigned ? "BIGINT UNSIGNED" : "BIGINT";
|
|
124
131
|
if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return "TIMESTAMP";
|
|
125
132
|
if (tags?.has("int")) return intTypeFromTags(tags, unsigned);
|
|
126
133
|
return "DOUBLE";
|
|
127
|
-
}
|
|
128
134
|
case "integer": return intTypeFromTags(tags, unsigned);
|
|
129
|
-
case "decimal":
|
|
135
|
+
case "decimal":
|
|
130
136
|
if (precision) return `DECIMAL(${precision.precision},${precision.scale})`;
|
|
131
137
|
return "DECIMAL(10,2)";
|
|
132
|
-
}
|
|
133
138
|
case "boolean": return "TINYINT(1)";
|
|
134
139
|
case "string": {
|
|
135
140
|
if (tags?.has("char")) return "CHAR(1)";
|
|
136
|
-
const maxLen = metadata?.get("expect.maxLength")?.length;
|
|
137
|
-
if (maxLen !==
|
|
138
|
-
if (maxLen !==
|
|
141
|
+
const maxLen = (metadata?.get("expect.maxLength"))?.length;
|
|
142
|
+
if (maxLen !== void 0 && maxLen <= 65535) return `VARCHAR(${maxLen})`;
|
|
143
|
+
if (maxLen !== void 0 && maxLen > 65535) return "LONGTEXT";
|
|
139
144
|
if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
|
|
140
145
|
return "TEXT";
|
|
141
146
|
}
|
|
142
147
|
case "json":
|
|
143
148
|
case "object":
|
|
144
149
|
case "array": return "JSON";
|
|
145
|
-
default:
|
|
150
|
+
default:
|
|
146
151
|
if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
|
|
147
152
|
return "TEXT";
|
|
148
|
-
}
|
|
149
153
|
}
|
|
150
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Builds a CREATE TABLE IF NOT EXISTS statement with MySQL options.
|
|
157
|
+
*/
|
|
151
158
|
function buildCreateTable(table, fields, foreignKeys, options) {
|
|
152
159
|
const colDefs = [];
|
|
153
160
|
const primaryKeys = fields.filter((f) => f.isPrimaryKey);
|
|
@@ -157,14 +164,14 @@ function buildCreateTable(table, fields, foreignKeys, options) {
|
|
|
157
164
|
let def = `${qi(field.physicalName)} ${sqlType}`;
|
|
158
165
|
if (options?.incrementFields?.has(field.physicalName)) def += " AUTO_INCREMENT";
|
|
159
166
|
if (!field.optional && !field.isPrimaryKey && !options?.incrementFields?.has(field.physicalName)) def += " NOT NULL";
|
|
160
|
-
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${(0,
|
|
161
|
-
else if (field.defaultValue?.kind === "fn") {
|
|
167
|
+
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
|
|
168
|
+
else if (field.defaultValue?.kind === "fn") {
|
|
162
169
|
if (field.defaultValue.fn === "uuid") def += " DEFAULT (UUID())";
|
|
163
|
-
else if (field.defaultValue.fn === "now") def += " DEFAULT CURRENT_TIMESTAMP";
|
|
170
|
+
else if (field.defaultValue.fn === "now") def += " DEFAULT CURRENT_TIMESTAMP";
|
|
164
171
|
}
|
|
165
172
|
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
166
173
|
if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
|
|
167
|
-
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
174
|
+
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
168
175
|
const onUpdate = options?.onUpdateFields?.get(field.physicalName);
|
|
169
176
|
if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
|
|
170
177
|
colDefs.push(def);
|
|
@@ -183,8 +190,8 @@ else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
|
183
190
|
const localCols = fk.fields.map((f) => qi(f)).join(", ");
|
|
184
191
|
const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
|
|
185
192
|
let constraint = `FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
|
|
186
|
-
if (fk.onDelete) constraint += ` ON DELETE ${(0,
|
|
187
|
-
if (fk.onUpdate) constraint += ` ON UPDATE ${(0,
|
|
193
|
+
if (fk.onDelete) constraint += ` ON DELETE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
|
|
194
|
+
if (fk.onUpdate) constraint += ` ON UPDATE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
|
|
188
195
|
colDefs.push(constraint);
|
|
189
196
|
}
|
|
190
197
|
let sql = `CREATE TABLE IF NOT EXISTS ${quoteTableName(table)} (${colDefs.join(", ")})`;
|
|
@@ -192,28 +199,23 @@ else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
|
192
199
|
const charset = options?.charset ?? "utf8mb4";
|
|
193
200
|
const collation = options?.collation ?? "utf8mb4_unicode_ci";
|
|
194
201
|
sql += ` ENGINE=${engine} DEFAULT CHARSET=${charset} COLLATE=${collation}`;
|
|
195
|
-
if (options?.autoIncrementStart !==
|
|
202
|
+
if (options?.autoIncrementStart !== void 0) sql += ` AUTO_INCREMENT=${options.autoIncrementStart}`;
|
|
196
203
|
return sql;
|
|
197
204
|
}
|
|
198
|
-
|
|
199
205
|
//#endregion
|
|
200
|
-
//#region
|
|
206
|
+
//#region src/filter-builder.ts
|
|
207
|
+
/**
|
|
208
|
+
* Translates a uniqu filter expression into a parameterized MySQL WHERE clause.
|
|
209
|
+
*
|
|
210
|
+
* @returns `{ sql, params }` — the WHERE clause (without "WHERE") and bound params.
|
|
211
|
+
* Returns `{ sql: '1=1', params: [] }` for empty/null filters.
|
|
212
|
+
*/
|
|
201
213
|
function buildWhere(filter) {
|
|
202
|
-
return (0,
|
|
214
|
+
return (0, _atscript_db_sql_tools.buildWhere)(mysqlDialect, filter);
|
|
203
215
|
}
|
|
204
|
-
|
|
205
216
|
//#endregion
|
|
206
|
-
//#region
|
|
207
|
-
|
|
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
|
+
//#region src/mysql-adapter.ts
|
|
218
|
+
/** Parses a MySQL UTC datetime string ('YYYY-MM-DD HH:MM:SS') to epoch ms. Returns the original value if parsing fails. */
|
|
217
219
|
function utcDatetimeToEpochMs(value) {
|
|
218
220
|
if (typeof value === "number") return value;
|
|
219
221
|
if (value instanceof Date) return value.getTime();
|
|
@@ -223,14 +225,50 @@ function utcDatetimeToEpochMs(value) {
|
|
|
223
225
|
}
|
|
224
226
|
return value;
|
|
225
227
|
}
|
|
226
|
-
/** Formats epoch ms as 'YYYY-MM-DD HH:MM:SS' in UTC for MySQL TIMESTAMP columns. */
|
|
228
|
+
/** Formats epoch ms as 'YYYY-MM-DD HH:MM:SS' in UTC for MySQL TIMESTAMP columns. */
|
|
229
|
+
function epochMsToUtcDatetime(ms) {
|
|
227
230
|
const d = new Date(ms);
|
|
228
231
|
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
232
|
}
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
/**
|
|
234
|
+
* MySQL adapter for {@link AtscriptDbTable}.
|
|
235
|
+
*
|
|
236
|
+
* Accepts any {@link TMysqlDriver} implementation — the actual MySQL driver
|
|
237
|
+
* is fully swappable (mysql2/promise pool, custom implementations, etc.).
|
|
238
|
+
*
|
|
239
|
+
* Usage:
|
|
240
|
+
* ```typescript
|
|
241
|
+
* import { Mysql2Driver, MysqlAdapter } from '@atscript/db-mysql'
|
|
242
|
+
* import { DbSpace } from '@atscript/db'
|
|
243
|
+
*
|
|
244
|
+
* const driver = new Mysql2Driver('mysql://root@localhost:3306/mydb')
|
|
245
|
+
* const space = new DbSpace(() => new MysqlAdapter(driver))
|
|
246
|
+
* const users = space.getTable(UsersType)
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
250
|
+
supportsColumnModify = true;
|
|
251
|
+
static NATIVE_DEFAULT_FNS = new Set(["now", "increment"]);
|
|
252
|
+
_engine = "InnoDB";
|
|
253
|
+
_charset = "utf8mb4";
|
|
254
|
+
_collation = "utf8mb4_unicode_ci";
|
|
255
|
+
_autoIncrementStart;
|
|
256
|
+
_incrementFields = /* @__PURE__ */ new Set();
|
|
257
|
+
_onUpdateFields = /* @__PURE__ */ new Map();
|
|
258
|
+
/** Whether the connected MySQL instance supports native VECTOR type (MySQL 9.0+). */
|
|
259
|
+
_supportsVector;
|
|
260
|
+
/** Vector fields: physical field name → { dimensions, similarity, indexName }. */
|
|
261
|
+
_vectorFields = /* @__PURE__ */ new Map();
|
|
262
|
+
/** Default similarity thresholds per vector field (from @db.search.vector.threshold). */
|
|
263
|
+
_vectorThresholds = /* @__PURE__ */ new Map();
|
|
264
|
+
/** Schema name for INFORMATION_SCHEMA queries (null falls through to DATABASE()). */
|
|
265
|
+
get _schema() {
|
|
232
266
|
return this._table.schema ?? null;
|
|
233
267
|
}
|
|
268
|
+
constructor(driver) {
|
|
269
|
+
super();
|
|
270
|
+
this.driver = driver;
|
|
271
|
+
}
|
|
234
272
|
async _beginTransaction() {
|
|
235
273
|
const conn = await this.driver.getConnection();
|
|
236
274
|
await conn.exec("START TRANSACTION");
|
|
@@ -258,11 +296,12 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
258
296
|
/**
|
|
259
297
|
* Returns the active executor: dedicated connection if inside a transaction,
|
|
260
298
|
* otherwise the pool-based driver.
|
|
261
|
-
*/
|
|
262
|
-
|
|
263
|
-
return
|
|
299
|
+
*/
|
|
300
|
+
_exec() {
|
|
301
|
+
return this._getTransactionState() ?? this.driver;
|
|
264
302
|
}
|
|
265
|
-
/** MySQL InnoDB enforces FK constraints natively. */
|
|
303
|
+
/** MySQL InnoDB enforces FK constraints natively. */
|
|
304
|
+
supportsNativeForeignKeys() {
|
|
266
305
|
return true;
|
|
267
306
|
}
|
|
268
307
|
prepareId(id, _fieldType) {
|
|
@@ -274,8 +313,8 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
274
313
|
nativeDefaultFns() {
|
|
275
314
|
return MysqlAdapter.NATIVE_DEFAULT_FNS;
|
|
276
315
|
}
|
|
277
|
-
onBeforeFlatten(
|
|
278
|
-
const meta =
|
|
316
|
+
onBeforeFlatten(_type) {
|
|
317
|
+
const meta = _type.metadata;
|
|
279
318
|
const engine = meta.get("db.mysql.engine");
|
|
280
319
|
if (engine) this._engine = engine;
|
|
281
320
|
const charset = meta.get("db.mysql.charset");
|
|
@@ -300,7 +339,7 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
300
339
|
indexName
|
|
301
340
|
});
|
|
302
341
|
const threshold = metadata.get("db.search.vector.threshold");
|
|
303
|
-
if (threshold !==
|
|
342
|
+
if (threshold !== void 0) this._vectorThresholds.set(indexName, threshold);
|
|
304
343
|
}
|
|
305
344
|
}
|
|
306
345
|
getDesiredTableOptions() {
|
|
@@ -344,18 +383,15 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
344
383
|
const tableName = this.resolveTableName();
|
|
345
384
|
const clauses = [];
|
|
346
385
|
for (const change of changes) switch (change.key) {
|
|
347
|
-
case "engine":
|
|
386
|
+
case "engine":
|
|
348
387
|
clauses.push(`ENGINE = ${change.newValue}`);
|
|
349
388
|
break;
|
|
350
|
-
|
|
351
|
-
case "charset": {
|
|
389
|
+
case "charset":
|
|
352
390
|
clauses.push(`CHARACTER SET = ${change.newValue}`);
|
|
353
391
|
break;
|
|
354
|
-
|
|
355
|
-
case "collation": {
|
|
392
|
+
case "collation":
|
|
356
393
|
clauses.push(`COLLATE = ${change.newValue}`);
|
|
357
394
|
break;
|
|
358
|
-
}
|
|
359
395
|
}
|
|
360
396
|
if (clauses.length > 0) {
|
|
361
397
|
const ddl = `ALTER TABLE ${quoteTableName(tableName)} ${clauses.join(", ")}`;
|
|
@@ -367,12 +403,12 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
367
403
|
* Returns a value formatter for TIMESTAMP-mapped fields.
|
|
368
404
|
* Number fields with @db.default.now map to MySQL TIMESTAMP — the formatter
|
|
369
405
|
* converts epoch ms to a UTC datetime string for the wire protocol.
|
|
370
|
-
*/
|
|
406
|
+
*/
|
|
407
|
+
formatValue(field) {
|
|
371
408
|
if (field.designType === "number" && field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return {
|
|
372
409
|
toStorage: (value) => typeof value === "number" ? epochMsToUtcDatetime(value) : value,
|
|
373
410
|
fromStorage: utcDatetimeToEpochMs
|
|
374
411
|
};
|
|
375
|
-
return undefined;
|
|
376
412
|
}
|
|
377
413
|
/**
|
|
378
414
|
* Wraps an async write operation to catch MySQL constraint errors
|
|
@@ -382,24 +418,18 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
382
418
|
* - 1062 = ER_DUP_ENTRY (unique constraint violation)
|
|
383
419
|
* - 1451 = ER_ROW_IS_REFERENCED_2 (FK violation on delete)
|
|
384
420
|
* - 1452 = ER_NO_REFERENCED_ROW_2 (FK violation on insert/update)
|
|
385
|
-
*/
|
|
421
|
+
*/
|
|
422
|
+
async _wrapConstraintError(fn) {
|
|
386
423
|
try {
|
|
387
424
|
return await fn();
|
|
388
425
|
} catch (error) {
|
|
389
426
|
if (error && typeof error === "object" && "errno" in error) {
|
|
390
427
|
const err = error;
|
|
391
|
-
if (err.errno === 1062) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
}
|
|
428
|
+
if (err.errno === 1062) throw new _atscript_db.DbError("CONFLICT", [{
|
|
429
|
+
path: (err.message?.match(/for key '(?:\w+\.)?(\w+)'/))?.[1] ?? "",
|
|
430
|
+
message: err.sqlMessage ?? err.message
|
|
431
|
+
}]);
|
|
432
|
+
if (err.errno === 1451 || err.errno === 1452) throw new _atscript_db.DbError("FK_VIOLATION", this._mapFkError(err.message));
|
|
403
433
|
}
|
|
404
434
|
throw error;
|
|
405
435
|
}
|
|
@@ -408,9 +438,8 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
408
438
|
const fkMatch = message.match(/FOREIGN KEY \(`(\w+)`\)/);
|
|
409
439
|
if (fkMatch) {
|
|
410
440
|
const physicalCol = fkMatch[1];
|
|
411
|
-
const field = this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol);
|
|
412
441
|
return [{
|
|
413
|
-
path:
|
|
442
|
+
path: this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol)?.path ?? physicalCol,
|
|
414
443
|
message
|
|
415
444
|
}];
|
|
416
445
|
}
|
|
@@ -477,28 +506,25 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
477
506
|
}
|
|
478
507
|
async count(query) {
|
|
479
508
|
const where = buildWhere(query.filter);
|
|
480
|
-
const
|
|
481
|
-
const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${where.sql}`;
|
|
509
|
+
const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(this.resolveTableName())} WHERE ${where.sql}`;
|
|
482
510
|
this._log(sql, where.params);
|
|
483
|
-
|
|
484
|
-
return row?.cnt ?? 0;
|
|
511
|
+
return (await this._exec().get(sql, where.params))?.cnt ?? 0;
|
|
485
512
|
}
|
|
486
513
|
async aggregate(query) {
|
|
487
514
|
const where = buildWhere(query.filter);
|
|
488
515
|
const tableName = this.resolveTableName();
|
|
489
516
|
if (query.controls.$count) {
|
|
490
|
-
const { sql
|
|
491
|
-
this._log(sql
|
|
492
|
-
|
|
493
|
-
return [{ count: row?.count ?? 0 }];
|
|
517
|
+
const { sql, params } = buildAggregateCount(tableName, where, query.controls);
|
|
518
|
+
this._log(sql, params);
|
|
519
|
+
return [{ count: (await this._exec().get(sql, params))?.count ?? 0 }];
|
|
494
520
|
}
|
|
495
521
|
const { sql, params } = buildAggregateSelect(tableName, where, query.controls);
|
|
496
522
|
this._log(sql, params);
|
|
497
523
|
return this._exec().all(sql, params);
|
|
498
524
|
}
|
|
499
|
-
async updateOne(filter, data) {
|
|
525
|
+
async updateOne(filter, data, ops) {
|
|
500
526
|
const where = buildWhere(filter);
|
|
501
|
-
const { sql, params } = buildUpdate(this.resolveTableName(), data, where, 1);
|
|
527
|
+
const { sql, params } = buildUpdate(this.resolveTableName(), data, where, 1, ops);
|
|
502
528
|
this._log(sql, params);
|
|
503
529
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
504
530
|
return {
|
|
@@ -506,9 +532,9 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
506
532
|
modifiedCount: result.changedRows
|
|
507
533
|
};
|
|
508
534
|
}
|
|
509
|
-
async updateMany(filter, data) {
|
|
535
|
+
async updateMany(filter, data, ops) {
|
|
510
536
|
const where = buildWhere(filter);
|
|
511
|
-
const { sql, params } = buildUpdate(this.resolveTableName(), data, where);
|
|
537
|
+
const { sql, params } = buildUpdate(this.resolveTableName(), data, where, void 0, ops);
|
|
512
538
|
this._log(sql, params);
|
|
513
539
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
514
540
|
return {
|
|
@@ -540,19 +566,17 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
540
566
|
const where = buildWhere(filter);
|
|
541
567
|
const { sql, params } = buildDelete(this.resolveTableName(), where, 1);
|
|
542
568
|
this._log(sql, params);
|
|
543
|
-
|
|
544
|
-
return { deletedCount: result.affectedRows };
|
|
569
|
+
return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
|
|
545
570
|
}
|
|
546
571
|
async deleteMany(filter) {
|
|
547
572
|
const where = buildWhere(filter);
|
|
548
573
|
const { sql, params } = buildDelete(this.resolveTableName(), where);
|
|
549
574
|
this._log(sql, params);
|
|
550
|
-
|
|
551
|
-
return { deletedCount: result.affectedRows };
|
|
575
|
+
return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
|
|
552
576
|
}
|
|
553
577
|
async ensureTable() {
|
|
554
|
-
if (this._supportsVector ===
|
|
555
|
-
if (this._table instanceof
|
|
578
|
+
if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
|
|
579
|
+
if (this._table instanceof _atscript_db.AtscriptDbView) return this._ensureView();
|
|
556
580
|
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
|
|
557
581
|
engine: this._engine,
|
|
558
582
|
charset: this._charset,
|
|
@@ -575,11 +599,10 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
575
599
|
}
|
|
576
600
|
async getExistingColumnsForTable(tableName) {
|
|
577
601
|
const schema = this._schema;
|
|
578
|
-
|
|
602
|
+
return (await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT
|
|
579
603
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
580
604
|
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
581
|
-
ORDER BY ORDINAL_POSITION`, [tableName, schema])
|
|
582
|
-
return rows.map((r) => ({
|
|
605
|
+
ORDER BY ORDINAL_POSITION`, [tableName, schema])).map((r) => ({
|
|
583
606
|
name: r.COLUMN_NAME,
|
|
584
607
|
type: r.COLUMN_TYPE.toUpperCase(),
|
|
585
608
|
notnull: r.IS_NULLABLE === "NO",
|
|
@@ -601,8 +624,8 @@ var MysqlAdapter = class MysqlAdapter extends __atscript_db.BaseDbAdapter {
|
|
|
601
624
|
const sqlType = this.typeMapper(field);
|
|
602
625
|
let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
|
|
603
626
|
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
604
|
-
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0,
|
|
605
|
-
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0,
|
|
627
|
+
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
|
|
628
|
+
else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueForType)(field.designType)}`;
|
|
606
629
|
if (field.collate) {
|
|
607
630
|
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
608
631
|
ddl += ` COLLATE ${nativeCollate ?? collationToMysql(field.collate)}`;
|
|
@@ -629,9 +652,9 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${(0, __atscri
|
|
|
629
652
|
const sqlType = this.typeMapper(field);
|
|
630
653
|
let ddl = `ALTER TABLE ${quoteTableName(tableName)} MODIFY COLUMN ${qi(field.physicalName)} ${sqlType}`;
|
|
631
654
|
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
632
|
-
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0,
|
|
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";
|
|
655
|
+
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
|
|
656
|
+
else if (field.defaultValue?.kind === "fn") ddl += ` DEFAULT ${field.defaultValue.fn === "now" ? "CURRENT_TIMESTAMP" : field.defaultValue.fn === "uuid" ? "(UUID())" : `(${field.defaultValue.fn}())`}`;
|
|
657
|
+
else ddl += " DEFAULT NULL";
|
|
635
658
|
this._log(ddl);
|
|
636
659
|
await this._exec().exec(ddl);
|
|
637
660
|
}
|
|
@@ -665,7 +688,7 @@ else ddl += " DEFAULT NULL";
|
|
|
665
688
|
const selectExprs = commonCols.map((c) => {
|
|
666
689
|
const field = fieldsByName.get(c);
|
|
667
690
|
if (field && !field.optional && !field.isPrimaryKey) {
|
|
668
|
-
const fallback = field.defaultValue?.kind === "value" ? (0,
|
|
691
|
+
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
692
|
return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
|
|
670
693
|
}
|
|
671
694
|
return qi(c);
|
|
@@ -741,10 +764,7 @@ else ddl += " DEFAULT NULL";
|
|
|
741
764
|
const fulltext = index.type === "fulltext" ? "FULLTEXT " : "";
|
|
742
765
|
const isFulltext = index.type === "fulltext";
|
|
743
766
|
const cols = index.fields.map((f) => {
|
|
744
|
-
|
|
745
|
-
const prefix = !isFulltext && stringFields.has(f.name) ? "(255)" : "";
|
|
746
|
-
const order = isFulltext ? "" : ` ${f.sort === "desc" ? "DESC" : "ASC"}`;
|
|
747
|
-
return `${col}${prefix}${order}`;
|
|
767
|
+
return `${qi(f.name)}${!isFulltext && stringFields.has(f.name) ? "(255)" : ""}${isFulltext ? "" : ` ${f.sort === "desc" ? "DESC" : "ASC"}`}`;
|
|
748
768
|
}).join(", ");
|
|
749
769
|
const sql = `CREATE ${fulltext}${unique}INDEX ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${cols})`;
|
|
750
770
|
this._log(sql);
|
|
@@ -759,25 +779,25 @@ else ddl += " DEFAULT NULL";
|
|
|
759
779
|
}
|
|
760
780
|
async syncForeignKeys() {
|
|
761
781
|
const existingByName = await this._getExistingFkConstraints();
|
|
762
|
-
const desiredFkKeys = new Set();
|
|
763
|
-
for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].
|
|
782
|
+
const desiredFkKeys = /* @__PURE__ */ new Set();
|
|
783
|
+
for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].toSorted().join(","));
|
|
764
784
|
for (const [constraintName, columns] of existingByName) {
|
|
765
|
-
const key = columns.
|
|
785
|
+
const key = columns.toSorted().join(",");
|
|
766
786
|
if (!desiredFkKeys.has(key)) {
|
|
767
787
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP FOREIGN KEY ${qi(constraintName)}`;
|
|
768
788
|
this._log(ddl);
|
|
769
789
|
await this._exec().exec(ddl);
|
|
770
790
|
}
|
|
771
791
|
}
|
|
772
|
-
const existingKeys = new Set([...existingByName.values()].map((cols) => cols.
|
|
792
|
+
const existingKeys = new Set([...existingByName.values()].map((cols) => cols.toSorted().join(",")));
|
|
773
793
|
for (const fk of this._table.foreignKeys.values()) {
|
|
774
|
-
const key = [...fk.fields].
|
|
794
|
+
const key = [...fk.fields].toSorted().join(",");
|
|
775
795
|
if (!existingKeys.has(key)) {
|
|
776
796
|
const localCols = fk.fields.map((f) => qi(f)).join(", ");
|
|
777
797
|
const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
|
|
778
798
|
let ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ADD FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
|
|
779
|
-
if (fk.onDelete) ddl += ` ON DELETE ${(0,
|
|
780
|
-
if (fk.onUpdate) ddl += ` ON UPDATE ${(0,
|
|
799
|
+
if (fk.onDelete) ddl += ` ON DELETE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
|
|
800
|
+
if (fk.onUpdate) ddl += ` ON UPDATE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
|
|
781
801
|
this._log(ddl);
|
|
782
802
|
await this._exec().exec(ddl);
|
|
783
803
|
}
|
|
@@ -788,7 +808,7 @@ else ddl += " DEFAULT NULL";
|
|
|
788
808
|
const keySet = new Set(fkFieldKeys);
|
|
789
809
|
const existingByName = await this._getExistingFkConstraints();
|
|
790
810
|
for (const [constraintName, cols] of existingByName) {
|
|
791
|
-
const key = cols.
|
|
811
|
+
const key = cols.toSorted().join(",");
|
|
792
812
|
if (keySet.has(key)) {
|
|
793
813
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP FOREIGN KEY ${qi(constraintName)}`;
|
|
794
814
|
this._log(ddl);
|
|
@@ -796,12 +816,13 @@ else ddl += " DEFAULT NULL";
|
|
|
796
816
|
}
|
|
797
817
|
}
|
|
798
818
|
}
|
|
799
|
-
/** Queries INFORMATION_SCHEMA for existing FK constraints, grouped by constraint name → column names. */
|
|
819
|
+
/** Queries INFORMATION_SCHEMA for existing FK constraints, grouped by constraint name → column names. */
|
|
820
|
+
async _getExistingFkConstraints() {
|
|
800
821
|
const rows = await this._exec().all(`SELECT kcu.CONSTRAINT_NAME, kcu.COLUMN_NAME
|
|
801
822
|
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
|
802
823
|
WHERE kcu.TABLE_NAME = ? AND kcu.TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
803
824
|
AND kcu.REFERENCED_TABLE_NAME IS NOT NULL`, [this._table.tableName, this._schema]);
|
|
804
|
-
const byName = new Map();
|
|
825
|
+
const byName = /* @__PURE__ */ new Map();
|
|
805
826
|
for (const row of rows) {
|
|
806
827
|
let cols = byName.get(row.CONSTRAINT_NAME);
|
|
807
828
|
if (!cols) {
|
|
@@ -843,8 +864,7 @@ else ddl += " DEFAULT NULL";
|
|
|
843
864
|
const countPromise = (async () => {
|
|
844
865
|
const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${combinedWhere.sql}`;
|
|
845
866
|
this._log(sql, combinedWhere.params);
|
|
846
|
-
|
|
847
|
-
return row?.cnt ?? 0;
|
|
867
|
+
return (await this._exec().get(sql, combinedWhere.params))?.cnt ?? 0;
|
|
848
868
|
})();
|
|
849
869
|
const [data, count] = await Promise.all([selectPromise, countPromise]);
|
|
850
870
|
return {
|
|
@@ -867,14 +887,14 @@ else ddl += " DEFAULT NULL";
|
|
|
867
887
|
for (const index of this._table.indexes.values()) if (index.type === "fulltext") {
|
|
868
888
|
if (!indexName || index.key === indexName) return index;
|
|
869
889
|
}
|
|
870
|
-
return undefined;
|
|
871
890
|
}
|
|
872
891
|
/**
|
|
873
892
|
* Detects native VECTOR type support by inspecting the server version.
|
|
874
893
|
* MySQL 9.0+ supports the VECTOR column type natively.
|
|
875
894
|
* Caches the result for the lifetime of this adapter instance.
|
|
876
|
-
*/
|
|
877
|
-
|
|
895
|
+
*/
|
|
896
|
+
async _detectVectorSupport() {
|
|
897
|
+
if (this._supportsVector !== void 0) return this._supportsVector;
|
|
878
898
|
try {
|
|
879
899
|
const row = await this.driver.get("SELECT VERSION() as v", []);
|
|
880
900
|
if (row?.v) {
|
|
@@ -909,7 +929,8 @@ else ddl += " DEFAULT NULL";
|
|
|
909
929
|
count: countRow?.cnt ?? 0
|
|
910
930
|
};
|
|
911
931
|
}
|
|
912
|
-
/** Resolves vector field and computes shared context for vector search SQL builders. */
|
|
932
|
+
/** Resolves vector field and computes shared context for vector search SQL builders. */
|
|
933
|
+
_prepareVectorSearch(vector, query, indexName) {
|
|
913
934
|
let field;
|
|
914
935
|
let vec;
|
|
915
936
|
if (indexName) {
|
|
@@ -947,13 +968,13 @@ else ddl += " DEFAULT NULL";
|
|
|
947
968
|
const inner = `SELECT *, ${ctx.distanceFn}(${qi(ctx.field)}, STRING_TO_VECTOR(?)) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${ctx.where.sql}`;
|
|
948
969
|
const params = [ctx.vectorStr, ...ctx.where.params];
|
|
949
970
|
let sql = `SELECT * FROM (${inner}) _v`;
|
|
950
|
-
if (ctx.threshold !==
|
|
971
|
+
if (ctx.threshold !== void 0) {
|
|
951
972
|
sql += ` WHERE _distance <= ?`;
|
|
952
973
|
params.push(2 * (1 - ctx.threshold));
|
|
953
974
|
}
|
|
954
975
|
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}`;
|
|
976
|
+
if (ctx.controls.$skip) sql += ` LIMIT ${Number(ctx.controls.$limit) || 1e3} OFFSET ${Number(ctx.controls.$skip)}`;
|
|
977
|
+
else sql += ` LIMIT ${Number(ctx.controls.$limit) || 20}`;
|
|
957
978
|
return {
|
|
958
979
|
sql,
|
|
959
980
|
params
|
|
@@ -964,7 +985,7 @@ else sql += ` LIMIT ${ctx.controls.$limit || 20}`;
|
|
|
964
985
|
const inner = `SELECT ${ctx.distanceFn}(${qi(ctx.field)}, STRING_TO_VECTOR(?)) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${ctx.where.sql}`;
|
|
965
986
|
const params = [ctx.vectorStr, ...ctx.where.params];
|
|
966
987
|
let sql = `SELECT COUNT(*) AS cnt FROM (${inner}) _v`;
|
|
967
|
-
if (ctx.threshold !==
|
|
988
|
+
if (ctx.threshold !== void 0) {
|
|
968
989
|
sql += ` WHERE _distance <= ?`;
|
|
969
990
|
params.push(2 * (1 - ctx.threshold));
|
|
970
991
|
}
|
|
@@ -973,16 +994,13 @@ else sql += ` LIMIT ${ctx.controls.$limit || 20}`;
|
|
|
973
994
|
params
|
|
974
995
|
};
|
|
975
996
|
}
|
|
976
|
-
/** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
|
|
997
|
+
/** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
|
|
998
|
+
_resolveVectorThreshold(controls, indexName) {
|
|
977
999
|
const queryThreshold = controls.$threshold;
|
|
978
|
-
if (queryThreshold !==
|
|
1000
|
+
if (queryThreshold !== void 0) return queryThreshold;
|
|
979
1001
|
return this._vectorThresholds.get(indexName);
|
|
980
1002
|
}
|
|
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
1003
|
};
|
|
985
|
-
_define_property$1(MysqlAdapter, "NATIVE_DEFAULT_FNS", new Set(["now", "increment"]));
|
|
986
1004
|
/**
|
|
987
1005
|
* Normalizes MySQL INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT values
|
|
988
1006
|
* to match the format produced by `serializeDefaultValue()`.
|
|
@@ -991,47 +1009,41 @@ _define_property$1(MysqlAdapter, "NATIVE_DEFAULT_FNS", new Set(["now", "incremen
|
|
|
991
1009
|
* `uuid()`), but the diff engine compares against serialized form (`fn:now`,
|
|
992
1010
|
* `fn:uuid`). Without normalization, every table with function defaults
|
|
993
1011
|
* produces phantom ALTER diffs on re-plan.
|
|
994
|
-
*/
|
|
995
|
-
|
|
1012
|
+
*/
|
|
1013
|
+
function normalizeMysqlDefault(value) {
|
|
1014
|
+
if (value === null) return;
|
|
996
1015
|
const lower = value.toLowerCase();
|
|
997
1016
|
if (lower === "current_timestamp" || lower === "current_timestamp()") return "fn:now";
|
|
998
1017
|
if (lower === "uuid()") return "fn:uuid";
|
|
999
1018
|
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1).replace(/''/g, "'");
|
|
1000
1019
|
return value;
|
|
1001
1020
|
}
|
|
1002
|
-
/** Maps generic similarity metric to MySQL 9+ distance function name. */
|
|
1021
|
+
/** Maps generic similarity metric to MySQL 9+ distance function name. */
|
|
1022
|
+
function similarityToMysqlFn(similarity) {
|
|
1003
1023
|
switch (similarity) {
|
|
1004
1024
|
case "euclidean": return "VEC_DISTANCE_EUCLIDEAN";
|
|
1005
1025
|
case "dotProduct": return "VEC_DISTANCE_DOT";
|
|
1006
1026
|
default: return "VEC_DISTANCE_COSINE";
|
|
1007
1027
|
}
|
|
1008
1028
|
}
|
|
1009
|
-
/** Formats a number[] vector as MySQL's STRING_TO_VECTOR input: '[1.0, 2.0, ...]'. */
|
|
1029
|
+
/** Formats a number[] vector as MySQL's STRING_TO_VECTOR input: '[1.0, 2.0, ...]'. */
|
|
1030
|
+
function vectorToString(vector) {
|
|
1010
1031
|
return `[${vector.join(",")}]`;
|
|
1011
1032
|
}
|
|
1012
|
-
|
|
1013
1033
|
//#endregion
|
|
1014
|
-
//#region
|
|
1015
|
-
|
|
1016
|
-
|
|
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) {
|
|
1034
|
+
//#region src/mysql2-driver.ts
|
|
1035
|
+
/** mysql2 rejects `undefined` in bind arrays — coerce to `null`. */
|
|
1036
|
+
function sanitizeParams(params) {
|
|
1026
1037
|
if (!params) return [];
|
|
1027
|
-
return params.map((v) => v ===
|
|
1038
|
+
return params.map((v) => v === void 0 ? null : v);
|
|
1028
1039
|
}
|
|
1029
1040
|
/**
|
|
1030
1041
|
* Custom type-casting for mysql2 result columns to maintain cross-adapter consistency.
|
|
1031
1042
|
*
|
|
1032
1043
|
* - TIMESTAMP/DATETIME → epoch milliseconds (number) instead of Date objects
|
|
1033
1044
|
* - DECIMAL/NEWDECIMAL → number instead of string
|
|
1034
|
-
*/
|
|
1045
|
+
*/
|
|
1046
|
+
function atscriptTypeCast(field, next) {
|
|
1035
1047
|
if (field.type === "TIMESTAMP" || field.type === "DATETIME") {
|
|
1036
1048
|
const str = field.string();
|
|
1037
1049
|
if (str === null) return null;
|
|
@@ -1043,13 +1055,66 @@ else obj[key] = value;
|
|
|
1043
1055
|
}
|
|
1044
1056
|
return next();
|
|
1045
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* {@link TMysqlDriver} implementation backed by `mysql2/promise`.
|
|
1060
|
+
*
|
|
1061
|
+
* Accepts a connection URI string, a `PoolOptions` object, or a pre-created
|
|
1062
|
+
* `Pool` instance from `mysql2/promise`.
|
|
1063
|
+
*
|
|
1064
|
+
* ```typescript
|
|
1065
|
+
* import { Mysql2Driver } from '@atscript/db-mysql'
|
|
1066
|
+
*
|
|
1067
|
+
* // Connection URI
|
|
1068
|
+
* const driver = new Mysql2Driver('mysql://root:pass@localhost:3306/mydb')
|
|
1069
|
+
*
|
|
1070
|
+
* // Pool options
|
|
1071
|
+
* const driver = new Mysql2Driver({
|
|
1072
|
+
* host: 'localhost',
|
|
1073
|
+
* user: 'root',
|
|
1074
|
+
* database: 'mydb',
|
|
1075
|
+
* waitForConnections: true,
|
|
1076
|
+
* connectionLimit: 10,
|
|
1077
|
+
* })
|
|
1078
|
+
*
|
|
1079
|
+
* // Pre-created pool
|
|
1080
|
+
* import mysql from 'mysql2/promise'
|
|
1081
|
+
* const pool = mysql.createPool({ host: 'localhost', database: 'mydb' })
|
|
1082
|
+
* const driver = new Mysql2Driver(pool)
|
|
1083
|
+
* ```
|
|
1084
|
+
*
|
|
1085
|
+
* Requires `mysql2` to be installed:
|
|
1086
|
+
* ```bash
|
|
1087
|
+
* pnpm add mysql2
|
|
1088
|
+
* ```
|
|
1089
|
+
*/
|
|
1046
1090
|
var Mysql2Driver = class {
|
|
1091
|
+
pool;
|
|
1092
|
+
poolInit;
|
|
1093
|
+
constructor(poolOrConfig) {
|
|
1094
|
+
if (typeof poolOrConfig === "object" && "execute" in poolOrConfig) this.pool = poolOrConfig;
|
|
1095
|
+
else this.poolInit = import("mysql2/promise").then((mysql) => {
|
|
1096
|
+
if (typeof poolOrConfig === "string") this.pool = mysql.createPool({
|
|
1097
|
+
uri: poolOrConfig,
|
|
1098
|
+
timezone: "+00:00",
|
|
1099
|
+
supportBigNumbers: true,
|
|
1100
|
+
bigNumberStrings: false,
|
|
1101
|
+
typeCast: atscriptTypeCast
|
|
1102
|
+
});
|
|
1103
|
+
else this.pool = mysql.createPool({
|
|
1104
|
+
...poolOrConfig,
|
|
1105
|
+
timezone: "+00:00",
|
|
1106
|
+
supportBigNumbers: true,
|
|
1107
|
+
bigNumberStrings: false,
|
|
1108
|
+
typeCast: atscriptTypeCast
|
|
1109
|
+
});
|
|
1110
|
+
return this.pool;
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1047
1113
|
getPool() {
|
|
1048
1114
|
return this.pool || this.poolInit;
|
|
1049
1115
|
}
|
|
1050
1116
|
async run(sql, params) {
|
|
1051
|
-
const
|
|
1052
|
-
const [result] = await pool.query(sql, sanitizeParams(params));
|
|
1117
|
+
const [result] = await (await this.getPool()).query(sql, sanitizeParams(params));
|
|
1053
1118
|
const header = result;
|
|
1054
1119
|
return {
|
|
1055
1120
|
affectedRows: header.affectedRows ?? 0,
|
|
@@ -1058,22 +1123,18 @@ var Mysql2Driver = class {
|
|
|
1058
1123
|
};
|
|
1059
1124
|
}
|
|
1060
1125
|
async all(sql, params) {
|
|
1061
|
-
const
|
|
1062
|
-
const [rows] = await pool.query(sql, sanitizeParams(params));
|
|
1126
|
+
const [rows] = await (await this.getPool()).query(sql, sanitizeParams(params));
|
|
1063
1127
|
return rows;
|
|
1064
1128
|
}
|
|
1065
1129
|
async get(sql, params) {
|
|
1066
|
-
const
|
|
1067
|
-
const [rows] = await pool.query(sql, sanitizeParams(params));
|
|
1130
|
+
const [rows] = await (await this.getPool()).query(sql, sanitizeParams(params));
|
|
1068
1131
|
return rows[0] ?? null;
|
|
1069
1132
|
}
|
|
1070
1133
|
async exec(sql) {
|
|
1071
|
-
|
|
1072
|
-
await pool.query(sql);
|
|
1134
|
+
await (await this.getPool()).query(sql);
|
|
1073
1135
|
}
|
|
1074
1136
|
async getConnection() {
|
|
1075
|
-
const
|
|
1076
|
-
const conn = await pool.getConnection();
|
|
1137
|
+
const conn = await (await this.getPool()).getConnection();
|
|
1077
1138
|
return {
|
|
1078
1139
|
async run(sql, params) {
|
|
1079
1140
|
const [result] = await conn.query(sql, sanitizeParams(params));
|
|
@@ -1101,130 +1162,28 @@ var Mysql2Driver = class {
|
|
|
1101
1162
|
};
|
|
1102
1163
|
}
|
|
1103
1164
|
async close() {
|
|
1104
|
-
|
|
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
|
-
});
|
|
1165
|
+
await (await this.getPool()).end();
|
|
1128
1166
|
}
|
|
1129
1167
|
};
|
|
1130
|
-
|
|
1131
1168
|
//#endregion
|
|
1132
|
-
//#region
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
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
|
|
1169
|
+
//#region src/index.ts
|
|
1170
|
+
/**
|
|
1171
|
+
* Creates a {@link DbSpace} backed by a MySQL connection pool.
|
|
1172
|
+
*
|
|
1173
|
+
* @param uri - MySQL connection URI (e.g., `mysql://root@localhost:3306/mydb`)
|
|
1174
|
+
* @param options - Additional pool options passed to mysql2.
|
|
1175
|
+
* @returns A `DbSpace` that creates `MysqlAdapter` instances per table.
|
|
1176
|
+
*/
|
|
1217
1177
|
function createAdapter(uri, options) {
|
|
1218
1178
|
const driver = new Mysql2Driver({
|
|
1219
1179
|
uri,
|
|
1220
1180
|
...options
|
|
1221
1181
|
});
|
|
1222
|
-
return new
|
|
1182
|
+
return new _atscript_db.DbSpace(() => new MysqlAdapter(driver));
|
|
1223
1183
|
}
|
|
1224
|
-
|
|
1225
1184
|
//#endregion
|
|
1226
|
-
exports.Mysql2Driver = Mysql2Driver
|
|
1227
|
-
exports.MysqlAdapter = MysqlAdapter
|
|
1228
|
-
exports.MysqlPlugin = MysqlPlugin
|
|
1229
|
-
exports.buildWhere = buildWhere
|
|
1230
|
-
exports.createAdapter = createAdapter
|
|
1185
|
+
exports.Mysql2Driver = Mysql2Driver;
|
|
1186
|
+
exports.MysqlAdapter = MysqlAdapter;
|
|
1187
|
+
exports.MysqlPlugin = require_plugin.MysqlPlugin;
|
|
1188
|
+
exports.buildWhere = buildWhere;
|
|
1189
|
+
exports.createAdapter = createAdapter;
|