@atscript/db-mysql 0.1.139 → 0.1.140
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 +119 -102
- package/dist/index.d.cts +11 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.mjs +119 -102
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -578,6 +578,28 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
578
578
|
_exec() {
|
|
579
579
|
return this._txConnection() ?? this.driver;
|
|
580
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Runs `fn` on one connection (the transaction's, or a dedicated one) with
|
|
583
|
+
* `STRICT_ALL_TABLES` added to the session `sql_mode`, restoring the
|
|
584
|
+
* previous mode after. Schema sync's converting statements (`MODIFY
|
|
585
|
+
* COLUMN`, the recreate copy) then fail on a value that does not convert
|
|
586
|
+
* instead of coercing or truncating it — a non-strict server (RDS defaults
|
|
587
|
+
* to `NO_ENGINE_SUBSTITUTION`) would turn `'abc'` into `0` silently.
|
|
588
|
+
*/
|
|
589
|
+
async _withStrictSession(fn) {
|
|
590
|
+
const tx = this._txConnection();
|
|
591
|
+
const conn = tx ?? await this.driver.getConnection();
|
|
592
|
+
try {
|
|
593
|
+
await conn.exec("SET @atscript_sql_mode = @@SESSION.sql_mode, SESSION sql_mode = CONCAT_WS(',', @@SESSION.sql_mode, 'STRICT_ALL_TABLES')");
|
|
594
|
+
try {
|
|
595
|
+
return await fn(conn);
|
|
596
|
+
} finally {
|
|
597
|
+
await conn.exec("SET SESSION sql_mode = @atscript_sql_mode");
|
|
598
|
+
}
|
|
599
|
+
} finally {
|
|
600
|
+
if (!tx) conn.release();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
581
603
|
/** MySQL InnoDB enforces FK constraints natively. */
|
|
582
604
|
supportsNativeForeignKeys() {
|
|
583
605
|
return true;
|
|
@@ -998,7 +1020,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
998
1020
|
if (clauses.length === 0) return;
|
|
999
1021
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ${clauses.join(", ")}`;
|
|
1000
1022
|
this._log(ddl);
|
|
1001
|
-
await this.
|
|
1023
|
+
await this._withStrictSession((conn) => conn.exec(ddl));
|
|
1002
1024
|
}
|
|
1003
1025
|
async _ensureView() {
|
|
1004
1026
|
const view = this._table;
|
|
@@ -1010,7 +1032,11 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1010
1032
|
return this.getExistingColumnsForTable(this._table.tableName);
|
|
1011
1033
|
}
|
|
1012
1034
|
async getExistingColumnsForTable(tableName) {
|
|
1013
|
-
return (
|
|
1035
|
+
return this._readColumns(this._exec(), tableName);
|
|
1036
|
+
}
|
|
1037
|
+
/** Live columns of `tableName`, read through `exec` (a held connection, or the pool). */
|
|
1038
|
+
async _readColumns(exec, tableName) {
|
|
1039
|
+
return (await exec.all(`SELECT c.COLUMN_NAME, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, c.SRS_ID,
|
|
1014
1040
|
(k.COLUMN_NAME IS NOT NULL) AS IS_PK
|
|
1015
1041
|
FROM INFORMATION_SCHEMA.COLUMNS c
|
|
1016
1042
|
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
|
|
@@ -1026,115 +1052,106 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1026
1052
|
}));
|
|
1027
1053
|
}
|
|
1028
1054
|
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);
|
|
1055
|
+
return this._withStrictSession(async (conn) => {
|
|
1056
|
+
const tableName = this.resolveTableName();
|
|
1057
|
+
const added = [];
|
|
1058
|
+
const renamed = [];
|
|
1059
|
+
for (const { field, oldName } of diff.renamed ?? []) {
|
|
1060
|
+
const ddl = `ALTER TABLE ${quoteTableName(tableName)} RENAME COLUMN ${qi(oldName)} TO ${qi(field.physicalName)}`;
|
|
1061
|
+
this._log(ddl);
|
|
1062
|
+
await conn.exec(ddl);
|
|
1063
|
+
renamed.push(field.physicalName);
|
|
1048
1064
|
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1065
|
+
const quotedTable = quoteTableName(tableName);
|
|
1066
|
+
for (const field of diff.added) {
|
|
1067
|
+
const { def, inventedDefault } = buildColumnDefinition(field, this._columnCtx("add"));
|
|
1068
|
+
const ddl = `ALTER TABLE ${quotedTable} ADD COLUMN ${def}`;
|
|
1069
|
+
this._log(ddl);
|
|
1070
|
+
await conn.exec(ddl);
|
|
1071
|
+
if (inventedDefault) {
|
|
1072
|
+
const drop = `ALTER TABLE ${quotedTable} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
|
|
1073
|
+
this._log(drop);
|
|
1074
|
+
await conn.exec(drop);
|
|
1075
|
+
}
|
|
1076
|
+
added.push(field.physicalName);
|
|
1059
1077
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
if (!field.optional) {
|
|
1078
|
+
const enteringKey = new Set(diff.primaryKeyChanged?.to ?? []);
|
|
1079
|
+
const modified = /* @__PURE__ */ new Map();
|
|
1080
|
+
for (const { field } of diff.typeChanged ?? []) {
|
|
1081
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
1065
1082
|
const sqlType = this.typeMapper(field);
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1083
|
+
if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
|
|
1084
|
+
await this._migrateJsonColumnToPoint(conn, tableName, field);
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
modified.set(field.physicalName, field);
|
|
1070
1088
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1089
|
+
for (const { field } of diff.nullableChanged ?? []) {
|
|
1090
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
1091
|
+
if (!field.optional) {
|
|
1092
|
+
const sqlType = this.typeMapper(field);
|
|
1093
|
+
const fallback = field.defaultValue?.kind === "value" ? mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value) : mysqlTypeDefault(sqlType, field);
|
|
1094
|
+
const backfill = `UPDATE ${quotedTable} SET ${qi(field.physicalName)} = ${fallback} WHERE ${qi(field.physicalName)} IS NULL`;
|
|
1095
|
+
this._log(backfill);
|
|
1096
|
+
await conn.exec(backfill);
|
|
1097
|
+
}
|
|
1098
|
+
modified.set(field.physicalName, field);
|
|
1099
|
+
}
|
|
1100
|
+
for (const { field } of diff.defaultChanged ?? []) if (!enteringKey.has(field.physicalName)) modified.set(field.physicalName, field);
|
|
1101
|
+
for (const field of modified.values()) {
|
|
1102
|
+
const ddl = `ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`;
|
|
1103
|
+
this._log(ddl);
|
|
1104
|
+
await conn.exec(ddl);
|
|
1105
|
+
}
|
|
1106
|
+
return {
|
|
1107
|
+
added,
|
|
1108
|
+
renamed
|
|
1109
|
+
};
|
|
1110
|
+
});
|
|
1083
1111
|
}
|
|
1084
1112
|
async recreateTable() {
|
|
1085
1113
|
const tableName = this.resolveTableName();
|
|
1086
1114
|
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(
|
|
1115
|
+
return this._withStrictSession(async (conn) => {
|
|
1116
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
|
|
1117
|
+
let originalDropped = false;
|
|
1118
|
+
try {
|
|
1119
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions());
|
|
1120
|
+
this._log(createSql);
|
|
1121
|
+
await conn.exec(createSql);
|
|
1122
|
+
const oldCols = (await this._readColumns(conn, this._table.tableName)).map((c) => c.name);
|
|
1123
|
+
const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
|
|
1124
|
+
const oldColSet = new Set(oldCols);
|
|
1125
|
+
const commonCols = newCols.filter((c) => oldColSet.has(c));
|
|
1126
|
+
if (commonCols.length > 0) {
|
|
1127
|
+
const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
1128
|
+
const colNames = commonCols.map((c) => qi(c)).join(", ");
|
|
1129
|
+
const selectExprs = commonCols.map((c) => {
|
|
1130
|
+
const field = fieldsByName.get(c);
|
|
1131
|
+
if (field && !field.optional && !field.isPrimaryKey) {
|
|
1132
|
+
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);
|
|
1133
|
+
return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
|
|
1134
|
+
}
|
|
1135
|
+
return qi(c);
|
|
1136
|
+
}).join(", ");
|
|
1137
|
+
const copySql = `INSERT INTO ${qi(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
|
|
1138
|
+
this._log(copySql);
|
|
1139
|
+
await conn.exec(copySql);
|
|
1140
|
+
}
|
|
1141
|
+
await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)}`);
|
|
1142
|
+
originalDropped = true;
|
|
1143
|
+
await conn.exec(`RENAME TABLE ${qi(tempName)} TO ${quoteTableName(tableName)}`);
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
if (originalDropped) throw new Error(`Recreate of "${tableName}" failed after the original table was dropped; its rows are in "${tempName}"`, { cause: error });
|
|
1146
|
+
await conn.exec(`DROP TABLE IF EXISTS ${qi(tempName)}`).catch(() => void 0);
|
|
1147
|
+
throw error;
|
|
1148
|
+
} finally {
|
|
1149
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
1114
1150
|
}
|
|
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
|
-
}
|
|
1151
|
+
});
|
|
1126
1152
|
}
|
|
1127
1153
|
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
|
-
}
|
|
1154
|
+
return this.dropTableByName(this.resolveTableName());
|
|
1138
1155
|
}
|
|
1139
1156
|
async dropColumns(columns) {
|
|
1140
1157
|
const tableName = this.resolveTableName();
|
|
@@ -1522,7 +1539,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1522
1539
|
* Migrates a v1 JSON `[lng, lat]` column to native `POINT SRID 4326` via a
|
|
1523
1540
|
* temp column (MySQL has no JSON→geometry cast for MODIFY COLUMN).
|
|
1524
1541
|
*/
|
|
1525
|
-
async _migrateJsonColumnToPoint(tableName, field) {
|
|
1542
|
+
async _migrateJsonColumnToPoint(conn, tableName, field) {
|
|
1526
1543
|
const col = qi(field.physicalName);
|
|
1527
1544
|
const tmp = qi(`${field.physicalName}__geo_mig`);
|
|
1528
1545
|
const quotedTable = quoteTableName(tableName);
|
|
@@ -1535,7 +1552,7 @@ var MysqlAdapter = class MysqlAdapter extends _atscript_db.BaseDbAdapter {
|
|
|
1535
1552
|
];
|
|
1536
1553
|
for (const ddl of steps) {
|
|
1537
1554
|
this._log(ddl);
|
|
1538
|
-
await
|
|
1555
|
+
await conn.exec(ddl);
|
|
1539
1556
|
}
|
|
1540
1557
|
}
|
|
1541
1558
|
};
|
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
|
@@ -577,6 +577,28 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
577
577
|
_exec() {
|
|
578
578
|
return this._txConnection() ?? this.driver;
|
|
579
579
|
}
|
|
580
|
+
/**
|
|
581
|
+
* Runs `fn` on one connection (the transaction's, or a dedicated one) with
|
|
582
|
+
* `STRICT_ALL_TABLES` added to the session `sql_mode`, restoring the
|
|
583
|
+
* previous mode after. Schema sync's converting statements (`MODIFY
|
|
584
|
+
* COLUMN`, the recreate copy) then fail on a value that does not convert
|
|
585
|
+
* instead of coercing or truncating it — a non-strict server (RDS defaults
|
|
586
|
+
* to `NO_ENGINE_SUBSTITUTION`) would turn `'abc'` into `0` silently.
|
|
587
|
+
*/
|
|
588
|
+
async _withStrictSession(fn) {
|
|
589
|
+
const tx = this._txConnection();
|
|
590
|
+
const conn = tx ?? await this.driver.getConnection();
|
|
591
|
+
try {
|
|
592
|
+
await conn.exec("SET @atscript_sql_mode = @@SESSION.sql_mode, SESSION sql_mode = CONCAT_WS(',', @@SESSION.sql_mode, 'STRICT_ALL_TABLES')");
|
|
593
|
+
try {
|
|
594
|
+
return await fn(conn);
|
|
595
|
+
} finally {
|
|
596
|
+
await conn.exec("SET SESSION sql_mode = @atscript_sql_mode");
|
|
597
|
+
}
|
|
598
|
+
} finally {
|
|
599
|
+
if (!tx) conn.release();
|
|
600
|
+
}
|
|
601
|
+
}
|
|
580
602
|
/** MySQL InnoDB enforces FK constraints natively. */
|
|
581
603
|
supportsNativeForeignKeys() {
|
|
582
604
|
return true;
|
|
@@ -997,7 +1019,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
997
1019
|
if (clauses.length === 0) return;
|
|
998
1020
|
const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} ${clauses.join(", ")}`;
|
|
999
1021
|
this._log(ddl);
|
|
1000
|
-
await this.
|
|
1022
|
+
await this._withStrictSession((conn) => conn.exec(ddl));
|
|
1001
1023
|
}
|
|
1002
1024
|
async _ensureView() {
|
|
1003
1025
|
const view = this._table;
|
|
@@ -1009,7 +1031,11 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
1009
1031
|
return this.getExistingColumnsForTable(this._table.tableName);
|
|
1010
1032
|
}
|
|
1011
1033
|
async getExistingColumnsForTable(tableName) {
|
|
1012
|
-
return (
|
|
1034
|
+
return this._readColumns(this._exec(), tableName);
|
|
1035
|
+
}
|
|
1036
|
+
/** Live columns of `tableName`, read through `exec` (a held connection, or the pool). */
|
|
1037
|
+
async _readColumns(exec, tableName) {
|
|
1038
|
+
return (await exec.all(`SELECT c.COLUMN_NAME, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, c.SRS_ID,
|
|
1013
1039
|
(k.COLUMN_NAME IS NOT NULL) AS IS_PK
|
|
1014
1040
|
FROM INFORMATION_SCHEMA.COLUMNS c
|
|
1015
1041
|
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
|
|
@@ -1025,115 +1051,106 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
1025
1051
|
}));
|
|
1026
1052
|
}
|
|
1027
1053
|
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);
|
|
1054
|
+
return this._withStrictSession(async (conn) => {
|
|
1055
|
+
const tableName = this.resolveTableName();
|
|
1056
|
+
const added = [];
|
|
1057
|
+
const renamed = [];
|
|
1058
|
+
for (const { field, oldName } of diff.renamed ?? []) {
|
|
1059
|
+
const ddl = `ALTER TABLE ${quoteTableName(tableName)} RENAME COLUMN ${qi(oldName)} TO ${qi(field.physicalName)}`;
|
|
1060
|
+
this._log(ddl);
|
|
1061
|
+
await conn.exec(ddl);
|
|
1062
|
+
renamed.push(field.physicalName);
|
|
1047
1063
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1064
|
+
const quotedTable = quoteTableName(tableName);
|
|
1065
|
+
for (const field of diff.added) {
|
|
1066
|
+
const { def, inventedDefault } = buildColumnDefinition(field, this._columnCtx("add"));
|
|
1067
|
+
const ddl = `ALTER TABLE ${quotedTable} ADD COLUMN ${def}`;
|
|
1068
|
+
this._log(ddl);
|
|
1069
|
+
await conn.exec(ddl);
|
|
1070
|
+
if (inventedDefault) {
|
|
1071
|
+
const drop = `ALTER TABLE ${quotedTable} ALTER COLUMN ${qi(field.physicalName)} DROP DEFAULT`;
|
|
1072
|
+
this._log(drop);
|
|
1073
|
+
await conn.exec(drop);
|
|
1074
|
+
}
|
|
1075
|
+
added.push(field.physicalName);
|
|
1058
1076
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if (!field.optional) {
|
|
1077
|
+
const enteringKey = new Set(diff.primaryKeyChanged?.to ?? []);
|
|
1078
|
+
const modified = /* @__PURE__ */ new Map();
|
|
1079
|
+
for (const { field } of diff.typeChanged ?? []) {
|
|
1080
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
1064
1081
|
const sqlType = this.typeMapper(field);
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1082
|
+
if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
|
|
1083
|
+
await this._migrateJsonColumnToPoint(conn, tableName, field);
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
modified.set(field.physicalName, field);
|
|
1069
1087
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1088
|
+
for (const { field } of diff.nullableChanged ?? []) {
|
|
1089
|
+
if (enteringKey.has(field.physicalName)) continue;
|
|
1090
|
+
if (!field.optional) {
|
|
1091
|
+
const sqlType = this.typeMapper(field);
|
|
1092
|
+
const fallback = field.defaultValue?.kind === "value" ? mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value) : mysqlTypeDefault(sqlType, field);
|
|
1093
|
+
const backfill = `UPDATE ${quotedTable} SET ${qi(field.physicalName)} = ${fallback} WHERE ${qi(field.physicalName)} IS NULL`;
|
|
1094
|
+
this._log(backfill);
|
|
1095
|
+
await conn.exec(backfill);
|
|
1096
|
+
}
|
|
1097
|
+
modified.set(field.physicalName, field);
|
|
1098
|
+
}
|
|
1099
|
+
for (const { field } of diff.defaultChanged ?? []) if (!enteringKey.has(field.physicalName)) modified.set(field.physicalName, field);
|
|
1100
|
+
for (const field of modified.values()) {
|
|
1101
|
+
const ddl = `ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`;
|
|
1102
|
+
this._log(ddl);
|
|
1103
|
+
await conn.exec(ddl);
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
added,
|
|
1107
|
+
renamed
|
|
1108
|
+
};
|
|
1109
|
+
});
|
|
1082
1110
|
}
|
|
1083
1111
|
async recreateTable() {
|
|
1084
1112
|
const tableName = this.resolveTableName();
|
|
1085
1113
|
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(
|
|
1114
|
+
return this._withStrictSession(async (conn) => {
|
|
1115
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
|
|
1116
|
+
let originalDropped = false;
|
|
1117
|
+
try {
|
|
1118
|
+
const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions());
|
|
1119
|
+
this._log(createSql);
|
|
1120
|
+
await conn.exec(createSql);
|
|
1121
|
+
const oldCols = (await this._readColumns(conn, this._table.tableName)).map((c) => c.name);
|
|
1122
|
+
const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
|
|
1123
|
+
const oldColSet = new Set(oldCols);
|
|
1124
|
+
const commonCols = newCols.filter((c) => oldColSet.has(c));
|
|
1125
|
+
if (commonCols.length > 0) {
|
|
1126
|
+
const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
|
|
1127
|
+
const colNames = commonCols.map((c) => qi(c)).join(", ");
|
|
1128
|
+
const selectExprs = commonCols.map((c) => {
|
|
1129
|
+
const field = fieldsByName.get(c);
|
|
1130
|
+
if (field && !field.optional && !field.isPrimaryKey) {
|
|
1131
|
+
const fallback = field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral(field.designType, field.defaultValue.value) : defaultValueForType(field.designType);
|
|
1132
|
+
return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
|
|
1133
|
+
}
|
|
1134
|
+
return qi(c);
|
|
1135
|
+
}).join(", ");
|
|
1136
|
+
const copySql = `INSERT INTO ${qi(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
|
|
1137
|
+
this._log(copySql);
|
|
1138
|
+
await conn.exec(copySql);
|
|
1139
|
+
}
|
|
1140
|
+
await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)}`);
|
|
1141
|
+
originalDropped = true;
|
|
1142
|
+
await conn.exec(`RENAME TABLE ${qi(tempName)} TO ${quoteTableName(tableName)}`);
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
if (originalDropped) throw new Error(`Recreate of "${tableName}" failed after the original table was dropped; its rows are in "${tempName}"`, { cause: error });
|
|
1145
|
+
await conn.exec(`DROP TABLE IF EXISTS ${qi(tempName)}`).catch(() => void 0);
|
|
1146
|
+
throw error;
|
|
1147
|
+
} finally {
|
|
1148
|
+
await conn.exec("SET FOREIGN_KEY_CHECKS = 1");
|
|
1113
1149
|
}
|
|
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
|
-
}
|
|
1150
|
+
});
|
|
1125
1151
|
}
|
|
1126
1152
|
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
|
-
}
|
|
1153
|
+
return this.dropTableByName(this.resolveTableName());
|
|
1137
1154
|
}
|
|
1138
1155
|
async dropColumns(columns) {
|
|
1139
1156
|
const tableName = this.resolveTableName();
|
|
@@ -1521,7 +1538,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
1521
1538
|
* Migrates a v1 JSON `[lng, lat]` column to native `POINT SRID 4326` via a
|
|
1522
1539
|
* temp column (MySQL has no JSON→geometry cast for MODIFY COLUMN).
|
|
1523
1540
|
*/
|
|
1524
|
-
async _migrateJsonColumnToPoint(tableName, field) {
|
|
1541
|
+
async _migrateJsonColumnToPoint(conn, tableName, field) {
|
|
1525
1542
|
const col = qi(field.physicalName);
|
|
1526
1543
|
const tmp = qi(`${field.physicalName}__geo_mig`);
|
|
1527
1544
|
const quotedTable = quoteTableName(tableName);
|
|
@@ -1534,7 +1551,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
|
|
|
1534
1551
|
];
|
|
1535
1552
|
for (const ddl of steps) {
|
|
1536
1553
|
this._log(ddl);
|
|
1537
|
-
await
|
|
1554
|
+
await conn.exec(ddl);
|
|
1538
1555
|
}
|
|
1539
1556
|
}
|
|
1540
1557
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/db-mysql",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.140",
|
|
4
4
|
"description": "MySQL adapter for @atscript/db with mysql2 driver support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"@atscript/typescript": "^0.1.92",
|
|
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.140",
|
|
56
|
+
"@atscript/db-sql-tools": "^0.1.140"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"mysql2": {
|