@atscript/db-mysql 0.1.127 → 0.1.128
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 +329 -92
- package/dist/index.d.cts +33 -4
- package/dist/index.d.mts +33 -4
- package/dist/index.mjs +331 -94
- package/package.json +10 -10
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,124 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let _atscript_db = require("@atscript/db");
|
|
3
3
|
let _atscript_db_sql_tools = require("@atscript/db-sql-tools");
|
|
4
4
|
//#region src/sql-builder.ts
|
|
5
|
+
/** TEXT / BLOB families (`TINYTEXT` … `LONGBLOB`). */
|
|
6
|
+
const TEXT_BLOB_RE = /^(TINY|MEDIUM|LONG)?(TEXT|BLOB)\b/i;
|
|
7
|
+
/** Spatial types (`POINT SRID 4326`, `GEOMETRY`, `MULTI*`, …). */
|
|
8
|
+
const GEOMETRY_RE = /^(GEOMETRY|POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)\b/i;
|
|
9
|
+
/** TEXT/BLOB/JSON/GEOMETRY families only accept the expression form `DEFAULT (expr)` (MySQL ≥ 8.0.13). */
|
|
10
|
+
function needsExpressionDefault(sqlType) {
|
|
11
|
+
return TEXT_BLOB_RE.test(sqlType) || /^JSON\b/i.test(sqlType) || GEOMETRY_RE.test(sqlType);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Renders a model value default for a column of `sqlType`: the SQL literal,
|
|
15
|
+
* wrapped in the expression form for the types that require it.
|
|
16
|
+
*/
|
|
17
|
+
function mysqlDefaultLiteral(sqlType, designType, value) {
|
|
18
|
+
const literal = (0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(designType, value);
|
|
19
|
+
return needsExpressionDefault(sqlType) ? `(${literal})` : literal;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Type-aware fallback value for a column that must get a value it has no
|
|
23
|
+
* model default for (invented ADD default, NULL backfill before NOT NULL).
|
|
24
|
+
* Every value is legal for its type under strict `sql_mode`.
|
|
25
|
+
*/
|
|
26
|
+
function mysqlTypeDefault(sqlType, field) {
|
|
27
|
+
const type = sqlType.toUpperCase();
|
|
28
|
+
if (type.startsWith("JSON")) return "('{}')";
|
|
29
|
+
if (GEOMETRY_RE.test(type)) return "(ST_SRID(POINT(0, 0), 4326))";
|
|
30
|
+
if (/^(TIMESTAMP|DATETIME)/.test(type)) return "CURRENT_TIMESTAMP";
|
|
31
|
+
if (/^DATE\b/.test(type)) return "'1970-01-01'";
|
|
32
|
+
if (/^YEAR\b/.test(type)) return "1970";
|
|
33
|
+
if (/^TIME\b/.test(type)) return "'00:00:00'";
|
|
34
|
+
if (TEXT_BLOB_RE.test(type)) return "('')";
|
|
35
|
+
if (/^(VARCHAR|CHAR|VARBINARY|BINARY|ENUM|SET)\b/.test(type)) return "''";
|
|
36
|
+
if (/^(TINYINT|SMALLINT|MEDIUMINT|INT|INTEGER|BIGINT|DOUBLE|FLOAT|DECIMAL|NUMERIC|REAL|BIT|BOOL)/.test(type)) return "0";
|
|
37
|
+
return (0, _atscript_db_sql_tools.defaultValueForType)(field.designType);
|
|
38
|
+
}
|
|
39
|
+
/** InnoDB maximum index key part in bytes (ROW_FORMAT=DYNAMIC/COMPRESSED, the default since 5.7.7). */
|
|
40
|
+
const MYSQL_MAX_KEY_PART_BYTES = 3072;
|
|
41
|
+
/** Bytes per character of a table charset (unknown charsets assume the widest, 4). */
|
|
42
|
+
function mysqlBytesPerChar(charset) {
|
|
43
|
+
switch ((charset ?? "utf8mb4").toLowerCase()) {
|
|
44
|
+
case "utf8mb4": return 4;
|
|
45
|
+
case "utf8":
|
|
46
|
+
case "utf8mb3": return 3;
|
|
47
|
+
case "latin1":
|
|
48
|
+
case "ascii":
|
|
49
|
+
case "binary": return 1;
|
|
50
|
+
default: return 4;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Declared character length of a `CHAR(n)` / `VARCHAR(n)` mapped type, else `undefined`. */
|
|
54
|
+
function mysqlCharLength(mappedType) {
|
|
55
|
+
const m = /^(?:VAR)?CHAR\((\d+)\)/i.exec(mappedType.trim());
|
|
56
|
+
return m ? Number(m[1]) : void 0;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Key-length prefix a plain/unique index needs for a column of `mappedType`:
|
|
60
|
+
* - `CHAR(n)` / `VARCHAR(n)` within the key-part limit (3072 bytes ÷ bytes per
|
|
61
|
+
* char: 768 chars on utf8mb4) → no prefix; longer → the full limit (a
|
|
62
|
+
* shorter constant would needlessly weaken uniqueness);
|
|
63
|
+
* - `BINARY(n)` / `VARBINARY(n)` → same rule with 1 byte per char;
|
|
64
|
+
* - TEXT / BLOB families → 255 (a prefix is mandatory);
|
|
65
|
+
* - everything else (numeric, ENUM, JSON, VECTOR, geometry, …) → never.
|
|
66
|
+
*/
|
|
67
|
+
function mysqlIndexPrefix(mappedType, bytesPerChar) {
|
|
68
|
+
const type = mappedType.trim().toUpperCase();
|
|
69
|
+
const chars = mysqlCharLength(type);
|
|
70
|
+
if (chars !== void 0) {
|
|
71
|
+
const limit = Math.floor(MYSQL_MAX_KEY_PART_BYTES / bytesPerChar);
|
|
72
|
+
return chars <= limit ? void 0 : limit;
|
|
73
|
+
}
|
|
74
|
+
const bin = /^(?:VAR)?BINARY\((\d+)\)/.exec(type);
|
|
75
|
+
if (bin) return Number(bin[1]) <= 3072 ? void 0 : MYSQL_MAX_KEY_PART_BYTES;
|
|
76
|
+
if (TEXT_BLOB_RE.test(type)) return 255;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The ONE column-definition renderer for CREATE TABLE, ADD COLUMN and MODIFY
|
|
80
|
+
* COLUMN. Rules:
|
|
81
|
+
* 1. type from the adapter mapper (vector/encrypted folding);
|
|
82
|
+
* 2. `AUTO_INCREMENT` for `@db.default.increment` columns on `create` and
|
|
83
|
+
* `modify` — never on `add` (ER 1075 without a key; `rebuildPrimaryKey`
|
|
84
|
+
* owns the columns entering the key and re-declares them);
|
|
85
|
+
* 3. nullability — `create`: as MySQL infers it (PRIMARY KEY / AUTO_INCREMENT
|
|
86
|
+
* imply NOT NULL); `add`/`modify`: always explicit (`NULL` matters for
|
|
87
|
+
* TIMESTAMP under `explicit_defaults_for_timestamp=OFF`, and MySQL rejects
|
|
88
|
+
* an explicit `NULL` on a key column, so key columns say NOT NULL);
|
|
89
|
+
* 4. DEFAULT — a model default only; NEVER `DEFAULT NULL` (it is the implicit
|
|
90
|
+
* default of a nullable column, and `NOT NULL DEFAULT NULL` is ER 1067);
|
|
91
|
+
* expression form for TEXT/BLOB/JSON/GEOMETRY; invented type default for a
|
|
92
|
+
* required default-less column on `add`;
|
|
93
|
+
* 5. COLLATE (native `@db.mysql.collate` or portable `@db.column.collate`);
|
|
94
|
+
* 6. ON UPDATE.
|
|
95
|
+
*/
|
|
96
|
+
function buildColumnDefinition(field, ctx) {
|
|
97
|
+
const sqlType = ctx.typeMapper?.(field) ?? mysqlTypeFromField(field);
|
|
98
|
+
const increment = ctx.purpose !== "add" && (ctx.incrementFields?.has(field.physicalName) ?? false);
|
|
99
|
+
let def = `${qi(field.physicalName)} ${sqlType}`;
|
|
100
|
+
if (increment) def += " AUTO_INCREMENT";
|
|
101
|
+
if (ctx.purpose === "create") {
|
|
102
|
+
if (!field.optional && !field.isPrimaryKey && !increment) def += " NOT NULL";
|
|
103
|
+
} else def += !field.optional || field.isPrimaryKey || increment ? " NOT NULL" : " NULL";
|
|
104
|
+
let inventedDefault = false;
|
|
105
|
+
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value)}`;
|
|
106
|
+
else if (field.defaultValue?.kind === "fn") {
|
|
107
|
+
if (field.defaultValue.fn === "uuid") def += " DEFAULT (UUID())";
|
|
108
|
+
else if (field.defaultValue.fn === "now") def += " DEFAULT CURRENT_TIMESTAMP";
|
|
109
|
+
} else if (ctx.purpose === "add" && !field.optional && !field.isPrimaryKey && !increment && !/^VECTOR\b/i.test(sqlType)) {
|
|
110
|
+
def += ` DEFAULT ${mysqlTypeDefault(sqlType, field)}`;
|
|
111
|
+
inventedDefault = true;
|
|
112
|
+
}
|
|
113
|
+
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
114
|
+
if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
|
|
115
|
+
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
116
|
+
const onUpdate = ctx.onUpdateFields?.get(field.physicalName);
|
|
117
|
+
if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
|
|
118
|
+
return {
|
|
119
|
+
def,
|
|
120
|
+
inventedDefault
|
|
121
|
+
};
|
|
122
|
+
}
|
|
5
123
|
/** Escapes a MySQL identifier by doubling backticks. */
|
|
6
124
|
function esc(name) {
|
|
7
125
|
return name.replace(/`/g, "``");
|
|
@@ -200,45 +318,36 @@ function mysqlTypeFromField(field) {
|
|
|
200
318
|
* Builds a CREATE TABLE IF NOT EXISTS statement with MySQL options.
|
|
201
319
|
*/
|
|
202
320
|
function buildCreateTable(table, fields, foreignKeys, options) {
|
|
203
|
-
const colDefs = [];
|
|
204
321
|
const primaryKeys = fields.filter((f) => f.isPrimaryKey);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
217
|
-
if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
|
|
218
|
-
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
219
|
-
const onUpdate = options?.onUpdateFields?.get(field.physicalName);
|
|
220
|
-
if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
|
|
221
|
-
colDefs.push(def);
|
|
222
|
-
}
|
|
322
|
+
const ctx = {
|
|
323
|
+
incrementFields: options?.incrementFields,
|
|
324
|
+
onUpdateFields: options?.onUpdateFields,
|
|
325
|
+
typeMapper: options?.typeMapper,
|
|
326
|
+
purpose: "create"
|
|
327
|
+
};
|
|
328
|
+
const colDefs = fields.filter((f) => !f.ignored).map((f) => ({
|
|
329
|
+
name: f.physicalName,
|
|
330
|
+
def: buildColumnDefinition(f, ctx).def
|
|
331
|
+
}));
|
|
332
|
+
const constraints = [];
|
|
223
333
|
if (primaryKeys.length === 1) {
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
colDefs[i] += " PRIMARY KEY";
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
334
|
+
const pk = colDefs.find((c) => c.name === primaryKeys[0].physicalName);
|
|
335
|
+
if (pk) pk.def += " PRIMARY KEY";
|
|
229
336
|
} else if (primaryKeys.length > 1) {
|
|
230
337
|
const pkCols = primaryKeys.map((pk) => qi(pk.physicalName)).join(", ");
|
|
231
|
-
|
|
338
|
+
constraints.push(`PRIMARY KEY (${pkCols})`);
|
|
232
339
|
}
|
|
233
340
|
if (foreignKeys) for (const fk of foreignKeys.values()) {
|
|
341
|
+
if (options?.deferForeignKeysTo?.has(fk.targetTable)) continue;
|
|
234
342
|
const localCols = fk.fields.map((f) => qi(f)).join(", ");
|
|
235
343
|
const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
|
|
236
344
|
let constraint = `FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
|
|
237
345
|
if (fk.onDelete) constraint += ` ON DELETE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onDelete)}`;
|
|
238
346
|
if (fk.onUpdate) constraint += ` ON UPDATE ${(0, _atscript_db_sql_tools.refActionToSql)(fk.onUpdate)}`;
|
|
239
|
-
|
|
347
|
+
constraints.push(constraint);
|
|
240
348
|
}
|
|
241
|
-
|
|
349
|
+
const body = [...colDefs.map((c) => c.def), ...constraints].join(", ");
|
|
350
|
+
let sql = `CREATE TABLE IF NOT EXISTS ${quoteTableName(table)} (${body})`;
|
|
242
351
|
const engine = options?.engine ?? "InnoDB";
|
|
243
352
|
const charset = options?.charset ?? "utf8mb4";
|
|
244
353
|
const collation = options?.collation ?? "utf8mb4_unicode_ci";
|
|
@@ -306,14 +415,27 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
306
415
|
_vectorFields = /* @__PURE__ */ new Map();
|
|
307
416
|
/** Default similarity thresholds per vector field (from @db.search.vector.threshold). */
|
|
308
417
|
_vectorThresholds = /* @__PURE__ */ new Map();
|
|
309
|
-
/**
|
|
418
|
+
/**
|
|
419
|
+
* Schema name for INFORMATION_SCHEMA queries — `@db.schema` of the bound
|
|
420
|
+
* table, or `null` (→ `DATABASE()`, the pool's database) when the table
|
|
421
|
+
* declares none or the adapter is an administrative one with no readable
|
|
422
|
+
* (the name-taking schema-sync primitives run on such an adapter).
|
|
423
|
+
*/
|
|
310
424
|
get _schema() {
|
|
311
|
-
return this._table
|
|
425
|
+
return this._table?.schema ?? null;
|
|
312
426
|
}
|
|
313
427
|
constructor(driver) {
|
|
314
428
|
super();
|
|
315
429
|
this.driver = driver;
|
|
316
430
|
}
|
|
431
|
+
/** Every adapter over this pool shares one transaction (since 0.1.128). */
|
|
432
|
+
_transactionOwner() {
|
|
433
|
+
return this.driver;
|
|
434
|
+
}
|
|
435
|
+
/** The dedicated connection of this pool's open transaction, if any. */
|
|
436
|
+
_txConnection() {
|
|
437
|
+
return this._getTransactionState();
|
|
438
|
+
}
|
|
317
439
|
async _beginTransaction() {
|
|
318
440
|
const conn = await this.driver.getConnection();
|
|
319
441
|
await conn.exec("START TRANSACTION");
|
|
@@ -343,7 +465,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
343
465
|
* otherwise the pool-based driver.
|
|
344
466
|
*/
|
|
345
467
|
_exec() {
|
|
346
|
-
return this.
|
|
468
|
+
return this._txConnection() ?? this.driver;
|
|
347
469
|
}
|
|
348
470
|
/** MySQL InnoDB enforces FK constraints natively. */
|
|
349
471
|
supportsNativeForeignKeys() {
|
|
@@ -403,10 +525,10 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
403
525
|
}
|
|
404
526
|
];
|
|
405
527
|
}
|
|
406
|
-
async getExistingTableOptions() {
|
|
528
|
+
async getExistingTableOptions(tableName) {
|
|
407
529
|
const row = await this._exec().get(`SELECT ENGINE, TABLE_COLLATION
|
|
408
530
|
FROM INFORMATION_SCHEMA.TABLES
|
|
409
|
-
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [this._table.tableName, this._schema]);
|
|
531
|
+
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [tableName ?? this._table.tableName, this._schema]);
|
|
410
532
|
if (!row) return [];
|
|
411
533
|
const charset = row.TABLE_COLLATION?.split("_")[0] ?? "utf8mb4";
|
|
412
534
|
return [
|
|
@@ -599,7 +721,8 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
599
721
|
async replaceOne(filter, data, expectedVersion) {
|
|
600
722
|
const where = buildWhere(filter);
|
|
601
723
|
const versionColumn = this._table.versionColumn;
|
|
602
|
-
const
|
|
724
|
+
const full = (0, _atscript_db_sql_tools.fillReplacePayload)(data, (0, _atscript_db_sql_tools.replaceColumnsFor)(this._table.fieldDescriptors, this.nativeDefaultFns()), versionColumn);
|
|
725
|
+
const { sql, params } = buildUpdate(this.resolveTableName(), full, where, 1, void 0, versionColumn, expectedVersion);
|
|
603
726
|
this._log(sql, params);
|
|
604
727
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
605
728
|
return {
|
|
@@ -633,19 +756,111 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
633
756
|
async prepareTypeMapper() {
|
|
634
757
|
if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
|
|
635
758
|
}
|
|
636
|
-
async ensureTable() {
|
|
759
|
+
async ensureTable(opts) {
|
|
637
760
|
await this.prepareTypeMapper();
|
|
638
|
-
if (this._table
|
|
639
|
-
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys,
|
|
761
|
+
if (this._table.isView) return this._ensureView();
|
|
762
|
+
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions(opts));
|
|
763
|
+
this._log(sql);
|
|
764
|
+
await this._exec().exec(sql);
|
|
765
|
+
}
|
|
766
|
+
/** The CREATE TABLE options (engine/charset/collation/increment/ON UPDATE/type mapper). */
|
|
767
|
+
_tableOptions(opts) {
|
|
768
|
+
return {
|
|
640
769
|
engine: this._engine,
|
|
641
770
|
charset: this._charset,
|
|
642
771
|
collation: this._collation,
|
|
643
772
|
autoIncrementStart: this._autoIncrementStart,
|
|
644
773
|
incrementFields: this._incrementFields,
|
|
645
|
-
onUpdateFields: this._onUpdateFields
|
|
646
|
-
|
|
774
|
+
onUpdateFields: this._onUpdateFields,
|
|
775
|
+
typeMapper: (field) => this.typeMapper(field),
|
|
776
|
+
deferForeignKeysTo: opts?.deferForeignKeysTo
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
/** The shared column-definition context for ADD/MODIFY statements. */
|
|
780
|
+
_columnCtx(purpose) {
|
|
781
|
+
return {
|
|
782
|
+
incrementFields: this._incrementFields,
|
|
783
|
+
onUpdateFields: this._onUpdateFields,
|
|
784
|
+
typeMapper: (field) => this.typeMapper(field),
|
|
785
|
+
purpose
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
async hasRows(tableName) {
|
|
789
|
+
const sql = `SELECT EXISTS(SELECT 1 FROM ${tableName ? quoteTableName(this._schema ? `${this._schema}.${tableName}` : tableName) : quoteTableName(this.resolveTableName())}) AS present`;
|
|
647
790
|
this._log(sql);
|
|
648
|
-
await this._exec().
|
|
791
|
+
const row = await this._exec().get(sql, []);
|
|
792
|
+
return Boolean(Number(row?.present ?? 0));
|
|
793
|
+
}
|
|
794
|
+
/** Live foreign keys referencing `tableName` (any table of the schema). */
|
|
795
|
+
async getReferencingForeignKeys(tableName) {
|
|
796
|
+
const rows = await this._exec().all(`SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_COLUMN_NAME
|
|
797
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
798
|
+
WHERE REFERENCED_TABLE_NAME = ? AND REFERENCED_TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
799
|
+
ORDER BY TABLE_NAME, CONSTRAINT_NAME, ORDINAL_POSITION`, [tableName, this._schema]);
|
|
800
|
+
const byConstraint = /* @__PURE__ */ new Map();
|
|
801
|
+
for (const r of rows) {
|
|
802
|
+
const key = `${r.TABLE_NAME}\0${r.CONSTRAINT_NAME}`;
|
|
803
|
+
let fk = byConstraint.get(key);
|
|
804
|
+
if (!fk) {
|
|
805
|
+
fk = {
|
|
806
|
+
table: r.TABLE_NAME,
|
|
807
|
+
fields: [],
|
|
808
|
+
targetFields: []
|
|
809
|
+
};
|
|
810
|
+
byConstraint.set(key, fk);
|
|
811
|
+
}
|
|
812
|
+
fk.fields.push(r.COLUMN_NAME);
|
|
813
|
+
fk.targetFields.push(r.REFERENCED_COLUMN_NAME);
|
|
814
|
+
}
|
|
815
|
+
return [...byConstraint.values()];
|
|
816
|
+
}
|
|
817
|
+
async getObjectKind(name) {
|
|
818
|
+
const row = await this._exec().get(`SELECT TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES
|
|
819
|
+
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [name, this._schema]);
|
|
820
|
+
if (!row) return;
|
|
821
|
+
return row.TABLE_TYPE.toUpperCase() === "VIEW" ? "view" : "table";
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* One `ALTER TABLE … [MODIFY …,] DROP PRIMARY KEY, ADD PRIMARY KEY (…)`
|
|
825
|
+
* statement (atomic). The sole owner of the columns entering the key:
|
|
826
|
+
* `syncColumns` adds them without AUTO_INCREMENT and skips their MODIFYs
|
|
827
|
+
* while the key change is pending, and this statement re-declares each of
|
|
828
|
+
* them in full (AUTO_INCREMENT when the model declares increment, explicit
|
|
829
|
+
* NOT NULL) together with the key swap — so an AUTO_INCREMENT column never
|
|
830
|
+
* exists without a key (ER 1075), in safe mode or otherwise. A demoted key
|
|
831
|
+
* column loses AUTO_INCREMENT in the same statement (pre-flight guarantees
|
|
832
|
+
* its model no longer declares increment). Called on an empty table only.
|
|
833
|
+
*/
|
|
834
|
+
async rebuildPrimaryKey(change) {
|
|
835
|
+
const fields = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
836
|
+
const to = new Set(change.to);
|
|
837
|
+
const clauses = [];
|
|
838
|
+
for (const name of change.to) {
|
|
839
|
+
const field = fields.get(name);
|
|
840
|
+
if (field) clauses.push(`MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`);
|
|
841
|
+
}
|
|
842
|
+
if (change.from.length > 0) {
|
|
843
|
+
const placeholders = change.from.map(() => "?").join(", ");
|
|
844
|
+
const live = await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, EXTRA FROM INFORMATION_SCHEMA.COLUMNS
|
|
845
|
+
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
846
|
+
AND COLUMN_NAME IN (${placeholders})`, [
|
|
847
|
+
this._table.tableName,
|
|
848
|
+
this._schema,
|
|
849
|
+
...change.from
|
|
850
|
+
]);
|
|
851
|
+
for (const col of live) {
|
|
852
|
+
if (to.has(col.COLUMN_NAME) || !/auto_increment/i.test(col.EXTRA ?? "")) continue;
|
|
853
|
+
const field = fields.get(col.COLUMN_NAME);
|
|
854
|
+
const def = field ? buildColumnDefinition(field, this._columnCtx("modify")).def : `${qi(col.COLUMN_NAME)} ${col.COLUMN_TYPE.toUpperCase()} NOT NULL`;
|
|
855
|
+
clauses.push(`MODIFY COLUMN ${def}`);
|
|
856
|
+
}
|
|
857
|
+
clauses.push("DROP PRIMARY KEY");
|
|
858
|
+
}
|
|
859
|
+
if (change.to.length > 0) clauses.push(`ADD PRIMARY KEY (${change.to.map((c) => qi(c)).join(", ")})`);
|
|
860
|
+
if (clauses.length === 0) return;
|
|
861
|
+
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ${clauses.join(", ")}`;
|
|
862
|
+
this._log(ddl);
|
|
863
|
+
await this._exec().exec(ddl);
|
|
649
864
|
}
|
|
650
865
|
async _ensureView() {
|
|
651
866
|
const view = this._table;
|
|
@@ -657,15 +872,18 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
657
872
|
return this.getExistingColumnsForTable(this._table.tableName);
|
|
658
873
|
}
|
|
659
874
|
async getExistingColumnsForTable(tableName) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
FROM INFORMATION_SCHEMA.COLUMNS
|
|
663
|
-
|
|
664
|
-
|
|
875
|
+
return (await this._exec().all(`SELECT c.COLUMN_NAME, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, c.SRS_ID,
|
|
876
|
+
(k.COLUMN_NAME IS NOT NULL) AS IS_PK
|
|
877
|
+
FROM INFORMATION_SCHEMA.COLUMNS c
|
|
878
|
+
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
|
|
879
|
+
ON k.TABLE_SCHEMA = c.TABLE_SCHEMA AND k.TABLE_NAME = c.TABLE_NAME
|
|
880
|
+
AND k.COLUMN_NAME = c.COLUMN_NAME AND k.CONSTRAINT_NAME = 'PRIMARY'
|
|
881
|
+
WHERE c.TABLE_NAME = ? AND c.TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
882
|
+
ORDER BY c.ORDINAL_POSITION`, [tableName, this._schema])).map((r) => ({
|
|
665
883
|
name: r.COLUMN_NAME,
|
|
666
884
|
type: r.SRS_ID == null ? r.COLUMN_TYPE.toUpperCase() : `${r.COLUMN_TYPE.toUpperCase()} SRID ${r.SRS_ID}`,
|
|
667
885
|
notnull: r.IS_NULLABLE === "NO",
|
|
668
|
-
pk: r.
|
|
886
|
+
pk: Boolean(Number(r.IS_PK ?? 0)),
|
|
669
887
|
dflt_value: normalizeMysqlDefault(r.COLUMN_DEFAULT)
|
|
670
888
|
}));
|
|
671
889
|
}
|
|
@@ -679,45 +897,44 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
679
897
|
await this._exec().exec(ddl);
|
|
680
898
|
renamed.push(field.physicalName);
|
|
681
899
|
}
|
|
900
|
+
const quotedTable = quoteTableName(tableName);
|
|
682
901
|
for (const field of diff.added) {
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
686
|
-
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
|
|
687
|
-
else if (!field.optional && !field.isPrimaryKey) ddl += field.isGeoPoint && !field.encrypted ? ` DEFAULT (ST_SRID(POINT(0, 0), 4326))` : ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueForType)(field.designType)}`;
|
|
688
|
-
if (field.collate) {
|
|
689
|
-
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
690
|
-
ddl += ` COLLATE ${nativeCollate ?? collationToMysql(field.collate)}`;
|
|
691
|
-
}
|
|
902
|
+
const { def, inventedDefault } = buildColumnDefinition(field, this._columnCtx("add"));
|
|
903
|
+
const ddl = `ALTER TABLE ${quotedTable} ADD COLUMN ${def}`;
|
|
692
904
|
this._log(ddl);
|
|
693
905
|
await this._exec().exec(ddl);
|
|
906
|
+
if (inventedDefault) {
|
|
907
|
+
const drop = `ALTER TABLE ${quotedTable} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
|
|
908
|
+
this._log(drop);
|
|
909
|
+
await this._exec().exec(drop);
|
|
910
|
+
}
|
|
694
911
|
added.push(field.physicalName);
|
|
695
912
|
}
|
|
913
|
+
const enteringKey = new Set(diff.primaryKeyChanged?.to ?? []);
|
|
914
|
+
const modified = /* @__PURE__ */ new Map();
|
|
696
915
|
for (const { field } of diff.typeChanged ?? []) {
|
|
916
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
697
917
|
const sqlType = this.typeMapper(field);
|
|
698
918
|
if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
|
|
699
919
|
await this._migrateJsonColumnToPoint(tableName, field);
|
|
700
920
|
continue;
|
|
701
921
|
}
|
|
702
|
-
|
|
703
|
-
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
704
|
-
this._log(ddl);
|
|
705
|
-
await this._exec().exec(ddl);
|
|
922
|
+
modified.set(field.physicalName, field);
|
|
706
923
|
}
|
|
707
924
|
for (const { field } of diff.nullableChanged ?? []) {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
925
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
926
|
+
if (!field.optional) {
|
|
927
|
+
const sqlType = this.typeMapper(field);
|
|
928
|
+
const fallback = field.defaultValue?.kind === "value" ? mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value) : mysqlTypeDefault(sqlType, field);
|
|
929
|
+
const backfill = `UPDATE ${quotedTable} SET ${qi(field.physicalName)} = ${fallback} WHERE ${qi(field.physicalName)} IS NULL`;
|
|
930
|
+
this._log(backfill);
|
|
931
|
+
await this._exec().exec(backfill);
|
|
932
|
+
}
|
|
933
|
+
modified.set(field.physicalName, field);
|
|
713
934
|
}
|
|
714
|
-
for (const { field } of diff.defaultChanged ?? [])
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
718
|
-
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${(0, _atscript_db_sql_tools.defaultValueToSqlLiteral)(field.designType, field.defaultValue.value)}`;
|
|
719
|
-
else if (field.defaultValue?.kind === "fn") ddl += ` DEFAULT ${field.defaultValue.fn === "now" ? "CURRENT_TIMESTAMP" : field.defaultValue.fn === "uuid" ? "(UUID())" : `(${field.defaultValue.fn}())`}`;
|
|
720
|
-
else ddl += " DEFAULT NULL";
|
|
935
|
+
for (const { field } of diff.defaultChanged ?? []) if (!enteringKey.has(field.physicalName)) modified.set(field.physicalName, field);
|
|
936
|
+
for (const field of modified.values()) {
|
|
937
|
+
const ddl = `ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`;
|
|
721
938
|
this._log(ddl);
|
|
722
939
|
await this._exec().exec(ddl);
|
|
723
940
|
}
|
|
@@ -729,18 +946,14 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
729
946
|
async recreateTable() {
|
|
730
947
|
const tableName = this.resolveTableName();
|
|
731
948
|
const tempName = `${this._table.tableName}__tmp_${Date.now()}`;
|
|
732
|
-
|
|
949
|
+
const tx = this._txConnection();
|
|
950
|
+
const conn = tx ?? await this.driver.getConnection();
|
|
951
|
+
const dedicated = tx === void 0;
|
|
952
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
|
|
733
953
|
try {
|
|
734
|
-
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys,
|
|
735
|
-
engine: this._engine,
|
|
736
|
-
charset: this._charset,
|
|
737
|
-
collation: this._collation,
|
|
738
|
-
autoIncrementStart: this._autoIncrementStart,
|
|
739
|
-
incrementFields: this._incrementFields,
|
|
740
|
-
onUpdateFields: this._onUpdateFields
|
|
741
|
-
});
|
|
954
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions());
|
|
742
955
|
this._log(createSql);
|
|
743
|
-
await
|
|
956
|
+
await conn.exec(createSql);
|
|
744
957
|
const oldCols = (await this.getExistingColumns()).map((c) => c.name);
|
|
745
958
|
const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
|
|
746
959
|
const oldColSet = new Set(oldCols);
|
|
@@ -758,12 +971,13 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
758
971
|
}).join(", ");
|
|
759
972
|
const copySql = `INSERT INTO ${qi(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
|
|
760
973
|
this._log(copySql);
|
|
761
|
-
await
|
|
974
|
+
await conn.exec(copySql);
|
|
762
975
|
}
|
|
763
|
-
await
|
|
764
|
-
await
|
|
976
|
+
await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)}`);
|
|
977
|
+
await conn.exec(`RENAME TABLE ${qi(tempName)} TO ${quoteTableName(tableName)}`);
|
|
765
978
|
} finally {
|
|
766
|
-
await
|
|
979
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
980
|
+
if (dedicated) conn.release();
|
|
767
981
|
}
|
|
768
982
|
}
|
|
769
983
|
async dropTable() {
|
|
@@ -834,18 +1048,38 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
834
1048
|
async syncIndexes() {
|
|
835
1049
|
const tableName = this._table.tableName;
|
|
836
1050
|
const schema = this._schema;
|
|
837
|
-
const
|
|
1051
|
+
const bytesPerChar = mysqlBytesPerChar(this._charset);
|
|
1052
|
+
const fields = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
1053
|
+
const mappedType = (column) => {
|
|
1054
|
+
const field = fields.get(column);
|
|
1055
|
+
return field ? this.typeMapper(field) : void 0;
|
|
1056
|
+
};
|
|
1057
|
+
const desiredPrefix = (column) => {
|
|
1058
|
+
const type = mappedType(column);
|
|
1059
|
+
return type === void 0 ? void 0 : mysqlIndexPrefix(type, bytesPerChar);
|
|
1060
|
+
};
|
|
1061
|
+
const renderPart = (column, prefix) => prefix === void 0 ? column : `${column}(${prefix})`;
|
|
838
1062
|
await this.syncIndexesWithDiff({
|
|
839
1063
|
listExisting: async () => {
|
|
840
|
-
|
|
841
|
-
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') AS columns
|
|
1064
|
+
const rows = await this._exec().all(`SELECT INDEX_NAME, COLUMN_NAME, SUB_PART, SEQ_IN_INDEX
|
|
842
1065
|
FROM INFORMATION_SCHEMA.STATISTICS
|
|
843
1066
|
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1067
|
+
ORDER BY INDEX_NAME, SEQ_IN_INDEX`, [tableName, schema]);
|
|
1068
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1069
|
+
for (const r of rows) {
|
|
1070
|
+
let subPart = r.SUB_PART ?? void 0;
|
|
1071
|
+
const type = mappedType(r.COLUMN_NAME);
|
|
1072
|
+
if (subPart !== void 0 && type !== void 0 && mysqlCharLength(type) === subPart) subPart = void 0;
|
|
1073
|
+
const parts = byName.get(r.INDEX_NAME) ?? [];
|
|
1074
|
+
parts.push(renderPart(r.COLUMN_NAME, subPart));
|
|
1075
|
+
byName.set(r.INDEX_NAME, parts);
|
|
1076
|
+
}
|
|
1077
|
+
return [...byName].map(([name, columns]) => ({
|
|
1078
|
+
name,
|
|
1079
|
+
columns
|
|
847
1080
|
}));
|
|
848
1081
|
},
|
|
1082
|
+
renderDesiredColumn: (_index, f) => renderPart(f.name, desiredPrefix(f.name)),
|
|
849
1083
|
createIndex: async (index) => {
|
|
850
1084
|
if (index.type === "geo") {
|
|
851
1085
|
const fieldMeta = this._table.fieldDescriptors.find((f) => f.physicalName === index.fields[0]?.name);
|
|
@@ -862,7 +1096,10 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
862
1096
|
const fulltext = index.type === "fulltext" ? "FULLTEXT " : "";
|
|
863
1097
|
const isFulltext = index.type === "fulltext";
|
|
864
1098
|
const cols = index.fields.map((f) => {
|
|
865
|
-
|
|
1099
|
+
const col = qi(f.name);
|
|
1100
|
+
const prefix = isFulltext ? void 0 : desiredPrefix(f.name);
|
|
1101
|
+
const order = isFulltext ? "" : ` ${f.sort === "desc" ? "DESC" : "ASC"}`;
|
|
1102
|
+
return `${col}${prefix === void 0 ? "" : `(${prefix})`}${order}`;
|
|
866
1103
|
}).join(", ");
|
|
867
1104
|
const sql = `CREATE ${fulltext}${unique}INDEX ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${cols})`;
|
|
868
1105
|
this._log(sql);
|
|
@@ -1150,7 +1387,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1150
1387
|
`UPDATE ${quotedTable} SET ${tmp} = ST_SRID(POINT(CAST(${col}->>'$[0]' AS DOUBLE), CAST(${col}->>'$[1]' AS DOUBLE)), 4326) WHERE ${col} IS NOT NULL`,
|
|
1151
1388
|
`ALTER TABLE ${quotedTable} DROP COLUMN ${col}`,
|
|
1152
1389
|
`ALTER TABLE ${quotedTable} RENAME COLUMN ${tmp} TO ${col}`,
|
|
1153
|
-
...field.optional || field.isPrimaryKey ? [] : [`ALTER TABLE ${quotedTable} MODIFY COLUMN ${
|
|
1390
|
+
...field.optional || field.isPrimaryKey ? [] : [`ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`]
|
|
1154
1391
|
];
|
|
1155
1392
|
for (const ddl of steps) {
|
|
1156
1393
|
this._log(ddl);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TExistingColumn, TExistingTableOption, TFieldOps, TSearchIndexInfo, TSyncColumnResult, TTableOptionDiff, TValueFormatterPair } from "@atscript/db";
|
|
1
|
+
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TExistingTableOption, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TTableOptionDiff, TValueFormatterPair } from "@atscript/db";
|
|
2
2
|
import { TMetadataMap } from "@atscript/typescript/utils";
|
|
3
3
|
import { FilterExpr as FilterExpr$1 } from "@uniqu/core";
|
|
4
4
|
import { TSqlFragment, TSqlFragment as TSqlFragment$1 } from "@atscript/db-sql-tools";
|
|
@@ -107,9 +107,18 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
107
107
|
private _vectorFields;
|
|
108
108
|
/** Default similarity thresholds per vector field (from @db.search.vector.threshold). */
|
|
109
109
|
private _vectorThresholds;
|
|
110
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Schema name for INFORMATION_SCHEMA queries — `@db.schema` of the bound
|
|
112
|
+
* table, or `null` (→ `DATABASE()`, the pool's database) when the table
|
|
113
|
+
* declares none or the adapter is an administrative one with no readable
|
|
114
|
+
* (the name-taking schema-sync primitives run on such an adapter).
|
|
115
|
+
*/
|
|
111
116
|
private get _schema();
|
|
112
117
|
constructor(driver: TMysqlDriver);
|
|
118
|
+
/** Every adapter over this pool shares one transaction (since 0.1.128). */
|
|
119
|
+
protected _transactionOwner(): unknown;
|
|
120
|
+
/** The dedicated connection of this pool's open transaction, if any. */
|
|
121
|
+
private _txConnection;
|
|
113
122
|
protected _beginTransaction(): Promise<TMysqlConnection>;
|
|
114
123
|
protected _commitTransaction(state: unknown): Promise<void>;
|
|
115
124
|
protected _rollbackTransaction(state: unknown): Promise<void>;
|
|
@@ -126,7 +135,7 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
126
135
|
onBeforeFlatten(_type: unknown): void;
|
|
127
136
|
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
128
137
|
getDesiredTableOptions(): TExistingTableOption[];
|
|
129
|
-
getExistingTableOptions(): Promise<TExistingTableOption[]>;
|
|
138
|
+
getExistingTableOptions(tableName?: string): Promise<TExistingTableOption[]>;
|
|
130
139
|
applyTableOptions(changes: TTableOptionDiff["changed"]): Promise<void>;
|
|
131
140
|
/**
|
|
132
141
|
* Returns a value formatter for TIMESTAMP-mapped fields.
|
|
@@ -158,7 +167,27 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
158
167
|
deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
159
168
|
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
160
169
|
prepareTypeMapper(): Promise<void>;
|
|
161
|
-
ensureTable(): Promise<void>;
|
|
170
|
+
ensureTable(opts?: TEnsureTableOptions): Promise<void>;
|
|
171
|
+
/** The CREATE TABLE options (engine/charset/collation/increment/ON UPDATE/type mapper). */
|
|
172
|
+
private _tableOptions;
|
|
173
|
+
/** The shared column-definition context for ADD/MODIFY statements. */
|
|
174
|
+
private _columnCtx;
|
|
175
|
+
hasRows(tableName?: string): Promise<boolean>;
|
|
176
|
+
/** Live foreign keys referencing `tableName` (any table of the schema). */
|
|
177
|
+
getReferencingForeignKeys(tableName: string): Promise<TReferencingForeignKey[]>;
|
|
178
|
+
getObjectKind(name: string): Promise<TDbObjectKind | undefined>;
|
|
179
|
+
/**
|
|
180
|
+
* One `ALTER TABLE … [MODIFY …,] DROP PRIMARY KEY, ADD PRIMARY KEY (…)`
|
|
181
|
+
* statement (atomic). The sole owner of the columns entering the key:
|
|
182
|
+
* `syncColumns` adds them without AUTO_INCREMENT and skips their MODIFYs
|
|
183
|
+
* while the key change is pending, and this statement re-declares each of
|
|
184
|
+
* them in full (AUTO_INCREMENT when the model declares increment, explicit
|
|
185
|
+
* NOT NULL) together with the key swap — so an AUTO_INCREMENT column never
|
|
186
|
+
* exists without a key (ER 1075), in safe mode or otherwise. A demoted key
|
|
187
|
+
* column loses AUTO_INCREMENT in the same statement (pre-flight guarantees
|
|
188
|
+
* its model no longer declares increment). Called on an empty table only.
|
|
189
|
+
*/
|
|
190
|
+
rebuildPrimaryKey(change: TPrimaryKeyChange): Promise<void>;
|
|
162
191
|
private _ensureView;
|
|
163
192
|
getExistingColumns(): Promise<TExistingColumn[]>;
|
|
164
193
|
getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbUpdateResult, TExistingColumn, TExistingTableOption, TFieldOps, TSearchIndexInfo, TSyncColumnResult, TTableOptionDiff, TValueFormatterPair } from "@atscript/db";
|
|
1
|
+
import { BaseDbAdapter, DbQuery, DbSpace, FilterExpr, TColumnDiff, TDbDefaultFn, TDbDeleteResult, TDbFieldMeta, TDbInsertManyResult, TDbInsertResult, TDbObjectKind, TDbUpdateResult, TEnsureTableOptions, TExistingColumn, TExistingTableOption, TFieldOps, TPrimaryKeyChange, TReferencingForeignKey, TSearchIndexInfo, TSyncColumnResult, TTableOptionDiff, TValueFormatterPair } from "@atscript/db";
|
|
2
2
|
import { TSqlFragment, TSqlFragment as TSqlFragment$1 } from "@atscript/db-sql-tools";
|
|
3
3
|
import { TMetadataMap } from "@atscript/typescript/utils";
|
|
4
4
|
import { FilterExpr as FilterExpr$1 } from "@uniqu/core";
|
|
@@ -107,9 +107,18 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
107
107
|
private _vectorFields;
|
|
108
108
|
/** Default similarity thresholds per vector field (from @db.search.vector.threshold). */
|
|
109
109
|
private _vectorThresholds;
|
|
110
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Schema name for INFORMATION_SCHEMA queries — `@db.schema` of the bound
|
|
112
|
+
* table, or `null` (→ `DATABASE()`, the pool's database) when the table
|
|
113
|
+
* declares none or the adapter is an administrative one with no readable
|
|
114
|
+
* (the name-taking schema-sync primitives run on such an adapter).
|
|
115
|
+
*/
|
|
111
116
|
private get _schema();
|
|
112
117
|
constructor(driver: TMysqlDriver);
|
|
118
|
+
/** Every adapter over this pool shares one transaction (since 0.1.128). */
|
|
119
|
+
protected _transactionOwner(): unknown;
|
|
120
|
+
/** The dedicated connection of this pool's open transaction, if any. */
|
|
121
|
+
private _txConnection;
|
|
113
122
|
protected _beginTransaction(): Promise<TMysqlConnection>;
|
|
114
123
|
protected _commitTransaction(state: unknown): Promise<void>;
|
|
115
124
|
protected _rollbackTransaction(state: unknown): Promise<void>;
|
|
@@ -126,7 +135,7 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
126
135
|
onBeforeFlatten(_type: unknown): void;
|
|
127
136
|
onFieldScanned(field: string, _type: unknown, metadata: TMetadataMap<AtscriptMetadata>): void;
|
|
128
137
|
getDesiredTableOptions(): TExistingTableOption[];
|
|
129
|
-
getExistingTableOptions(): Promise<TExistingTableOption[]>;
|
|
138
|
+
getExistingTableOptions(tableName?: string): Promise<TExistingTableOption[]>;
|
|
130
139
|
applyTableOptions(changes: TTableOptionDiff["changed"]): Promise<void>;
|
|
131
140
|
/**
|
|
132
141
|
* Returns a value formatter for TIMESTAMP-mapped fields.
|
|
@@ -158,7 +167,27 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
158
167
|
deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
159
168
|
deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
|
|
160
169
|
prepareTypeMapper(): Promise<void>;
|
|
161
|
-
ensureTable(): Promise<void>;
|
|
170
|
+
ensureTable(opts?: TEnsureTableOptions): Promise<void>;
|
|
171
|
+
/** The CREATE TABLE options (engine/charset/collation/increment/ON UPDATE/type mapper). */
|
|
172
|
+
private _tableOptions;
|
|
173
|
+
/** The shared column-definition context for ADD/MODIFY statements. */
|
|
174
|
+
private _columnCtx;
|
|
175
|
+
hasRows(tableName?: string): Promise<boolean>;
|
|
176
|
+
/** Live foreign keys referencing `tableName` (any table of the schema). */
|
|
177
|
+
getReferencingForeignKeys(tableName: string): Promise<TReferencingForeignKey[]>;
|
|
178
|
+
getObjectKind(name: string): Promise<TDbObjectKind | undefined>;
|
|
179
|
+
/**
|
|
180
|
+
* One `ALTER TABLE … [MODIFY …,] DROP PRIMARY KEY, ADD PRIMARY KEY (…)`
|
|
181
|
+
* statement (atomic). The sole owner of the columns entering the key:
|
|
182
|
+
* `syncColumns` adds them without AUTO_INCREMENT and skips their MODIFYs
|
|
183
|
+
* while the key change is pending, and this statement re-declares each of
|
|
184
|
+
* them in full (AUTO_INCREMENT when the model declares increment, explicit
|
|
185
|
+
* NOT NULL) together with the key swap — so an AUTO_INCREMENT column never
|
|
186
|
+
* exists without a key (ER 1075), in safe mode or otherwise. A demoted key
|
|
187
|
+
* column loses AUTO_INCREMENT in the same statement (pre-flight guarantees
|
|
188
|
+
* its model no longer declares increment). Called on an empty table only.
|
|
189
|
+
*/
|
|
190
|
+
rebuildPrimaryKey(change: TPrimaryKeyChange): Promise<void>;
|
|
162
191
|
private _ensureView;
|
|
163
192
|
getExistingColumns(): Promise<TExistingColumn[]>;
|
|
164
193
|
getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,124 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, geoWindowFromControls, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance, toSqlValue } from "@atscript/db-sql-tools";
|
|
1
|
+
import { BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
|
|
2
|
+
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, geoWindowFromControls, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, toSqlValue } from "@atscript/db-sql-tools";
|
|
3
3
|
//#region src/sql-builder.ts
|
|
4
|
+
/** TEXT / BLOB families (`TINYTEXT` … `LONGBLOB`). */
|
|
5
|
+
const TEXT_BLOB_RE = /^(TINY|MEDIUM|LONG)?(TEXT|BLOB)\b/i;
|
|
6
|
+
/** Spatial types (`POINT SRID 4326`, `GEOMETRY`, `MULTI*`, …). */
|
|
7
|
+
const GEOMETRY_RE = /^(GEOMETRY|POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)\b/i;
|
|
8
|
+
/** TEXT/BLOB/JSON/GEOMETRY families only accept the expression form `DEFAULT (expr)` (MySQL ≥ 8.0.13). */
|
|
9
|
+
function needsExpressionDefault(sqlType) {
|
|
10
|
+
return TEXT_BLOB_RE.test(sqlType) || /^JSON\b/i.test(sqlType) || GEOMETRY_RE.test(sqlType);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Renders a model value default for a column of `sqlType`: the SQL literal,
|
|
14
|
+
* wrapped in the expression form for the types that require it.
|
|
15
|
+
*/
|
|
16
|
+
function mysqlDefaultLiteral(sqlType, designType, value) {
|
|
17
|
+
const literal = defaultValueToSqlLiteral(designType, value);
|
|
18
|
+
return needsExpressionDefault(sqlType) ? `(${literal})` : literal;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Type-aware fallback value for a column that must get a value it has no
|
|
22
|
+
* model default for (invented ADD default, NULL backfill before NOT NULL).
|
|
23
|
+
* Every value is legal for its type under strict `sql_mode`.
|
|
24
|
+
*/
|
|
25
|
+
function mysqlTypeDefault(sqlType, field) {
|
|
26
|
+
const type = sqlType.toUpperCase();
|
|
27
|
+
if (type.startsWith("JSON")) return "('{}')";
|
|
28
|
+
if (GEOMETRY_RE.test(type)) return "(ST_SRID(POINT(0, 0), 4326))";
|
|
29
|
+
if (/^(TIMESTAMP|DATETIME)/.test(type)) return "CURRENT_TIMESTAMP";
|
|
30
|
+
if (/^DATE\b/.test(type)) return "'1970-01-01'";
|
|
31
|
+
if (/^YEAR\b/.test(type)) return "1970";
|
|
32
|
+
if (/^TIME\b/.test(type)) return "'00:00:00'";
|
|
33
|
+
if (TEXT_BLOB_RE.test(type)) return "('')";
|
|
34
|
+
if (/^(VARCHAR|CHAR|VARBINARY|BINARY|ENUM|SET)\b/.test(type)) return "''";
|
|
35
|
+
if (/^(TINYINT|SMALLINT|MEDIUMINT|INT|INTEGER|BIGINT|DOUBLE|FLOAT|DECIMAL|NUMERIC|REAL|BIT|BOOL)/.test(type)) return "0";
|
|
36
|
+
return defaultValueForType(field.designType);
|
|
37
|
+
}
|
|
38
|
+
/** InnoDB maximum index key part in bytes (ROW_FORMAT=DYNAMIC/COMPRESSED, the default since 5.7.7). */
|
|
39
|
+
const MYSQL_MAX_KEY_PART_BYTES = 3072;
|
|
40
|
+
/** Bytes per character of a table charset (unknown charsets assume the widest, 4). */
|
|
41
|
+
function mysqlBytesPerChar(charset) {
|
|
42
|
+
switch ((charset ?? "utf8mb4").toLowerCase()) {
|
|
43
|
+
case "utf8mb4": return 4;
|
|
44
|
+
case "utf8":
|
|
45
|
+
case "utf8mb3": return 3;
|
|
46
|
+
case "latin1":
|
|
47
|
+
case "ascii":
|
|
48
|
+
case "binary": return 1;
|
|
49
|
+
default: return 4;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Declared character length of a `CHAR(n)` / `VARCHAR(n)` mapped type, else `undefined`. */
|
|
53
|
+
function mysqlCharLength(mappedType) {
|
|
54
|
+
const m = /^(?:VAR)?CHAR\((\d+)\)/i.exec(mappedType.trim());
|
|
55
|
+
return m ? Number(m[1]) : void 0;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Key-length prefix a plain/unique index needs for a column of `mappedType`:
|
|
59
|
+
* - `CHAR(n)` / `VARCHAR(n)` within the key-part limit (3072 bytes ÷ bytes per
|
|
60
|
+
* char: 768 chars on utf8mb4) → no prefix; longer → the full limit (a
|
|
61
|
+
* shorter constant would needlessly weaken uniqueness);
|
|
62
|
+
* - `BINARY(n)` / `VARBINARY(n)` → same rule with 1 byte per char;
|
|
63
|
+
* - TEXT / BLOB families → 255 (a prefix is mandatory);
|
|
64
|
+
* - everything else (numeric, ENUM, JSON, VECTOR, geometry, …) → never.
|
|
65
|
+
*/
|
|
66
|
+
function mysqlIndexPrefix(mappedType, bytesPerChar) {
|
|
67
|
+
const type = mappedType.trim().toUpperCase();
|
|
68
|
+
const chars = mysqlCharLength(type);
|
|
69
|
+
if (chars !== void 0) {
|
|
70
|
+
const limit = Math.floor(MYSQL_MAX_KEY_PART_BYTES / bytesPerChar);
|
|
71
|
+
return chars <= limit ? void 0 : limit;
|
|
72
|
+
}
|
|
73
|
+
const bin = /^(?:VAR)?BINARY\((\d+)\)/.exec(type);
|
|
74
|
+
if (bin) return Number(bin[1]) <= 3072 ? void 0 : MYSQL_MAX_KEY_PART_BYTES;
|
|
75
|
+
if (TEXT_BLOB_RE.test(type)) return 255;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The ONE column-definition renderer for CREATE TABLE, ADD COLUMN and MODIFY
|
|
79
|
+
* COLUMN. Rules:
|
|
80
|
+
* 1. type from the adapter mapper (vector/encrypted folding);
|
|
81
|
+
* 2. `AUTO_INCREMENT` for `@db.default.increment` columns on `create` and
|
|
82
|
+
* `modify` — never on `add` (ER 1075 without a key; `rebuildPrimaryKey`
|
|
83
|
+
* owns the columns entering the key and re-declares them);
|
|
84
|
+
* 3. nullability — `create`: as MySQL infers it (PRIMARY KEY / AUTO_INCREMENT
|
|
85
|
+
* imply NOT NULL); `add`/`modify`: always explicit (`NULL` matters for
|
|
86
|
+
* TIMESTAMP under `explicit_defaults_for_timestamp=OFF`, and MySQL rejects
|
|
87
|
+
* an explicit `NULL` on a key column, so key columns say NOT NULL);
|
|
88
|
+
* 4. DEFAULT — a model default only; NEVER `DEFAULT NULL` (it is the implicit
|
|
89
|
+
* default of a nullable column, and `NOT NULL DEFAULT NULL` is ER 1067);
|
|
90
|
+
* expression form for TEXT/BLOB/JSON/GEOMETRY; invented type default for a
|
|
91
|
+
* required default-less column on `add`;
|
|
92
|
+
* 5. COLLATE (native `@db.mysql.collate` or portable `@db.column.collate`);
|
|
93
|
+
* 6. ON UPDATE.
|
|
94
|
+
*/
|
|
95
|
+
function buildColumnDefinition(field, ctx) {
|
|
96
|
+
const sqlType = ctx.typeMapper?.(field) ?? mysqlTypeFromField(field);
|
|
97
|
+
const increment = ctx.purpose !== "add" && (ctx.incrementFields?.has(field.physicalName) ?? false);
|
|
98
|
+
let def = `${qi(field.physicalName)} ${sqlType}`;
|
|
99
|
+
if (increment) def += " AUTO_INCREMENT";
|
|
100
|
+
if (ctx.purpose === "create") {
|
|
101
|
+
if (!field.optional && !field.isPrimaryKey && !increment) def += " NOT NULL";
|
|
102
|
+
} else def += !field.optional || field.isPrimaryKey || increment ? " NOT NULL" : " NULL";
|
|
103
|
+
let inventedDefault = false;
|
|
104
|
+
if (field.defaultValue?.kind === "value") def += ` DEFAULT ${mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value)}`;
|
|
105
|
+
else if (field.defaultValue?.kind === "fn") {
|
|
106
|
+
if (field.defaultValue.fn === "uuid") def += " DEFAULT (UUID())";
|
|
107
|
+
else if (field.defaultValue.fn === "now") def += " DEFAULT CURRENT_TIMESTAMP";
|
|
108
|
+
} else if (ctx.purpose === "add" && !field.optional && !field.isPrimaryKey && !increment && !/^VECTOR\b/i.test(sqlType)) {
|
|
109
|
+
def += ` DEFAULT ${mysqlTypeDefault(sqlType, field)}`;
|
|
110
|
+
inventedDefault = true;
|
|
111
|
+
}
|
|
112
|
+
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
113
|
+
if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
|
|
114
|
+
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
115
|
+
const onUpdate = ctx.onUpdateFields?.get(field.physicalName);
|
|
116
|
+
if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
|
|
117
|
+
return {
|
|
118
|
+
def,
|
|
119
|
+
inventedDefault
|
|
120
|
+
};
|
|
121
|
+
}
|
|
4
122
|
/** Escapes a MySQL identifier by doubling backticks. */
|
|
5
123
|
function esc(name) {
|
|
6
124
|
return name.replace(/`/g, "``");
|
|
@@ -199,45 +317,36 @@ function mysqlTypeFromField(field) {
|
|
|
199
317
|
* Builds a CREATE TABLE IF NOT EXISTS statement with MySQL options.
|
|
200
318
|
*/
|
|
201
319
|
function buildCreateTable(table, fields, foreignKeys, options) {
|
|
202
|
-
const colDefs = [];
|
|
203
320
|
const primaryKeys = fields.filter((f) => f.isPrimaryKey);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
216
|
-
if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
|
|
217
|
-
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
218
|
-
const onUpdate = options?.onUpdateFields?.get(field.physicalName);
|
|
219
|
-
if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
|
|
220
|
-
colDefs.push(def);
|
|
221
|
-
}
|
|
321
|
+
const ctx = {
|
|
322
|
+
incrementFields: options?.incrementFields,
|
|
323
|
+
onUpdateFields: options?.onUpdateFields,
|
|
324
|
+
typeMapper: options?.typeMapper,
|
|
325
|
+
purpose: "create"
|
|
326
|
+
};
|
|
327
|
+
const colDefs = fields.filter((f) => !f.ignored).map((f) => ({
|
|
328
|
+
name: f.physicalName,
|
|
329
|
+
def: buildColumnDefinition(f, ctx).def
|
|
330
|
+
}));
|
|
331
|
+
const constraints = [];
|
|
222
332
|
if (primaryKeys.length === 1) {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
colDefs[i] += " PRIMARY KEY";
|
|
226
|
-
break;
|
|
227
|
-
}
|
|
333
|
+
const pk = colDefs.find((c) => c.name === primaryKeys[0].physicalName);
|
|
334
|
+
if (pk) pk.def += " PRIMARY KEY";
|
|
228
335
|
} else if (primaryKeys.length > 1) {
|
|
229
336
|
const pkCols = primaryKeys.map((pk) => qi(pk.physicalName)).join(", ");
|
|
230
|
-
|
|
337
|
+
constraints.push(`PRIMARY KEY (${pkCols})`);
|
|
231
338
|
}
|
|
232
339
|
if (foreignKeys) for (const fk of foreignKeys.values()) {
|
|
340
|
+
if (options?.deferForeignKeysTo?.has(fk.targetTable)) continue;
|
|
233
341
|
const localCols = fk.fields.map((f) => qi(f)).join(", ");
|
|
234
342
|
const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
|
|
235
343
|
let constraint = `FOREIGN KEY (${localCols}) REFERENCES ${qi(fk.targetTable)} (${targetCols})`;
|
|
236
344
|
if (fk.onDelete) constraint += ` ON DELETE ${refActionToSql(fk.onDelete)}`;
|
|
237
345
|
if (fk.onUpdate) constraint += ` ON UPDATE ${refActionToSql(fk.onUpdate)}`;
|
|
238
|
-
|
|
346
|
+
constraints.push(constraint);
|
|
239
347
|
}
|
|
240
|
-
|
|
348
|
+
const body = [...colDefs.map((c) => c.def), ...constraints].join(", ");
|
|
349
|
+
let sql = `CREATE TABLE IF NOT EXISTS ${quoteTableName(table)} (${body})`;
|
|
241
350
|
const engine = options?.engine ?? "InnoDB";
|
|
242
351
|
const charset = options?.charset ?? "utf8mb4";
|
|
243
352
|
const collation = options?.collation ?? "utf8mb4_unicode_ci";
|
|
@@ -305,14 +414,27 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
305
414
|
_vectorFields = /* @__PURE__ */ new Map();
|
|
306
415
|
/** Default similarity thresholds per vector field (from @db.search.vector.threshold). */
|
|
307
416
|
_vectorThresholds = /* @__PURE__ */ new Map();
|
|
308
|
-
/**
|
|
417
|
+
/**
|
|
418
|
+
* Schema name for INFORMATION_SCHEMA queries — `@db.schema` of the bound
|
|
419
|
+
* table, or `null` (→ `DATABASE()`, the pool's database) when the table
|
|
420
|
+
* declares none or the adapter is an administrative one with no readable
|
|
421
|
+
* (the name-taking schema-sync primitives run on such an adapter).
|
|
422
|
+
*/
|
|
309
423
|
get _schema() {
|
|
310
|
-
return this._table
|
|
424
|
+
return this._table?.schema ?? null;
|
|
311
425
|
}
|
|
312
426
|
constructor(driver) {
|
|
313
427
|
super();
|
|
314
428
|
this.driver = driver;
|
|
315
429
|
}
|
|
430
|
+
/** Every adapter over this pool shares one transaction (since 0.1.128). */
|
|
431
|
+
_transactionOwner() {
|
|
432
|
+
return this.driver;
|
|
433
|
+
}
|
|
434
|
+
/** The dedicated connection of this pool's open transaction, if any. */
|
|
435
|
+
_txConnection() {
|
|
436
|
+
return this._getTransactionState();
|
|
437
|
+
}
|
|
316
438
|
async _beginTransaction() {
|
|
317
439
|
const conn = await this.driver.getConnection();
|
|
318
440
|
await conn.exec("START TRANSACTION");
|
|
@@ -342,7 +464,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
342
464
|
* otherwise the pool-based driver.
|
|
343
465
|
*/
|
|
344
466
|
_exec() {
|
|
345
|
-
return this.
|
|
467
|
+
return this._txConnection() ?? this.driver;
|
|
346
468
|
}
|
|
347
469
|
/** MySQL InnoDB enforces FK constraints natively. */
|
|
348
470
|
supportsNativeForeignKeys() {
|
|
@@ -402,10 +524,10 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
402
524
|
}
|
|
403
525
|
];
|
|
404
526
|
}
|
|
405
|
-
async getExistingTableOptions() {
|
|
527
|
+
async getExistingTableOptions(tableName) {
|
|
406
528
|
const row = await this._exec().get(`SELECT ENGINE, TABLE_COLLATION
|
|
407
529
|
FROM INFORMATION_SCHEMA.TABLES
|
|
408
|
-
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [this._table.tableName, this._schema]);
|
|
530
|
+
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [tableName ?? this._table.tableName, this._schema]);
|
|
409
531
|
if (!row) return [];
|
|
410
532
|
const charset = row.TABLE_COLLATION?.split("_")[0] ?? "utf8mb4";
|
|
411
533
|
return [
|
|
@@ -598,7 +720,8 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
598
720
|
async replaceOne(filter, data, expectedVersion) {
|
|
599
721
|
const where = buildWhere(filter);
|
|
600
722
|
const versionColumn = this._table.versionColumn;
|
|
601
|
-
const
|
|
723
|
+
const full = fillReplacePayload(data, replaceColumnsFor(this._table.fieldDescriptors, this.nativeDefaultFns()), versionColumn);
|
|
724
|
+
const { sql, params } = buildUpdate$1(this.resolveTableName(), full, where, 1, void 0, versionColumn, expectedVersion);
|
|
602
725
|
this._log(sql, params);
|
|
603
726
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
604
727
|
return {
|
|
@@ -632,19 +755,111 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
632
755
|
async prepareTypeMapper() {
|
|
633
756
|
if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
|
|
634
757
|
}
|
|
635
|
-
async ensureTable() {
|
|
758
|
+
async ensureTable(opts) {
|
|
636
759
|
await this.prepareTypeMapper();
|
|
637
|
-
if (this._table
|
|
638
|
-
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys,
|
|
760
|
+
if (this._table.isView) return this._ensureView();
|
|
761
|
+
const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions(opts));
|
|
762
|
+
this._log(sql);
|
|
763
|
+
await this._exec().exec(sql);
|
|
764
|
+
}
|
|
765
|
+
/** The CREATE TABLE options (engine/charset/collation/increment/ON UPDATE/type mapper). */
|
|
766
|
+
_tableOptions(opts) {
|
|
767
|
+
return {
|
|
639
768
|
engine: this._engine,
|
|
640
769
|
charset: this._charset,
|
|
641
770
|
collation: this._collation,
|
|
642
771
|
autoIncrementStart: this._autoIncrementStart,
|
|
643
772
|
incrementFields: this._incrementFields,
|
|
644
|
-
onUpdateFields: this._onUpdateFields
|
|
645
|
-
|
|
773
|
+
onUpdateFields: this._onUpdateFields,
|
|
774
|
+
typeMapper: (field) => this.typeMapper(field),
|
|
775
|
+
deferForeignKeysTo: opts?.deferForeignKeysTo
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
/** The shared column-definition context for ADD/MODIFY statements. */
|
|
779
|
+
_columnCtx(purpose) {
|
|
780
|
+
return {
|
|
781
|
+
incrementFields: this._incrementFields,
|
|
782
|
+
onUpdateFields: this._onUpdateFields,
|
|
783
|
+
typeMapper: (field) => this.typeMapper(field),
|
|
784
|
+
purpose
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
async hasRows(tableName) {
|
|
788
|
+
const sql = `SELECT EXISTS(SELECT 1 FROM ${tableName ? quoteTableName(this._schema ? `${this._schema}.${tableName}` : tableName) : quoteTableName(this.resolveTableName())}) AS present`;
|
|
646
789
|
this._log(sql);
|
|
647
|
-
await this._exec().
|
|
790
|
+
const row = await this._exec().get(sql, []);
|
|
791
|
+
return Boolean(Number(row?.present ?? 0));
|
|
792
|
+
}
|
|
793
|
+
/** Live foreign keys referencing `tableName` (any table of the schema). */
|
|
794
|
+
async getReferencingForeignKeys(tableName) {
|
|
795
|
+
const rows = await this._exec().all(`SELECT TABLE_NAME, CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_COLUMN_NAME
|
|
796
|
+
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
|
|
797
|
+
WHERE REFERENCED_TABLE_NAME = ? AND REFERENCED_TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
798
|
+
ORDER BY TABLE_NAME, CONSTRAINT_NAME, ORDINAL_POSITION`, [tableName, this._schema]);
|
|
799
|
+
const byConstraint = /* @__PURE__ */ new Map();
|
|
800
|
+
for (const r of rows) {
|
|
801
|
+
const key = `${r.TABLE_NAME}\0${r.CONSTRAINT_NAME}`;
|
|
802
|
+
let fk = byConstraint.get(key);
|
|
803
|
+
if (!fk) {
|
|
804
|
+
fk = {
|
|
805
|
+
table: r.TABLE_NAME,
|
|
806
|
+
fields: [],
|
|
807
|
+
targetFields: []
|
|
808
|
+
};
|
|
809
|
+
byConstraint.set(key, fk);
|
|
810
|
+
}
|
|
811
|
+
fk.fields.push(r.COLUMN_NAME);
|
|
812
|
+
fk.targetFields.push(r.REFERENCED_COLUMN_NAME);
|
|
813
|
+
}
|
|
814
|
+
return [...byConstraint.values()];
|
|
815
|
+
}
|
|
816
|
+
async getObjectKind(name) {
|
|
817
|
+
const row = await this._exec().get(`SELECT TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES
|
|
818
|
+
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())`, [name, this._schema]);
|
|
819
|
+
if (!row) return;
|
|
820
|
+
return row.TABLE_TYPE.toUpperCase() === "VIEW" ? "view" : "table";
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* One `ALTER TABLE … [MODIFY …,] DROP PRIMARY KEY, ADD PRIMARY KEY (…)`
|
|
824
|
+
* statement (atomic). The sole owner of the columns entering the key:
|
|
825
|
+
* `syncColumns` adds them without AUTO_INCREMENT and skips their MODIFYs
|
|
826
|
+
* while the key change is pending, and this statement re-declares each of
|
|
827
|
+
* them in full (AUTO_INCREMENT when the model declares increment, explicit
|
|
828
|
+
* NOT NULL) together with the key swap — so an AUTO_INCREMENT column never
|
|
829
|
+
* exists without a key (ER 1075), in safe mode or otherwise. A demoted key
|
|
830
|
+
* column loses AUTO_INCREMENT in the same statement (pre-flight guarantees
|
|
831
|
+
* its model no longer declares increment). Called on an empty table only.
|
|
832
|
+
*/
|
|
833
|
+
async rebuildPrimaryKey(change) {
|
|
834
|
+
const fields = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
835
|
+
const to = new Set(change.to);
|
|
836
|
+
const clauses = [];
|
|
837
|
+
for (const name of change.to) {
|
|
838
|
+
const field = fields.get(name);
|
|
839
|
+
if (field) clauses.push(`MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`);
|
|
840
|
+
}
|
|
841
|
+
if (change.from.length > 0) {
|
|
842
|
+
const placeholders = change.from.map(() => "?").join(", ");
|
|
843
|
+
const live = await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, EXTRA FROM INFORMATION_SCHEMA.COLUMNS
|
|
844
|
+
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
845
|
+
AND COLUMN_NAME IN (${placeholders})`, [
|
|
846
|
+
this._table.tableName,
|
|
847
|
+
this._schema,
|
|
848
|
+
...change.from
|
|
849
|
+
]);
|
|
850
|
+
for (const col of live) {
|
|
851
|
+
if (to.has(col.COLUMN_NAME) || !/auto_increment/i.test(col.EXTRA ?? "")) continue;
|
|
852
|
+
const field = fields.get(col.COLUMN_NAME);
|
|
853
|
+
const def = field ? buildColumnDefinition(field, this._columnCtx("modify")).def : `${qi(col.COLUMN_NAME)} ${col.COLUMN_TYPE.toUpperCase()} NOT NULL`;
|
|
854
|
+
clauses.push(`MODIFY COLUMN ${def}`);
|
|
855
|
+
}
|
|
856
|
+
clauses.push("DROP PRIMARY KEY");
|
|
857
|
+
}
|
|
858
|
+
if (change.to.length > 0) clauses.push(`ADD PRIMARY KEY (${change.to.map((c) => qi(c)).join(", ")})`);
|
|
859
|
+
if (clauses.length === 0) return;
|
|
860
|
+
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ${clauses.join(", ")}`;
|
|
861
|
+
this._log(ddl);
|
|
862
|
+
await this._exec().exec(ddl);
|
|
648
863
|
}
|
|
649
864
|
async _ensureView() {
|
|
650
865
|
const view = this._table;
|
|
@@ -656,15 +871,18 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
656
871
|
return this.getExistingColumnsForTable(this._table.tableName);
|
|
657
872
|
}
|
|
658
873
|
async getExistingColumnsForTable(tableName) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
FROM INFORMATION_SCHEMA.COLUMNS
|
|
662
|
-
|
|
663
|
-
|
|
874
|
+
return (await this._exec().all(`SELECT c.COLUMN_NAME, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, c.SRS_ID,
|
|
875
|
+
(k.COLUMN_NAME IS NOT NULL) AS IS_PK
|
|
876
|
+
FROM INFORMATION_SCHEMA.COLUMNS c
|
|
877
|
+
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
|
|
878
|
+
ON k.TABLE_SCHEMA = c.TABLE_SCHEMA AND k.TABLE_NAME = c.TABLE_NAME
|
|
879
|
+
AND k.COLUMN_NAME = c.COLUMN_NAME AND k.CONSTRAINT_NAME = 'PRIMARY'
|
|
880
|
+
WHERE c.TABLE_NAME = ? AND c.TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
881
|
+
ORDER BY c.ORDINAL_POSITION`, [tableName, this._schema])).map((r) => ({
|
|
664
882
|
name: r.COLUMN_NAME,
|
|
665
883
|
type: r.SRS_ID == null ? r.COLUMN_TYPE.toUpperCase() : `${r.COLUMN_TYPE.toUpperCase()} SRID ${r.SRS_ID}`,
|
|
666
884
|
notnull: r.IS_NULLABLE === "NO",
|
|
667
|
-
pk: r.
|
|
885
|
+
pk: Boolean(Number(r.IS_PK ?? 0)),
|
|
668
886
|
dflt_value: normalizeMysqlDefault(r.COLUMN_DEFAULT)
|
|
669
887
|
}));
|
|
670
888
|
}
|
|
@@ -678,45 +896,44 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
678
896
|
await this._exec().exec(ddl);
|
|
679
897
|
renamed.push(field.physicalName);
|
|
680
898
|
}
|
|
899
|
+
const quotedTable = quoteTableName(tableName);
|
|
681
900
|
for (const field of diff.added) {
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
685
|
-
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
686
|
-
else if (!field.optional && !field.isPrimaryKey) ddl += field.isGeoPoint && !field.encrypted ? ` DEFAULT (ST_SRID(POINT(0, 0), 4326))` : ` DEFAULT ${defaultValueForType(field.designType)}`;
|
|
687
|
-
if (field.collate) {
|
|
688
|
-
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
689
|
-
ddl += ` COLLATE ${nativeCollate ?? collationToMysql(field.collate)}`;
|
|
690
|
-
}
|
|
901
|
+
const { def, inventedDefault } = buildColumnDefinition(field, this._columnCtx("add"));
|
|
902
|
+
const ddl = `ALTER TABLE ${quotedTable} ADD COLUMN ${def}`;
|
|
691
903
|
this._log(ddl);
|
|
692
904
|
await this._exec().exec(ddl);
|
|
905
|
+
if (inventedDefault) {
|
|
906
|
+
const drop = `ALTER TABLE ${quotedTable} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
|
|
907
|
+
this._log(drop);
|
|
908
|
+
await this._exec().exec(drop);
|
|
909
|
+
}
|
|
693
910
|
added.push(field.physicalName);
|
|
694
911
|
}
|
|
912
|
+
const enteringKey = new Set(diff.primaryKeyChanged?.to ?? []);
|
|
913
|
+
const modified = /* @__PURE__ */ new Map();
|
|
695
914
|
for (const { field } of diff.typeChanged ?? []) {
|
|
915
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
696
916
|
const sqlType = this.typeMapper(field);
|
|
697
917
|
if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
|
|
698
918
|
await this._migrateJsonColumnToPoint(tableName, field);
|
|
699
919
|
continue;
|
|
700
920
|
}
|
|
701
|
-
|
|
702
|
-
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
703
|
-
this._log(ddl);
|
|
704
|
-
await this._exec().exec(ddl);
|
|
921
|
+
modified.set(field.physicalName, field);
|
|
705
922
|
}
|
|
706
923
|
for (const { field } of diff.nullableChanged ?? []) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
924
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
925
|
+
if (!field.optional) {
|
|
926
|
+
const sqlType = this.typeMapper(field);
|
|
927
|
+
const fallback = field.defaultValue?.kind === "value" ? mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value) : mysqlTypeDefault(sqlType, field);
|
|
928
|
+
const backfill = `UPDATE ${quotedTable} SET ${qi(field.physicalName)} = ${fallback} WHERE ${qi(field.physicalName)} IS NULL`;
|
|
929
|
+
this._log(backfill);
|
|
930
|
+
await this._exec().exec(backfill);
|
|
931
|
+
}
|
|
932
|
+
modified.set(field.physicalName, field);
|
|
712
933
|
}
|
|
713
|
-
for (const { field } of diff.defaultChanged ?? [])
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
|
|
717
|
-
if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
|
|
718
|
-
else if (field.defaultValue?.kind === "fn") ddl += ` DEFAULT ${field.defaultValue.fn === "now" ? "CURRENT_TIMESTAMP" : field.defaultValue.fn === "uuid" ? "(UUID())" : `(${field.defaultValue.fn}())`}`;
|
|
719
|
-
else ddl += " DEFAULT NULL";
|
|
934
|
+
for (const { field } of diff.defaultChanged ?? []) if (!enteringKey.has(field.physicalName)) modified.set(field.physicalName, field);
|
|
935
|
+
for (const field of modified.values()) {
|
|
936
|
+
const ddl = `ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`;
|
|
720
937
|
this._log(ddl);
|
|
721
938
|
await this._exec().exec(ddl);
|
|
722
939
|
}
|
|
@@ -728,18 +945,14 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
728
945
|
async recreateTable() {
|
|
729
946
|
const tableName = this.resolveTableName();
|
|
730
947
|
const tempName = `${this._table.tableName}__tmp_${Date.now()}`;
|
|
731
|
-
|
|
948
|
+
const tx = this._txConnection();
|
|
949
|
+
const conn = tx ?? await this.driver.getConnection();
|
|
950
|
+
const dedicated = tx === void 0;
|
|
951
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
|
|
732
952
|
try {
|
|
733
|
-
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys,
|
|
734
|
-
engine: this._engine,
|
|
735
|
-
charset: this._charset,
|
|
736
|
-
collation: this._collation,
|
|
737
|
-
autoIncrementStart: this._autoIncrementStart,
|
|
738
|
-
incrementFields: this._incrementFields,
|
|
739
|
-
onUpdateFields: this._onUpdateFields
|
|
740
|
-
});
|
|
953
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions());
|
|
741
954
|
this._log(createSql);
|
|
742
|
-
await
|
|
955
|
+
await conn.exec(createSql);
|
|
743
956
|
const oldCols = (await this.getExistingColumns()).map((c) => c.name);
|
|
744
957
|
const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
|
|
745
958
|
const oldColSet = new Set(oldCols);
|
|
@@ -757,12 +970,13 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
757
970
|
}).join(", ");
|
|
758
971
|
const copySql = `INSERT INTO ${qi(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
|
|
759
972
|
this._log(copySql);
|
|
760
|
-
await
|
|
973
|
+
await conn.exec(copySql);
|
|
761
974
|
}
|
|
762
|
-
await
|
|
763
|
-
await
|
|
975
|
+
await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)}`);
|
|
976
|
+
await conn.exec(`RENAME TABLE ${qi(tempName)} TO ${quoteTableName(tableName)}`);
|
|
764
977
|
} finally {
|
|
765
|
-
await
|
|
978
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
979
|
+
if (dedicated) conn.release();
|
|
766
980
|
}
|
|
767
981
|
}
|
|
768
982
|
async dropTable() {
|
|
@@ -833,18 +1047,38 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
833
1047
|
async syncIndexes() {
|
|
834
1048
|
const tableName = this._table.tableName;
|
|
835
1049
|
const schema = this._schema;
|
|
836
|
-
const
|
|
1050
|
+
const bytesPerChar = mysqlBytesPerChar(this._charset);
|
|
1051
|
+
const fields = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
1052
|
+
const mappedType = (column) => {
|
|
1053
|
+
const field = fields.get(column);
|
|
1054
|
+
return field ? this.typeMapper(field) : void 0;
|
|
1055
|
+
};
|
|
1056
|
+
const desiredPrefix = (column) => {
|
|
1057
|
+
const type = mappedType(column);
|
|
1058
|
+
return type === void 0 ? void 0 : mysqlIndexPrefix(type, bytesPerChar);
|
|
1059
|
+
};
|
|
1060
|
+
const renderPart = (column, prefix) => prefix === void 0 ? column : `${column}(${prefix})`;
|
|
837
1061
|
await this.syncIndexesWithDiff({
|
|
838
1062
|
listExisting: async () => {
|
|
839
|
-
|
|
840
|
-
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX SEPARATOR ',') AS columns
|
|
1063
|
+
const rows = await this._exec().all(`SELECT INDEX_NAME, COLUMN_NAME, SUB_PART, SEQ_IN_INDEX
|
|
841
1064
|
FROM INFORMATION_SCHEMA.STATISTICS
|
|
842
1065
|
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1066
|
+
ORDER BY INDEX_NAME, SEQ_IN_INDEX`, [tableName, schema]);
|
|
1067
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1068
|
+
for (const r of rows) {
|
|
1069
|
+
let subPart = r.SUB_PART ?? void 0;
|
|
1070
|
+
const type = mappedType(r.COLUMN_NAME);
|
|
1071
|
+
if (subPart !== void 0 && type !== void 0 && mysqlCharLength(type) === subPart) subPart = void 0;
|
|
1072
|
+
const parts = byName.get(r.INDEX_NAME) ?? [];
|
|
1073
|
+
parts.push(renderPart(r.COLUMN_NAME, subPart));
|
|
1074
|
+
byName.set(r.INDEX_NAME, parts);
|
|
1075
|
+
}
|
|
1076
|
+
return [...byName].map(([name, columns]) => ({
|
|
1077
|
+
name,
|
|
1078
|
+
columns
|
|
846
1079
|
}));
|
|
847
1080
|
},
|
|
1081
|
+
renderDesiredColumn: (_index, f) => renderPart(f.name, desiredPrefix(f.name)),
|
|
848
1082
|
createIndex: async (index) => {
|
|
849
1083
|
if (index.type === "geo") {
|
|
850
1084
|
const fieldMeta = this._table.fieldDescriptors.find((f) => f.physicalName === index.fields[0]?.name);
|
|
@@ -861,7 +1095,10 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
861
1095
|
const fulltext = index.type === "fulltext" ? "FULLTEXT " : "";
|
|
862
1096
|
const isFulltext = index.type === "fulltext";
|
|
863
1097
|
const cols = index.fields.map((f) => {
|
|
864
|
-
|
|
1098
|
+
const col = qi(f.name);
|
|
1099
|
+
const prefix = isFulltext ? void 0 : desiredPrefix(f.name);
|
|
1100
|
+
const order = isFulltext ? "" : ` ${f.sort === "desc" ? "DESC" : "ASC"}`;
|
|
1101
|
+
return `${col}${prefix === void 0 ? "" : `(${prefix})`}${order}`;
|
|
865
1102
|
}).join(", ");
|
|
866
1103
|
const sql = `CREATE ${fulltext}${unique}INDEX ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${cols})`;
|
|
867
1104
|
this._log(sql);
|
|
@@ -1149,7 +1386,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
1149
1386
|
`UPDATE ${quotedTable} SET ${tmp} = ST_SRID(POINT(CAST(${col}->>'$[0]' AS DOUBLE), CAST(${col}->>'$[1]' AS DOUBLE)), 4326) WHERE ${col} IS NOT NULL`,
|
|
1150
1387
|
`ALTER TABLE ${quotedTable} DROP COLUMN ${col}`,
|
|
1151
1388
|
`ALTER TABLE ${quotedTable} RENAME COLUMN ${tmp} TO ${col}`,
|
|
1152
|
-
...field.optional || field.isPrimaryKey ? [] : [`ALTER TABLE ${quotedTable} MODIFY COLUMN ${
|
|
1389
|
+
...field.optional || field.isPrimaryKey ? [] : [`ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`]
|
|
1153
1390
|
];
|
|
1154
1391
|
for (const ddl of steps) {
|
|
1155
1392
|
this._log(ddl);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-mysql",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.128",
|
|
4
4
|
"description": "MySQL adapter for @atscript/db with mysql2 driver support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -41,19 +41,19 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@atscript/core": "^0.1.
|
|
45
|
-
"@atscript/typescript": "^0.1.
|
|
46
|
-
"@uniqu/core": "^0.1.
|
|
44
|
+
"@atscript/core": "^0.1.92",
|
|
45
|
+
"@atscript/typescript": "^0.1.92",
|
|
46
|
+
"@uniqu/core": "^0.1.8",
|
|
47
47
|
"mysql2": "^3.11.0",
|
|
48
|
-
"unplugin-atscript": "^0.1.
|
|
48
|
+
"unplugin-atscript": "^0.1.92"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@atscript/core": "^0.1.
|
|
52
|
-
"@atscript/typescript": "^0.1.
|
|
53
|
-
"@uniqu/core": "^0.1.
|
|
51
|
+
"@atscript/core": "^0.1.92",
|
|
52
|
+
"@atscript/typescript": "^0.1.92",
|
|
53
|
+
"@uniqu/core": "^0.1.8",
|
|
54
54
|
"mysql2": ">=3.0.0",
|
|
55
|
-
"@atscript/db": "^0.1.
|
|
56
|
-
"@atscript/db
|
|
55
|
+
"@atscript/db-sql-tools": "^0.1.128",
|
|
56
|
+
"@atscript/db": "^0.1.128"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"mysql2": {
|