@atscript/db-mysql 0.1.139 → 0.1.141
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 +153 -117
- package/dist/index.d.cts +11 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.mjs +154 -118
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.mjs +1 -0
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -94,9 +94,17 @@ function mysqlIndexPrefix(mappedType, bytesPerChar) {
|
|
|
94
94
|
* required default-less column on `add`;
|
|
95
95
|
* 5. COLLATE (native `@db.mysql.collate` or portable `@db.column.collate`);
|
|
96
96
|
* 6. ON UPDATE.
|
|
97
|
+
*
|
|
98
|
+
* A `@db.column.derived` field (since 0.1.141) renders as a VIRTUAL generated
|
|
99
|
+
* column — `type [COLLATE …] GENERATED ALWAYS AS (<json extraction>) VIRTUAL`
|
|
100
|
+
* — with no nullability, DEFAULT, AUTO_INCREMENT or ON UPDATE, on every purpose.
|
|
97
101
|
*/
|
|
98
102
|
function buildColumnDefinition(field, ctx) {
|
|
99
103
|
const sqlType = ctx.typeMapper?.(field) ?? mysqlTypeFromField(field);
|
|
104
|
+
if (field.derived) return {
|
|
105
|
+
def: `${qi(field.physicalName)} ${sqlType}${mysqlCollateClause(field)} GENERATED ALWAYS AS (${(0, _atscript_db_sql_tools.derivedColumnExpr)(mysqlDialect, field)}) VIRTUAL`,
|
|
106
|
+
inventedDefault: false
|
|
107
|
+
};
|
|
100
108
|
const increment = ctx.purpose !== "add" && (ctx.incrementFields?.has(field.physicalName) ?? false);
|
|
101
109
|
let def = `${qi(field.physicalName)} ${sqlType}`;
|
|
102
110
|
if (increment) def += " AUTO_INCREMENT";
|
|
@@ -112,9 +120,7 @@ function buildColumnDefinition(field, ctx) {
|
|
|
112
120
|
def += ` DEFAULT ${mysqlTypeDefault(sqlType, field)}`;
|
|
113
121
|
inventedDefault = true;
|
|
114
122
|
}
|
|
115
|
-
|
|
116
|
-
if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
|
|
117
|
-
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
123
|
+
def += mysqlCollateClause(field);
|
|
118
124
|
const onUpdate = ctx.onUpdateFields?.get(field.physicalName);
|
|
119
125
|
if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
|
|
120
126
|
return {
|
|
@@ -340,6 +346,15 @@ function buildAggregateCount(table, where, controls) {
|
|
|
340
346
|
/**
|
|
341
347
|
* Maps portable collation values to MySQL collation names.
|
|
342
348
|
*/
|
|
349
|
+
/**
|
|
350
|
+
* The `COLLATE` clause of a column (native `@db.mysql.collate`, else the
|
|
351
|
+
* portable `@db.column.collate` mapping) — `""` when none applies.
|
|
352
|
+
*/
|
|
353
|
+
function mysqlCollateClause(field) {
|
|
354
|
+
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
355
|
+
if (nativeCollate) return ` COLLATE ${nativeCollate}`;
|
|
356
|
+
return field.collate ? ` COLLATE ${collationToMysql(field.collate)}` : "";
|
|
357
|
+
}
|
|
343
358
|
function collationToMysql(collation) {
|
|
344
359
|
switch (collation) {
|
|
345
360
|
case "binary": return "utf8mb4_bin";
|
|
@@ -395,7 +410,7 @@ function mysqlTypeFromField(field) {
|
|
|
395
410
|
const maxLen = (metadata?.get("expect.maxLength"))?.length;
|
|
396
411
|
if (maxLen !== void 0 && maxLen <= 65535) return `VARCHAR(${maxLen})`;
|
|
397
412
|
if (maxLen !== void 0 && maxLen > 65535) return "LONGTEXT";
|
|
398
|
-
if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
|
|
413
|
+
if (field.isPrimaryKey || field.defaultValue || field.derived) return "VARCHAR(255)";
|
|
399
414
|
return "TEXT";
|
|
400
415
|
}
|
|
401
416
|
case "json":
|
|
@@ -578,6 +593,28 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
578
593
|
_exec() {
|
|
579
594
|
return this._txConnection() ?? this.driver;
|
|
580
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Runs `fn` on one connection (the transaction's, or a dedicated one) with
|
|
598
|
+
* `STRICT_ALL_TABLES` added to the session `sql_mode`, restoring the
|
|
599
|
+
* previous mode after. Schema sync's converting statements (`MODIFY
|
|
600
|
+
* COLUMN`, the recreate copy) then fail on a value that does not convert
|
|
601
|
+
* instead of coercing or truncating it — a non-strict server (RDS defaults
|
|
602
|
+
* to `NO_ENGINE_SUBSTITUTION`) would turn `'abc'` into `0` silently.
|
|
603
|
+
*/
|
|
604
|
+
async _withStrictSession(fn) {
|
|
605
|
+
const tx = this._txConnection();
|
|
606
|
+
const conn = tx ?? await this.driver.getConnection();
|
|
607
|
+
try {
|
|
608
|
+
await conn.exec("SET @atscript_sql_mode = @@SESSION.sql_mode, SESSION sql_mode = CONCAT_WS(',', @@SESSION.sql_mode, 'STRICT_ALL_TABLES')");
|
|
609
|
+
try {
|
|
610
|
+
return await fn(conn);
|
|
611
|
+
} finally {
|
|
612
|
+
await conn.exec("SET SESSION sql_mode = @atscript_sql_mode");
|
|
613
|
+
}
|
|
614
|
+
} finally {
|
|
615
|
+
if (!tx) conn.release();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
581
618
|
/** MySQL InnoDB enforces FK constraints natively. */
|
|
582
619
|
supportsNativeForeignKeys() {
|
|
583
620
|
return true;
|
|
@@ -836,7 +873,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
836
873
|
}
|
|
837
874
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
838
875
|
const where = buildWhere(filter);
|
|
839
|
-
const versionColumn = this._table.
|
|
876
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
840
877
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where, 1, ops, versionColumn, expectedVersion);
|
|
841
878
|
this._log(sql, params);
|
|
842
879
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -847,7 +884,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
847
884
|
}
|
|
848
885
|
async updateMany(filter, data, ops) {
|
|
849
886
|
const where = buildWhere(filter);
|
|
850
|
-
const versionColumn = this._table.
|
|
887
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
851
888
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where, void 0, ops, versionColumn);
|
|
852
889
|
this._log(sql, params);
|
|
853
890
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -858,7 +895,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
858
895
|
}
|
|
859
896
|
async replaceOne(filter, data, expectedVersion) {
|
|
860
897
|
const where = buildWhere(filter);
|
|
861
|
-
const versionColumn = this._table.
|
|
898
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
862
899
|
const full = (0, _atscript_db_sql_tools.fillReplacePayload)(data, (0, _atscript_db_sql_tools.replaceColumnsFor)(this._table.fieldDescriptors, this.nativeDefaultFns()), versionColumn);
|
|
863
900
|
const { sql, params } = buildUpdate(this.resolveTableName(), full, where, 1, void 0, versionColumn, expectedVersion);
|
|
864
901
|
this._log(sql, params);
|
|
@@ -870,7 +907,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
870
907
|
}
|
|
871
908
|
async replaceMany(filter, data) {
|
|
872
909
|
const where = buildWhere(filter);
|
|
873
|
-
const versionColumn = this._table.
|
|
910
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
874
911
|
const { sql, params } = buildUpdate(this.resolveTableName(), data, where, void 0, void 0, versionColumn);
|
|
875
912
|
this._log(sql, params);
|
|
876
913
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -998,7 +1035,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
998
1035
|
if (clauses.length === 0) return;
|
|
999
1036
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ${clauses.join(", ")}`;
|
|
1000
1037
|
this._log(ddl);
|
|
1001
|
-
await this.
|
|
1038
|
+
await this._withStrictSession((conn) => conn.exec(ddl));
|
|
1002
1039
|
}
|
|
1003
1040
|
async _ensureView() {
|
|
1004
1041
|
const view = this._table;
|
|
@@ -1010,131 +1047,130 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1010
1047
|
return this.getExistingColumnsForTable(this._table.tableName);
|
|
1011
1048
|
}
|
|
1012
1049
|
async getExistingColumnsForTable(tableName) {
|
|
1013
|
-
return (
|
|
1050
|
+
return this._readColumns(this._exec(), tableName);
|
|
1051
|
+
}
|
|
1052
|
+
/** Live columns of `tableName`, read through `exec` (a held connection, or the pool). */
|
|
1053
|
+
async _readColumns(exec, tableName) {
|
|
1054
|
+
return (await exec.all(`SELECT c.COLUMN_NAME, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, c.SRS_ID, c.EXTRA,
|
|
1014
1055
|
(k.COLUMN_NAME IS NOT NULL) AS IS_PK
|
|
1015
1056
|
FROM INFORMATION_SCHEMA.COLUMNS c
|
|
1016
1057
|
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
|
|
1017
1058
|
ON k.TABLE_SCHEMA = c.TABLE_SCHEMA AND k.TABLE_NAME = c.TABLE_NAME
|
|
1018
1059
|
AND k.COLUMN_NAME = c.COLUMN_NAME AND k.CONSTRAINT_NAME = 'PRIMARY'
|
|
1019
1060
|
WHERE c.TABLE_NAME = ? AND c.TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
1020
|
-
ORDER BY c.ORDINAL_POSITION`, [tableName, this._schema])).map((r) =>
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1061
|
+
ORDER BY c.ORDINAL_POSITION`, [tableName, this._schema])).map((r) => {
|
|
1062
|
+
const column = {
|
|
1063
|
+
name: r.COLUMN_NAME,
|
|
1064
|
+
type: r.SRS_ID == null ? r.COLUMN_TYPE.toUpperCase() : `${r.COLUMN_TYPE.toUpperCase()} SRID ${r.SRS_ID}`,
|
|
1065
|
+
notnull: r.IS_NULLABLE === "NO",
|
|
1066
|
+
pk: Boolean(Number(r.IS_PK ?? 0)),
|
|
1067
|
+
dflt_value: normalizeMysqlDefault(r.COLUMN_DEFAULT)
|
|
1068
|
+
};
|
|
1069
|
+
if (/\b(VIRTUAL|STORED) GENERATED\b/i.test(r.EXTRA ?? "")) column.generated = true;
|
|
1070
|
+
return column;
|
|
1071
|
+
});
|
|
1027
1072
|
}
|
|
1028
1073
|
async syncColumns(diff) {
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
const quotedTable = quoteTableName(tableName);
|
|
1039
|
-
for (const field of diff.added) {
|
|
1040
|
-
const { def, inventedDefault } = buildColumnDefinition(field, this._columnCtx("add"));
|
|
1041
|
-
const ddl = `ALTER TABLE ${quotedTable} ADD COLUMN ${def}`;
|
|
1042
|
-
this._log(ddl);
|
|
1043
|
-
await this._exec().exec(ddl);
|
|
1044
|
-
if (inventedDefault) {
|
|
1045
|
-
const drop = `ALTER TABLE ${quotedTable} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
|
|
1046
|
-
this._log(drop);
|
|
1047
|
-
await this._exec().exec(drop);
|
|
1074
|
+
return this._withStrictSession(async (conn) => {
|
|
1075
|
+
const tableName = this.resolveTableName();
|
|
1076
|
+
const added = [];
|
|
1077
|
+
const renamed = [];
|
|
1078
|
+
for (const { field, oldName } of diff.renamed ?? []) {
|
|
1079
|
+
const ddl = `ALTER TABLE ${quoteTableName(tableName)} RENAME COLUMN ${qi(oldName)} TO ${qi(field.physicalName)}`;
|
|
1080
|
+
this._log(ddl);
|
|
1081
|
+
await conn.exec(ddl);
|
|
1082
|
+
renamed.push(field.physicalName);
|
|
1048
1083
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1084
|
+
const quotedTable = quoteTableName(tableName);
|
|
1085
|
+
for (const field of diff.added) {
|
|
1086
|
+
const { def, inventedDefault } = buildColumnDefinition(field, this._columnCtx("add"));
|
|
1087
|
+
const ddl = `ALTER TABLE ${quotedTable} ADD COLUMN ${def}`;
|
|
1088
|
+
this._log(ddl);
|
|
1089
|
+
await conn.exec(ddl);
|
|
1090
|
+
if (inventedDefault) {
|
|
1091
|
+
const drop = `ALTER TABLE ${quotedTable} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
|
|
1092
|
+
this._log(drop);
|
|
1093
|
+
await conn.exec(drop);
|
|
1094
|
+
}
|
|
1095
|
+
added.push(field.physicalName);
|
|
1059
1096
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
if (!field.optional) {
|
|
1097
|
+
const enteringKey = new Set(diff.primaryKeyChanged?.to ?? []);
|
|
1098
|
+
const modified = /* @__PURE__ */ new Map();
|
|
1099
|
+
for (const { field } of diff.typeChanged ?? []) {
|
|
1100
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
1065
1101
|
const sqlType = this.typeMapper(field);
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1102
|
+
if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
|
|
1103
|
+
await this._migrateJsonColumnToPoint(conn, tableName, field);
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
modified.set(field.physicalName, field);
|
|
1070
1107
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1108
|
+
for (const { field } of diff.nullableChanged ?? []) {
|
|
1109
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
1110
|
+
if (!field.optional) {
|
|
1111
|
+
const sqlType = this.typeMapper(field);
|
|
1112
|
+
const fallback = field.defaultValue?.kind === "value" ? mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value) : mysqlTypeDefault(sqlType, field);
|
|
1113
|
+
const backfill = `UPDATE ${quotedTable} SET ${qi(field.physicalName)} = ${fallback} WHERE ${qi(field.physicalName)} IS NULL`;
|
|
1114
|
+
this._log(backfill);
|
|
1115
|
+
await conn.exec(backfill);
|
|
1116
|
+
}
|
|
1117
|
+
modified.set(field.physicalName, field);
|
|
1118
|
+
}
|
|
1119
|
+
for (const { field } of diff.defaultChanged ?? []) if (!enteringKey.has(field.physicalName)) modified.set(field.physicalName, field);
|
|
1120
|
+
for (const field of modified.values()) {
|
|
1121
|
+
const ddl = `ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`;
|
|
1122
|
+
this._log(ddl);
|
|
1123
|
+
await conn.exec(ddl);
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
added,
|
|
1127
|
+
renamed
|
|
1128
|
+
};
|
|
1129
|
+
});
|
|
1083
1130
|
}
|
|
1084
1131
|
async recreateTable() {
|
|
1085
1132
|
const tableName = this.resolveTableName();
|
|
1086
1133
|
const tempName = `${this._table.tableName}__tmp_${Date.now()}`;
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
return
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
await conn.exec(
|
|
1134
|
+
return this._withStrictSession(async (conn) => {
|
|
1135
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
|
|
1136
|
+
let originalDropped = false;
|
|
1137
|
+
try {
|
|
1138
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions());
|
|
1139
|
+
this._log(createSql);
|
|
1140
|
+
await conn.exec(createSql);
|
|
1141
|
+
const oldCols = (await this._readColumns(conn, this._table.tableName)).map((c) => c.name);
|
|
1142
|
+
const newCols = this._table.storedDescriptors.map((f) => f.physicalName);
|
|
1143
|
+
const oldColSet = new Set(oldCols);
|
|
1144
|
+
const commonCols = newCols.filter((c) => oldColSet.has(c));
|
|
1145
|
+
if (commonCols.length > 0) {
|
|
1146
|
+
const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
1147
|
+
const colNames = commonCols.map((c) => qi(c)).join(", ");
|
|
1148
|
+
const selectExprs = commonCols.map((c) => {
|
|
1149
|
+
const field = fieldsByName.get(c);
|
|
1150
|
+
if (field && !field.optional && !field.isPrimaryKey) {
|
|
1151
|
+
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);
|
|
1152
|
+
return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
|
|
1153
|
+
}
|
|
1154
|
+
return qi(c);
|
|
1155
|
+
}).join(", ");
|
|
1156
|
+
const copySql = `INSERT INTO ${qi(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
|
|
1157
|
+
this._log(copySql);
|
|
1158
|
+
await conn.exec(copySql);
|
|
1159
|
+
}
|
|
1160
|
+
await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)}`);
|
|
1161
|
+
originalDropped = true;
|
|
1162
|
+
await conn.exec(`RENAME TABLE ${qi(tempName)} TO ${quoteTableName(tableName)}`);
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
if (originalDropped) throw new Error(`Recreate of "${tableName}" failed after the original table was dropped; its rows are in "${tempName}"`, { cause: error });
|
|
1165
|
+
await conn.exec(`DROP TABLE IF EXISTS ${qi(tempName)}`).catch(() => void 0);
|
|
1166
|
+
throw error;
|
|
1167
|
+
} finally {
|
|
1168
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
1114
1169
|
}
|
|
1115
|
-
|
|
1116
|
-
originalDropped = true;
|
|
1117
|
-
await conn.exec(`RENAME TABLE ${qi(tempName)} TO ${quoteTableName(tableName)}`);
|
|
1118
|
-
} catch (error) {
|
|
1119
|
-
if (originalDropped) throw new Error(`Recreate of "${tableName}" failed after the original table was dropped; its rows are in "${tempName}"`, { cause: error });
|
|
1120
|
-
await conn.exec(`DROP TABLE IF EXISTS ${qi(tempName)}`).catch(() => void 0);
|
|
1121
|
-
throw error;
|
|
1122
|
-
} finally {
|
|
1123
|
-
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
1124
|
-
if (dedicated) conn.release();
|
|
1125
|
-
}
|
|
1170
|
+
});
|
|
1126
1171
|
}
|
|
1127
1172
|
async dropTable() {
|
|
1128
|
-
|
|
1129
|
-
this._log(ddl);
|
|
1130
|
-
const conn = await this.driver.getConnection();
|
|
1131
|
-
await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
|
|
1132
|
-
try {
|
|
1133
|
-
await conn.exec(ddl);
|
|
1134
|
-
} finally {
|
|
1135
|
-
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
1136
|
-
conn.release();
|
|
1137
|
-
}
|
|
1173
|
+
return this.dropTableByName(this.resolveTableName());
|
|
1138
1174
|
}
|
|
1139
1175
|
async dropColumns(columns) {
|
|
1140
1176
|
const tableName = this.resolveTableName();
|
|
@@ -1522,7 +1558,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1522
1558
|
* Migrates a v1 JSON `[lng, lat]` column to native `POINT SRID 4326` via a
|
|
1523
1559
|
* temp column (MySQL has no JSON→geometry cast for MODIFY COLUMN).
|
|
1524
1560
|
*/
|
|
1525
|
-
async _migrateJsonColumnToPoint(tableName, field) {
|
|
1561
|
+
async _migrateJsonColumnToPoint(conn, tableName, field) {
|
|
1526
1562
|
const col = qi(field.physicalName);
|
|
1527
1563
|
const tmp = qi(`${field.physicalName}__geo_mig`);
|
|
1528
1564
|
const quotedTable = quoteTableName(tableName);
|
|
@@ -1535,7 +1571,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1535
1571
|
];
|
|
1536
1572
|
for (const ddl of steps) {
|
|
1537
1573
|
this._log(ddl);
|
|
1538
|
-
await
|
|
1574
|
+
await conn.exec(ddl);
|
|
1539
1575
|
}
|
|
1540
1576
|
}
|
|
1541
1577
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -127,6 +127,15 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
127
127
|
* otherwise the pool-based driver.
|
|
128
128
|
*/
|
|
129
129
|
private _exec;
|
|
130
|
+
/**
|
|
131
|
+
* Runs `fn` on one connection (the transaction's, or a dedicated one) with
|
|
132
|
+
* `STRICT_ALL_TABLES` added to the session `sql_mode`, restoring the
|
|
133
|
+
* previous mode after. Schema sync's converting statements (`MODIFY
|
|
134
|
+
* COLUMN`, the recreate copy) then fail on a value that does not convert
|
|
135
|
+
* instead of coercing or truncating it — a non-strict server (RDS defaults
|
|
136
|
+
* to `NO_ENGINE_SUBSTITUTION`) would turn `'abc'` into `0` silently.
|
|
137
|
+
*/
|
|
138
|
+
private _withStrictSession;
|
|
130
139
|
/** MySQL InnoDB enforces FK constraints natively. */
|
|
131
140
|
supportsNativeForeignKeys(): boolean;
|
|
132
141
|
prepareId(id: unknown, _fieldType: unknown): unknown;
|
|
@@ -208,6 +217,8 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
208
217
|
private _ensureView;
|
|
209
218
|
getExistingColumns(): Promise<TExistingColumn[]>;
|
|
210
219
|
getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
|
|
220
|
+
/** Live columns of `tableName`, read through `exec` (a held connection, or the pool). */
|
|
221
|
+
private _readColumns;
|
|
211
222
|
syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
|
|
212
223
|
recreateTable(): Promise<void>;
|
|
213
224
|
dropTable(): Promise<void>;
|
package/dist/index.d.mts
CHANGED
|
@@ -127,6 +127,15 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
127
127
|
* otherwise the pool-based driver.
|
|
128
128
|
*/
|
|
129
129
|
private _exec;
|
|
130
|
+
/**
|
|
131
|
+
* Runs `fn` on one connection (the transaction's, or a dedicated one) with
|
|
132
|
+
* `STRICT_ALL_TABLES` added to the session `sql_mode`, restoring the
|
|
133
|
+
* previous mode after. Schema sync's converting statements (`MODIFY
|
|
134
|
+
* COLUMN`, the recreate copy) then fail on a value that does not convert
|
|
135
|
+
* instead of coercing or truncating it — a non-strict server (RDS defaults
|
|
136
|
+
* to `NO_ENGINE_SUBSTITUTION`) would turn `'abc'` into `0` silently.
|
|
137
|
+
*/
|
|
138
|
+
private _withStrictSession;
|
|
130
139
|
/** MySQL InnoDB enforces FK constraints natively. */
|
|
131
140
|
supportsNativeForeignKeys(): boolean;
|
|
132
141
|
prepareId(id: unknown, _fieldType: unknown): unknown;
|
|
@@ -208,6 +217,8 @@ declare class MysqlAdapter extends BaseDbAdapter {
|
|
|
208
217
|
private _ensureView;
|
|
209
218
|
getExistingColumns(): Promise<TExistingColumn[]>;
|
|
210
219
|
getExistingColumnsForTable(tableName: string): Promise<TExistingColumn[]>;
|
|
220
|
+
/** Live columns of `tableName`, read through `exec` (a held connection, or the pool). */
|
|
221
|
+
private _readColumns;
|
|
211
222
|
syncColumns(diff: TColumnDiff): Promise<TSyncColumnResult>;
|
|
212
223
|
recreateTable(): Promise<void>;
|
|
213
224
|
dropTable(): Promise<void>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ALL_AGGREGATE_FNS, ALL_BUCKET_UNITS, BaseDbAdapter, DbError, DbSpace, bucketTimeZoneUnavailable } from "@atscript/db";
|
|
2
2
|
import { resolveAggregateSearch } from "@atscript/db/agg";
|
|
3
|
-
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, fillReplacePayload, geoWindowFromControls, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral, toSqlValue } from "@atscript/db-sql-tools";
|
|
3
|
+
import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildGeoSearchCount, buildGeoSearchSelect, buildInsert, buildInsertMany, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, derivedColumnExpr, fillReplacePayload, geoWindowFromControls, insertManyColumns, jsonDollarPath, normalizeGeoPointValue, parseRegexString, refActionToSql, renameGeoDistance, replaceColumnsFor, sqlTimeZoneLiteral, toSqlValue } from "@atscript/db-sql-tools";
|
|
4
4
|
import { BUCKET_MAX_INSTANT, BUCKET_MIN_INSTANT } from "@uniqu/core";
|
|
5
5
|
//#region src/sql-builder.ts
|
|
6
6
|
/** TEXT / BLOB families (`TINYTEXT` … `LONGBLOB`). */
|
|
@@ -93,9 +93,17 @@ function mysqlIndexPrefix(mappedType, bytesPerChar) {
|
|
|
93
93
|
* required default-less column on `add`;
|
|
94
94
|
* 5. COLLATE (native `@db.mysql.collate` or portable `@db.column.collate`);
|
|
95
95
|
* 6. ON UPDATE.
|
|
96
|
+
*
|
|
97
|
+
* A `@db.column.derived` field (since 0.1.141) renders as a VIRTUAL generated
|
|
98
|
+
* column — `type [COLLATE …] GENERATED ALWAYS AS (<json extraction>) VIRTUAL`
|
|
99
|
+
* — with no nullability, DEFAULT, AUTO_INCREMENT or ON UPDATE, on every purpose.
|
|
96
100
|
*/
|
|
97
101
|
function buildColumnDefinition(field, ctx) {
|
|
98
102
|
const sqlType = ctx.typeMapper?.(field) ?? mysqlTypeFromField(field);
|
|
103
|
+
if (field.derived) return {
|
|
104
|
+
def: `${qi(field.physicalName)} ${sqlType}${mysqlCollateClause(field)} GENERATED ALWAYS AS (${derivedColumnExpr(mysqlDialect, field)}) VIRTUAL`,
|
|
105
|
+
inventedDefault: false
|
|
106
|
+
};
|
|
99
107
|
const increment = ctx.purpose !== "add" && (ctx.incrementFields?.has(field.physicalName) ?? false);
|
|
100
108
|
let def = `${qi(field.physicalName)} ${sqlType}`;
|
|
101
109
|
if (increment) def += " AUTO_INCREMENT";
|
|
@@ -111,9 +119,7 @@ function buildColumnDefinition(field, ctx) {
|
|
|
111
119
|
def += ` DEFAULT ${mysqlTypeDefault(sqlType, field)}`;
|
|
112
120
|
inventedDefault = true;
|
|
113
121
|
}
|
|
114
|
-
|
|
115
|
-
if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
|
|
116
|
-
else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
|
|
122
|
+
def += mysqlCollateClause(field);
|
|
117
123
|
const onUpdate = ctx.onUpdateFields?.get(field.physicalName);
|
|
118
124
|
if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
|
|
119
125
|
return {
|
|
@@ -339,6 +345,15 @@ function buildAggregateCount$1(table, where, controls) {
|
|
|
339
345
|
/**
|
|
340
346
|
* Maps portable collation values to MySQL collation names.
|
|
341
347
|
*/
|
|
348
|
+
/**
|
|
349
|
+
* The `COLLATE` clause of a column (native `@db.mysql.collate`, else the
|
|
350
|
+
* portable `@db.column.collate` mapping) — `""` when none applies.
|
|
351
|
+
*/
|
|
352
|
+
function mysqlCollateClause(field) {
|
|
353
|
+
const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
|
|
354
|
+
if (nativeCollate) return ` COLLATE ${nativeCollate}`;
|
|
355
|
+
return field.collate ? ` COLLATE ${collationToMysql(field.collate)}` : "";
|
|
356
|
+
}
|
|
342
357
|
function collationToMysql(collation) {
|
|
343
358
|
switch (collation) {
|
|
344
359
|
case "binary": return "utf8mb4_bin";
|
|
@@ -394,7 +409,7 @@ function mysqlTypeFromField(field) {
|
|
|
394
409
|
const maxLen = (metadata?.get("expect.maxLength"))?.length;
|
|
395
410
|
if (maxLen !== void 0 && maxLen <= 65535) return `VARCHAR(${maxLen})`;
|
|
396
411
|
if (maxLen !== void 0 && maxLen > 65535) return "LONGTEXT";
|
|
397
|
-
if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
|
|
412
|
+
if (field.isPrimaryKey || field.defaultValue || field.derived) return "VARCHAR(255)";
|
|
398
413
|
return "TEXT";
|
|
399
414
|
}
|
|
400
415
|
case "json":
|
|
@@ -577,6 +592,28 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
577
592
|
_exec() {
|
|
578
593
|
return this._txConnection() ?? this.driver;
|
|
579
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* Runs `fn` on one connection (the transaction's, or a dedicated one) with
|
|
597
|
+
* `STRICT_ALL_TABLES` added to the session `sql_mode`, restoring the
|
|
598
|
+
* previous mode after. Schema sync's converting statements (`MODIFY
|
|
599
|
+
* COLUMN`, the recreate copy) then fail on a value that does not convert
|
|
600
|
+
* instead of coercing or truncating it — a non-strict server (RDS defaults
|
|
601
|
+
* to `NO_ENGINE_SUBSTITUTION`) would turn `'abc'` into `0` silently.
|
|
602
|
+
*/
|
|
603
|
+
async _withStrictSession(fn) {
|
|
604
|
+
const tx = this._txConnection();
|
|
605
|
+
const conn = tx ?? await this.driver.getConnection();
|
|
606
|
+
try {
|
|
607
|
+
await conn.exec("SET @atscript_sql_mode = @@SESSION.sql_mode, SESSION sql_mode = CONCAT_WS(',', @@SESSION.sql_mode, 'STRICT_ALL_TABLES')");
|
|
608
|
+
try {
|
|
609
|
+
return await fn(conn);
|
|
610
|
+
} finally {
|
|
611
|
+
await conn.exec("SET SESSION sql_mode = @atscript_sql_mode");
|
|
612
|
+
}
|
|
613
|
+
} finally {
|
|
614
|
+
if (!tx) conn.release();
|
|
615
|
+
}
|
|
616
|
+
}
|
|
580
617
|
/** MySQL InnoDB enforces FK constraints natively. */
|
|
581
618
|
supportsNativeForeignKeys() {
|
|
582
619
|
return true;
|
|
@@ -835,7 +872,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
835
872
|
}
|
|
836
873
|
async updateOne(filter, data, ops, expectedVersion) {
|
|
837
874
|
const where = buildWhere(filter);
|
|
838
|
-
const versionColumn = this._table.
|
|
875
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
839
876
|
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, 1, ops, versionColumn, expectedVersion);
|
|
840
877
|
this._log(sql, params);
|
|
841
878
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -846,7 +883,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
846
883
|
}
|
|
847
884
|
async updateMany(filter, data, ops) {
|
|
848
885
|
const where = buildWhere(filter);
|
|
849
|
-
const versionColumn = this._table.
|
|
886
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
850
887
|
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, void 0, ops, versionColumn);
|
|
851
888
|
this._log(sql, params);
|
|
852
889
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -857,7 +894,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
857
894
|
}
|
|
858
895
|
async replaceOne(filter, data, expectedVersion) {
|
|
859
896
|
const where = buildWhere(filter);
|
|
860
|
-
const versionColumn = this._table.
|
|
897
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
861
898
|
const full = fillReplacePayload(data, replaceColumnsFor(this._table.fieldDescriptors, this.nativeDefaultFns()), versionColumn);
|
|
862
899
|
const { sql, params } = buildUpdate$1(this.resolveTableName(), full, where, 1, void 0, versionColumn, expectedVersion);
|
|
863
900
|
this._log(sql, params);
|
|
@@ -869,7 +906,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
869
906
|
}
|
|
870
907
|
async replaceMany(filter, data) {
|
|
871
908
|
const where = buildWhere(filter);
|
|
872
|
-
const versionColumn = this._table.
|
|
909
|
+
const versionColumn = this._table.versionColumnPhysical;
|
|
873
910
|
const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, void 0, void 0, versionColumn);
|
|
874
911
|
this._log(sql, params);
|
|
875
912
|
const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
|
|
@@ -997,7 +1034,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
997
1034
|
if (clauses.length === 0) return;
|
|
998
1035
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ${clauses.join(", ")}`;
|
|
999
1036
|
this._log(ddl);
|
|
1000
|
-
await this.
|
|
1037
|
+
await this._withStrictSession((conn) => conn.exec(ddl));
|
|
1001
1038
|
}
|
|
1002
1039
|
async _ensureView() {
|
|
1003
1040
|
const view = this._table;
|
|
@@ -1009,131 +1046,130 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
1009
1046
|
return this.getExistingColumnsForTable(this._table.tableName);
|
|
1010
1047
|
}
|
|
1011
1048
|
async getExistingColumnsForTable(tableName) {
|
|
1012
|
-
return (
|
|
1049
|
+
return this._readColumns(this._exec(), tableName);
|
|
1050
|
+
}
|
|
1051
|
+
/** Live columns of `tableName`, read through `exec` (a held connection, or the pool). */
|
|
1052
|
+
async _readColumns(exec, tableName) {
|
|
1053
|
+
return (await exec.all(`SELECT c.COLUMN_NAME, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, c.SRS_ID, c.EXTRA,
|
|
1013
1054
|
(k.COLUMN_NAME IS NOT NULL) AS IS_PK
|
|
1014
1055
|
FROM INFORMATION_SCHEMA.COLUMNS c
|
|
1015
1056
|
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
|
|
1016
1057
|
ON k.TABLE_SCHEMA = c.TABLE_SCHEMA AND k.TABLE_NAME = c.TABLE_NAME
|
|
1017
1058
|
AND k.COLUMN_NAME = c.COLUMN_NAME AND k.CONSTRAINT_NAME = 'PRIMARY'
|
|
1018
1059
|
WHERE c.TABLE_NAME = ? AND c.TABLE_SCHEMA = COALESCE(?, DATABASE())
|
|
1019
|
-
ORDER BY c.ORDINAL_POSITION`, [tableName, this._schema])).map((r) =>
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1060
|
+
ORDER BY c.ORDINAL_POSITION`, [tableName, this._schema])).map((r) => {
|
|
1061
|
+
const column = {
|
|
1062
|
+
name: r.COLUMN_NAME,
|
|
1063
|
+
type: r.SRS_ID == null ? r.COLUMN_TYPE.toUpperCase() : `${r.COLUMN_TYPE.toUpperCase()} SRID ${r.SRS_ID}`,
|
|
1064
|
+
notnull: r.IS_NULLABLE === "NO",
|
|
1065
|
+
pk: Boolean(Number(r.IS_PK ?? 0)),
|
|
1066
|
+
dflt_value: normalizeMysqlDefault(r.COLUMN_DEFAULT)
|
|
1067
|
+
};
|
|
1068
|
+
if (/\b(VIRTUAL|STORED) GENERATED\b/i.test(r.EXTRA ?? "")) column.generated = true;
|
|
1069
|
+
return column;
|
|
1070
|
+
});
|
|
1026
1071
|
}
|
|
1027
1072
|
async syncColumns(diff) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
const quotedTable = quoteTableName(tableName);
|
|
1038
|
-
for (const field of diff.added) {
|
|
1039
|
-
const { def, inventedDefault } = buildColumnDefinition(field, this._columnCtx("add"));
|
|
1040
|
-
const ddl = `ALTER TABLE ${quotedTable} ADD COLUMN ${def}`;
|
|
1041
|
-
this._log(ddl);
|
|
1042
|
-
await this._exec().exec(ddl);
|
|
1043
|
-
if (inventedDefault) {
|
|
1044
|
-
const drop = `ALTER TABLE ${quotedTable} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
|
|
1045
|
-
this._log(drop);
|
|
1046
|
-
await this._exec().exec(drop);
|
|
1073
|
+
return this._withStrictSession(async (conn) => {
|
|
1074
|
+
const tableName = this.resolveTableName();
|
|
1075
|
+
const added = [];
|
|
1076
|
+
const renamed = [];
|
|
1077
|
+
for (const { field, oldName } of diff.renamed ?? []) {
|
|
1078
|
+
const ddl = `ALTER TABLE ${quoteTableName(tableName)} RENAME COLUMN ${qi(oldName)} TO ${qi(field.physicalName)}`;
|
|
1079
|
+
this._log(ddl);
|
|
1080
|
+
await conn.exec(ddl);
|
|
1081
|
+
renamed.push(field.physicalName);
|
|
1047
1082
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1083
|
+
const quotedTable = quoteTableName(tableName);
|
|
1084
|
+
for (const field of diff.added) {
|
|
1085
|
+
const { def, inventedDefault } = buildColumnDefinition(field, this._columnCtx("add"));
|
|
1086
|
+
const ddl = `ALTER TABLE ${quotedTable} ADD COLUMN ${def}`;
|
|
1087
|
+
this._log(ddl);
|
|
1088
|
+
await conn.exec(ddl);
|
|
1089
|
+
if (inventedDefault) {
|
|
1090
|
+
const drop = `ALTER TABLE ${quotedTable} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
|
|
1091
|
+
this._log(drop);
|
|
1092
|
+
await conn.exec(drop);
|
|
1093
|
+
}
|
|
1094
|
+
added.push(field.physicalName);
|
|
1058
1095
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if (!field.optional) {
|
|
1096
|
+
const enteringKey = new Set(diff.primaryKeyChanged?.to ?? []);
|
|
1097
|
+
const modified = /* @__PURE__ */ new Map();
|
|
1098
|
+
for (const { field } of diff.typeChanged ?? []) {
|
|
1099
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
1064
1100
|
const sqlType = this.typeMapper(field);
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1101
|
+
if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
|
|
1102
|
+
await this._migrateJsonColumnToPoint(conn, tableName, field);
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
modified.set(field.physicalName, field);
|
|
1069
1106
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1107
|
+
for (const { field } of diff.nullableChanged ?? []) {
|
|
1108
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
1109
|
+
if (!field.optional) {
|
|
1110
|
+
const sqlType = this.typeMapper(field);
|
|
1111
|
+
const fallback = field.defaultValue?.kind === "value" ? mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value) : mysqlTypeDefault(sqlType, field);
|
|
1112
|
+
const backfill = `UPDATE ${quotedTable} SET ${qi(field.physicalName)} = ${fallback} WHERE ${qi(field.physicalName)} IS NULL`;
|
|
1113
|
+
this._log(backfill);
|
|
1114
|
+
await conn.exec(backfill);
|
|
1115
|
+
}
|
|
1116
|
+
modified.set(field.physicalName, field);
|
|
1117
|
+
}
|
|
1118
|
+
for (const { field } of diff.defaultChanged ?? []) if (!enteringKey.has(field.physicalName)) modified.set(field.physicalName, field);
|
|
1119
|
+
for (const field of modified.values()) {
|
|
1120
|
+
const ddl = `ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`;
|
|
1121
|
+
this._log(ddl);
|
|
1122
|
+
await conn.exec(ddl);
|
|
1123
|
+
}
|
|
1124
|
+
return {
|
|
1125
|
+
added,
|
|
1126
|
+
renamed
|
|
1127
|
+
};
|
|
1128
|
+
});
|
|
1082
1129
|
}
|
|
1083
1130
|
async recreateTable() {
|
|
1084
1131
|
const tableName = this.resolveTableName();
|
|
1085
1132
|
const tempName = `${this._table.tableName}__tmp_${Date.now()}`;
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
return
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
await conn.exec(
|
|
1133
|
+
return this._withStrictSession(async (conn) => {
|
|
1134
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
|
|
1135
|
+
let originalDropped = false;
|
|
1136
|
+
try {
|
|
1137
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions());
|
|
1138
|
+
this._log(createSql);
|
|
1139
|
+
await conn.exec(createSql);
|
|
1140
|
+
const oldCols = (await this._readColumns(conn, this._table.tableName)).map((c) => c.name);
|
|
1141
|
+
const newCols = this._table.storedDescriptors.map((f) => f.physicalName);
|
|
1142
|
+
const oldColSet = new Set(oldCols);
|
|
1143
|
+
const commonCols = newCols.filter((c) => oldColSet.has(c));
|
|
1144
|
+
if (commonCols.length > 0) {
|
|
1145
|
+
const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
1146
|
+
const colNames = commonCols.map((c) => qi(c)).join(", ");
|
|
1147
|
+
const selectExprs = commonCols.map((c) => {
|
|
1148
|
+
const field = fieldsByName.get(c);
|
|
1149
|
+
if (field && !field.optional && !field.isPrimaryKey) {
|
|
1150
|
+
const fallback = field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral(field.designType, field.defaultValue.value) : defaultValueForType(field.designType);
|
|
1151
|
+
return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
|
|
1152
|
+
}
|
|
1153
|
+
return qi(c);
|
|
1154
|
+
}).join(", ");
|
|
1155
|
+
const copySql = `INSERT INTO ${qi(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
|
|
1156
|
+
this._log(copySql);
|
|
1157
|
+
await conn.exec(copySql);
|
|
1158
|
+
}
|
|
1159
|
+
await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)}`);
|
|
1160
|
+
originalDropped = true;
|
|
1161
|
+
await conn.exec(`RENAME TABLE ${qi(tempName)} TO ${quoteTableName(tableName)}`);
|
|
1162
|
+
} catch (error) {
|
|
1163
|
+
if (originalDropped) throw new Error(`Recreate of "${tableName}" failed after the original table was dropped; its rows are in "${tempName}"`, { cause: error });
|
|
1164
|
+
await conn.exec(`DROP TABLE IF EXISTS ${qi(tempName)}`).catch(() => void 0);
|
|
1165
|
+
throw error;
|
|
1166
|
+
} finally {
|
|
1167
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
1113
1168
|
}
|
|
1114
|
-
|
|
1115
|
-
originalDropped = true;
|
|
1116
|
-
await conn.exec(`RENAME TABLE ${qi(tempName)} TO ${quoteTableName(tableName)}`);
|
|
1117
|
-
} catch (error) {
|
|
1118
|
-
if (originalDropped) throw new Error(`Recreate of "${tableName}" failed after the original table was dropped; its rows are in "${tempName}"`, { cause: error });
|
|
1119
|
-
await conn.exec(`DROP TABLE IF EXISTS ${qi(tempName)}`).catch(() => void 0);
|
|
1120
|
-
throw error;
|
|
1121
|
-
} finally {
|
|
1122
|
-
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
1123
|
-
if (dedicated) conn.release();
|
|
1124
|
-
}
|
|
1169
|
+
});
|
|
1125
1170
|
}
|
|
1126
1171
|
async dropTable() {
|
|
1127
|
-
|
|
1128
|
-
this._log(ddl);
|
|
1129
|
-
const conn = await this.driver.getConnection();
|
|
1130
|
-
await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
|
|
1131
|
-
try {
|
|
1132
|
-
await conn.exec(ddl);
|
|
1133
|
-
} finally {
|
|
1134
|
-
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
1135
|
-
conn.release();
|
|
1136
|
-
}
|
|
1172
|
+
return this.dropTableByName(this.resolveTableName());
|
|
1137
1173
|
}
|
|
1138
1174
|
async dropColumns(columns) {
|
|
1139
1175
|
const tableName = this.resolveTableName();
|
|
@@ -1521,7 +1557,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
1521
1557
|
* Migrates a v1 JSON `[lng, lat]` column to native `POINT SRID 4326` via a
|
|
1522
1558
|
* temp column (MySQL has no JSON→geometry cast for MODIFY COLUMN).
|
|
1523
1559
|
*/
|
|
1524
|
-
async _migrateJsonColumnToPoint(tableName, field) {
|
|
1560
|
+
async _migrateJsonColumnToPoint(conn, tableName, field) {
|
|
1525
1561
|
const col = qi(field.physicalName);
|
|
1526
1562
|
const tmp = qi(`${field.physicalName}__geo_mig`);
|
|
1527
1563
|
const quotedTable = quoteTableName(tableName);
|
|
@@ -1534,7 +1570,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
1534
1570
|
];
|
|
1535
1571
|
for (const ddl of steps) {
|
|
1536
1572
|
this._log(ddl);
|
|
1537
|
-
await
|
|
1573
|
+
await conn.exec(ddl);
|
|
1538
1574
|
}
|
|
1539
1575
|
}
|
|
1540
1576
|
};
|
package/dist/plugin.cjs
CHANGED
|
@@ -14,6 +14,7 @@ const annotations = {
|
|
|
14
14
|
engine: new _atscript_core.AnnotationSpec({
|
|
15
15
|
description: "Specifies the MySQL storage engine.\n\n**Default:** `\"InnoDB\"`\n\n```atscript\n@db.mysql.engine \"MyISAM\"\nexport interface Logs { ... }\n```",
|
|
16
16
|
nodeType: ["interface"],
|
|
17
|
+
passedWhenReferred: false,
|
|
17
18
|
multiple: false,
|
|
18
19
|
argument: {
|
|
19
20
|
name: "engine",
|
package/dist/plugin.mjs
CHANGED
|
@@ -13,6 +13,7 @@ const annotations = {
|
|
|
13
13
|
engine: new AnnotationSpec({
|
|
14
14
|
description: "Specifies the MySQL storage engine.\n\n**Default:** `\"InnoDB\"`\n\n```atscript\n@db.mysql.engine \"MyISAM\"\nexport interface Logs { ... }\n```",
|
|
15
15
|
nodeType: ["interface"],
|
|
16
|
+
passedWhenReferred: false,
|
|
16
17
|
multiple: false,
|
|
17
18
|
argument: {
|
|
18
19
|
name: "engine",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-mysql",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.141",
|
|
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.
|
|
44
|
+
"@atscript/core": "^0.1.95",
|
|
45
|
+
"@atscript/typescript": "^0.1.95",
|
|
46
46
|
"@uniqu/core": "^0.1.11",
|
|
47
47
|
"mysql2": "^3.11.0",
|
|
48
|
-
"unplugin-atscript": "^0.1.
|
|
48
|
+
"unplugin-atscript": "^0.1.95"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"@atscript/core": "^0.1.
|
|
52
|
-
"@atscript/typescript": "^0.1.
|
|
51
|
+
"@atscript/core": "^0.1.95",
|
|
52
|
+
"@atscript/typescript": "^0.1.95",
|
|
53
53
|
"@uniqu/core": "^0.1.11",
|
|
54
54
|
"mysql2": ">=3.0.0",
|
|
55
|
-
"@atscript/db": "^0.1.
|
|
56
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
55
|
+
"@atscript/db": "^0.1.141",
|
|
56
|
+
"@atscript/db-sql-tools": "^0.1.141"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"mysql2": {
|