@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 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._exec().exec(ddl);
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 (await this._exec().all(`SELECT c.COLUMN_NAME, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, c.SRS_ID,
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
- const tableName = this.resolveTableName();
1030
- const added = [];
1031
- const renamed = [];
1032
- for (const { field, oldName } of diff.renamed ?? []) {
1033
- const ddl = `ALTER TABLE ${quoteTableName(tableName)} RENAME COLUMN ${qi(oldName)} TO ${qi(field.physicalName)}`;
1034
- this._log(ddl);
1035
- await this._exec().exec(ddl);
1036
- renamed.push(field.physicalName);
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
- added.push(field.physicalName);
1050
- }
1051
- const enteringKey = new Set(diff.primaryKeyChanged?.to ?? []);
1052
- const modified = /* @__PURE__ */ new Map();
1053
- for (const { field } of diff.typeChanged ?? []) {
1054
- if (enteringKey.has(field.physicalName)) continue;
1055
- const sqlType = this.typeMapper(field);
1056
- if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
1057
- await this._migrateJsonColumnToPoint(tableName, field);
1058
- continue;
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
- modified.set(field.physicalName, field);
1061
- }
1062
- for (const { field } of diff.nullableChanged ?? []) {
1063
- if (enteringKey.has(field.physicalName)) continue;
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
- const fallback = field.defaultValue?.kind === "value" ? mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value) : mysqlTypeDefault(sqlType, field);
1067
- const backfill = `UPDATE ${quotedTable} SET ${qi(field.physicalName)} = ${fallback} WHERE ${qi(field.physicalName)} IS NULL`;
1068
- this._log(backfill);
1069
- await this._exec().exec(backfill);
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
- modified.set(field.physicalName, field);
1072
- }
1073
- for (const { field } of diff.defaultChanged ?? []) if (!enteringKey.has(field.physicalName)) modified.set(field.physicalName, field);
1074
- for (const field of modified.values()) {
1075
- const ddl = `ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`;
1076
- this._log(ddl);
1077
- await this._exec().exec(ddl);
1078
- }
1079
- return {
1080
- added,
1081
- renamed
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
- const tx = this._txConnection();
1088
- const conn = tx ?? await this.driver.getConnection();
1089
- const dedicated = tx === void 0;
1090
- await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
1091
- let originalDropped = false;
1092
- try {
1093
- const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions());
1094
- this._log(createSql);
1095
- await conn.exec(createSql);
1096
- const oldCols = (await this.getExistingColumns()).map((c) => c.name);
1097
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
1098
- const oldColSet = new Set(oldCols);
1099
- const commonCols = newCols.filter((c) => oldColSet.has(c));
1100
- if (commonCols.length > 0) {
1101
- const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
1102
- const colNames = commonCols.map((c) => qi(c)).join(", ");
1103
- const selectExprs = commonCols.map((c) => {
1104
- const field = fieldsByName.get(c);
1105
- if (field && !field.optional && !field.isPrimaryKey) {
1106
- 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);
1107
- return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
1108
- }
1109
- return qi(c);
1110
- }).join(", ");
1111
- const copySql = `INSERT INTO ${qi(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
1112
- this._log(copySql);
1113
- await conn.exec(copySql);
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
- await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)}`);
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
- const ddl = `DROP TABLE IF EXISTS ${quoteTableName(this.resolveTableName())}`;
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 this._exec().exec(ddl);
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._exec().exec(ddl);
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 (await this._exec().all(`SELECT c.COLUMN_NAME, c.COLUMN_TYPE, c.IS_NULLABLE, c.COLUMN_DEFAULT, c.SRS_ID,
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
- const tableName = this.resolveTableName();
1029
- const added = [];
1030
- const renamed = [];
1031
- for (const { field, oldName } of diff.renamed ?? []) {
1032
- const ddl = `ALTER TABLE ${quoteTableName(tableName)} RENAME COLUMN ${qi(oldName)} TO ${qi(field.physicalName)}`;
1033
- this._log(ddl);
1034
- await this._exec().exec(ddl);
1035
- renamed.push(field.physicalName);
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
- added.push(field.physicalName);
1049
- }
1050
- const enteringKey = new Set(diff.primaryKeyChanged?.to ?? []);
1051
- const modified = /* @__PURE__ */ new Map();
1052
- for (const { field } of diff.typeChanged ?? []) {
1053
- if (enteringKey.has(field.physicalName)) continue;
1054
- const sqlType = this.typeMapper(field);
1055
- if (field.isGeoPoint && !field.encrypted && sqlType.startsWith("POINT")) {
1056
- await this._migrateJsonColumnToPoint(tableName, field);
1057
- continue;
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
- modified.set(field.physicalName, field);
1060
- }
1061
- for (const { field } of diff.nullableChanged ?? []) {
1062
- if (enteringKey.has(field.physicalName)) continue;
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
- const fallback = field.defaultValue?.kind === "value" ? mysqlDefaultLiteral(sqlType, field.designType, field.defaultValue.value) : mysqlTypeDefault(sqlType, field);
1066
- const backfill = `UPDATE ${quotedTable} SET ${qi(field.physicalName)} = ${fallback} WHERE ${qi(field.physicalName)} IS NULL`;
1067
- this._log(backfill);
1068
- await this._exec().exec(backfill);
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
- modified.set(field.physicalName, field);
1071
- }
1072
- for (const { field } of diff.defaultChanged ?? []) if (!enteringKey.has(field.physicalName)) modified.set(field.physicalName, field);
1073
- for (const field of modified.values()) {
1074
- const ddl = `ALTER TABLE ${quotedTable} MODIFY COLUMN ${buildColumnDefinition(field, this._columnCtx("modify")).def}`;
1075
- this._log(ddl);
1076
- await this._exec().exec(ddl);
1077
- }
1078
- return {
1079
- added,
1080
- renamed
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
- const tx = this._txConnection();
1087
- const conn = tx ?? await this.driver.getConnection();
1088
- const dedicated = tx === void 0;
1089
- await conn.exec("SET FOREIGN_KEY_CHECKS = 0");
1090
- let originalDropped = false;
1091
- try {
1092
- const createSql = buildCreateTable(tempName, this._table.fieldDescriptors, this._table.foreignKeys, this._tableOptions());
1093
- this._log(createSql);
1094
- await conn.exec(createSql);
1095
- const oldCols = (await this.getExistingColumns()).map((c) => c.name);
1096
- const newCols = this._table.fieldDescriptors.filter((f) => !f.ignored).map((f) => f.physicalName);
1097
- const oldColSet = new Set(oldCols);
1098
- const commonCols = newCols.filter((c) => oldColSet.has(c));
1099
- if (commonCols.length > 0) {
1100
- const fieldsByName = new Map(this._table.fieldDescriptors.map((f) => [f.physicalName, f]));
1101
- const colNames = commonCols.map((c) => qi(c)).join(", ");
1102
- const selectExprs = commonCols.map((c) => {
1103
- const field = fieldsByName.get(c);
1104
- if (field && !field.optional && !field.isPrimaryKey) {
1105
- const fallback = field.defaultValue?.kind === "value" ? defaultValueToSqlLiteral(field.designType, field.defaultValue.value) : defaultValueForType(field.designType);
1106
- return `COALESCE(${qi(c)}, ${fallback}) AS ${qi(c)}`;
1107
- }
1108
- return qi(c);
1109
- }).join(", ");
1110
- const copySql = `INSERT INTO ${qi(tempName)} (${colNames}) SELECT ${selectExprs} FROM ${quoteTableName(tableName)}`;
1111
- this._log(copySql);
1112
- await conn.exec(copySql);
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
- await conn.exec(`DROP TABLE IF EXISTS ${quoteTableName(tableName)}`);
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
- const ddl = `DROP TABLE IF EXISTS ${quoteTableName(this.resolveTableName())}`;
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 this._exec().exec(ddl);
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.139",
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.139",
56
- "@atscript/db-sql-tools": "^0.1.139"
55
+ "@atscript/db": "^0.1.140",
56
+ "@atscript/db-sql-tools": "^0.1.140"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "mysql2": {