bun-query-builder 0.2.18 → 0.2.20

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.
@@ -18,6 +18,8 @@ export declare function sqlTypeToNormalized(rawType: string, dialect: SupportedD
18
18
  * details carried only by COLUMN_TYPE (for example TINYINT(1) and VARCHAR(n)).
19
19
  */
20
20
  export declare function mysqlColumnType(dataType: unknown, columnType: unknown, enumValues?: string[]): NormalizedColumnType;
21
+ /** Preserve the physical bound of a MySQL VARCHAR column for round-trip diffs. */
22
+ export declare function mysqlColumnMaxLength(columnType: unknown): number | undefined;
21
23
  /**
22
24
  * The plan type for one Postgres column, given what the catalogue knows.
23
25
  *
package/dist/bin/cli.js CHANGED
@@ -17740,6 +17740,8 @@ function sqlTypeToNormalized(rawType, dialect, ctx) {
17740
17740
  if (/^(?:varchar|character varying|nvarchar|nchar|char|character|bpchar)/.test(base)) {
17741
17741
  const m = t.match(/\((\d+)\)/);
17742
17742
  const len = m ? Number(m[1]) : undefined;
17743
+ if (isMysqlLike(dialect))
17744
+ return "string";
17743
17745
  return len !== undefined && len > 255 ? "text" : "string";
17744
17746
  }
17745
17747
  if (/text|clob/.test(base))
@@ -17767,6 +17769,13 @@ function sqlTypeToNormalized(rawType, dialect, ctx) {
17767
17769
  function mysqlColumnType(dataType, columnType, enumValues) {
17768
17770
  return sqlTypeToNormalized(String(columnType || dataType || ""), "mysql", { enumValues });
17769
17771
  }
17772
+ function mysqlColumnMaxLength(columnType) {
17773
+ const match = String(columnType || "").trim().match(/^varchar\((\d+)\)/i);
17774
+ if (!match)
17775
+ return;
17776
+ const length = Number(match[1]);
17777
+ return Number.isSafeInteger(length) && length > 0 ? length : undefined;
17778
+ }
17770
17779
  function asAction(raw2) {
17771
17780
  const v = String(raw2 ?? "").toLowerCase().trim();
17772
17781
  if (v === "cascade" || v === "restrict" || v === "set null")
@@ -17881,7 +17890,10 @@ async function buildPgTable(qb, table, enumLabels) {
17881
17890
  WHERE t.relname = $1 AND t.relkind = 'r'
17882
17891
  ORDER BY i.relname, ord`, [table]);
17883
17892
  const fkRows = await qb.unsafe(`SELECT kcu.column_name, ccu.table_name AS ref_table, ccu.column_name AS ref_column,
17884
- rc.delete_rule, rc.update_rule
17893
+ tc.constraint_name, rc.delete_rule, rc.update_rule,
17894
+ (SELECT COUNT(*) FROM information_schema.key_column_usage count_kcu
17895
+ WHERE count_kcu.constraint_name = tc.constraint_name
17896
+ AND count_kcu.constraint_schema = tc.constraint_schema) AS constraint_column_count
17885
17897
  FROM information_schema.table_constraints tc
17886
17898
  JOIN information_schema.key_column_usage kcu ON kcu.constraint_name = tc.constraint_name
17887
17899
  JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name
@@ -17889,11 +17901,15 @@ async function buildPgTable(qb, table, enumLabels) {
17889
17901
  WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1 AND tc.table_schema = 'public'`, [table]);
17890
17902
  const fkByColumn = new Map;
17891
17903
  for (const fk of fkRows) {
17892
- fkByColumn.set(String(fk.column_name), {
17904
+ const name = String(fk.column_name);
17905
+ const existing = fkByColumn.get(name);
17906
+ const constraintNames = Number(fk.constraint_column_count) === 1 ? [...new Set([...existing?.constraintNames ?? [], String(fk.constraint_name)])] : existing?.constraintNames;
17907
+ fkByColumn.set(name, existing ? { ...existing, constraintNames } : {
17893
17908
  table: String(fk.ref_table),
17894
17909
  column: String(fk.ref_column ?? "id"),
17895
17910
  onDelete: asAction(fk.delete_rule),
17896
- onUpdate: asAction(fk.update_rule)
17911
+ onUpdate: asAction(fk.update_rule),
17912
+ constraintNames
17897
17913
  });
17898
17914
  }
17899
17915
  const { indexes, uniqueColumns } = groupPhysicalIndexes(table, idxRows.map((r) => ({
@@ -17932,18 +17948,27 @@ async function buildMysqlTable(qb, table) {
17932
17948
  WHERE table_name = ? AND table_schema = DATABASE()
17933
17949
  ORDER BY index_name, seq_in_index`, [table]);
17934
17950
  const fkRows = await qb.unsafe(`SELECT kcu.column_name, kcu.referenced_table_name AS ref_table, kcu.referenced_column_name AS ref_column,
17935
- rc.delete_rule, rc.update_rule
17951
+ kcu.constraint_name, rc.delete_rule, rc.update_rule,
17952
+ (SELECT COUNT(*) FROM information_schema.key_column_usage count_kcu
17953
+ WHERE count_kcu.constraint_name = kcu.constraint_name
17954
+ AND count_kcu.constraint_schema = kcu.constraint_schema) AS constraint_column_count
17936
17955
  FROM information_schema.key_column_usage kcu
17937
17956
  JOIN information_schema.referential_constraints rc
17938
17957
  ON rc.constraint_name = kcu.constraint_name AND rc.constraint_schema = kcu.table_schema
17939
17958
  WHERE kcu.table_name = ? AND kcu.table_schema = DATABASE() AND kcu.referenced_table_name IS NOT NULL`, [table]);
17940
17959
  const fkByColumn = new Map;
17941
17960
  for (const fk of fkRows) {
17942
- fkByColumn.set(String(fk.column_name ?? fk.COLUMN_NAME), {
17961
+ const name = String(fk.column_name ?? fk.COLUMN_NAME);
17962
+ const existing = fkByColumn.get(name);
17963
+ const columnCount = Number(fk.constraint_column_count ?? fk.CONSTRAINT_COLUMN_COUNT);
17964
+ const constraintName = String(fk.constraint_name ?? fk.CONSTRAINT_NAME);
17965
+ const constraintNames = columnCount === 1 ? [...new Set([...existing?.constraintNames ?? [], constraintName])] : existing?.constraintNames;
17966
+ fkByColumn.set(name, existing ? { ...existing, constraintNames } : {
17943
17967
  table: String(fk.ref_table ?? fk.REF_TABLE),
17944
17968
  column: String(fk.ref_column ?? fk.REF_COLUMN ?? "id"),
17945
17969
  onDelete: asAction(fk.delete_rule ?? fk.DELETE_RULE),
17946
- onUpdate: asAction(fk.update_rule ?? fk.UPDATE_RULE)
17970
+ onUpdate: asAction(fk.update_rule ?? fk.UPDATE_RULE),
17971
+ constraintNames
17947
17972
  });
17948
17973
  }
17949
17974
  const { indexes, uniqueColumns } = groupPhysicalIndexes(table, idxRows.map((r) => ({
@@ -17969,6 +17994,7 @@ async function buildMysqlTable(qb, table) {
17969
17994
  hasDefault,
17970
17995
  defaultValue: hasDefault ? normalizeRawDefault(rawDefault) : undefined,
17971
17996
  enumValues,
17997
+ maxLength: mysqlColumnMaxLength(columnType),
17972
17998
  references: fkByColumn.get(name)
17973
17999
  };
17974
18000
  });
@@ -18203,49 +18229,17 @@ class MySQLDriver {
18203
18229
  const columns = index.columns.map((c) => this.quoteIdentifier(c)).join(", ");
18204
18230
  return `CREATE ${kind}INDEX ${this.quoteIdentifier(idxName)} ON ${this.quoteIdentifier(tableName)} (${columns});`;
18205
18231
  }
18206
- addForeignKey(tableName, columnName, refTable, refColumn, onDelete, onUpdate) {
18232
+ addForeignKey(tableName, columnName, refTable, refColumn, onDelete, onUpdate, existingConstraintNames = []) {
18207
18233
  const fkName = `${tableName}_${columnName}_fk`;
18208
18234
  let sql = `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD CONSTRAINT ${this.quoteIdentifier(fkName)} FOREIGN KEY (${this.quoteIdentifier(columnName)}) REFERENCES ${this.quoteIdentifier(refTable)}(${this.quoteIdentifier(refColumn)})`;
18209
18235
  if (onDelete)
18210
18236
  sql += ` ON DELETE ${onDelete.toUpperCase()}`;
18211
18237
  if (onUpdate)
18212
18238
  sql += ` ON UPDATE ${onUpdate.toUpperCase()}`;
18213
- return `${this.dropForeignKeysOn(tableName, columnName)}
18214
- ${sql};`;
18215
- }
18216
- dropForeignKeysOn(tableName, columnName) {
18217
- const table = this.quoteLiteral(tableName);
18218
- const column = this.quoteLiteral(columnName);
18219
- return `SET @drop_fks := (
18220
- SELECT CONCAT(
18221
- 'ALTER TABLE \`', ${table}, '\` DROP FOREIGN KEY \`',
18222
- GROUP_CONCAT(kcu.CONSTRAINT_NAME SEPARATOR '\`, DROP FOREIGN KEY \`'),
18223
- '\`'
18224
- )
18225
- FROM information_schema.KEY_COLUMN_USAGE kcu
18226
- WHERE kcu.TABLE_SCHEMA = DATABASE()
18227
- AND kcu.TABLE_NAME = ${table}
18228
- AND kcu.COLUMN_NAME = ${column}
18229
- AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
18230
- AND kcu.CONSTRAINT_NAME IN (
18231
- SELECT single.CONSTRAINT_NAME FROM (
18232
- SELECT inner_kcu.CONSTRAINT_NAME
18233
- FROM information_schema.KEY_COLUMN_USAGE inner_kcu
18234
- WHERE inner_kcu.TABLE_SCHEMA = DATABASE()
18235
- AND inner_kcu.TABLE_NAME = ${table}
18236
- AND inner_kcu.REFERENCED_TABLE_NAME IS NOT NULL
18237
- GROUP BY inner_kcu.CONSTRAINT_NAME
18238
- HAVING COUNT(*) = 1
18239
- ) AS single
18240
- )
18241
- );
18242
- SET @drop_fks := IFNULL(@drop_fks, 'SELECT 1');
18243
- PREPARE drop_fks_stmt FROM @drop_fks;
18244
- EXECUTE drop_fks_stmt;
18245
- DEALLOCATE PREPARE drop_fks_stmt;`;
18246
- }
18247
- quoteLiteral(value) {
18248
- return `'${value.replace(/'/g, "''")}'`;
18239
+ const drops = [...new Set(existingConstraintNames)].map((name) => `ALTER TABLE ${this.quoteIdentifier(tableName)} DROP FOREIGN KEY ${this.quoteIdentifier(name)};`).join(`
18240
+ `);
18241
+ return drops ? `${drops}
18242
+ ${sql};` : `${sql};`;
18249
18243
  }
18250
18244
  addColumn(tableName, column) {
18251
18245
  const typeSql = this.getColumnType(column);
@@ -18428,41 +18422,17 @@ class PostgresDriver {
18428
18422
  const where = index.where ? ` WHERE ${index.where}` : "";
18429
18423
  return `CREATE ${kind}INDEX IF NOT EXISTS ${this.quoteIdentifier(idxName)} ON ${this.quoteIdentifier(tableName)} (${columns})${where};`;
18430
18424
  }
18431
- addForeignKey(tableName, columnName, refTable, refColumn, onDelete, onUpdate) {
18425
+ addForeignKey(tableName, columnName, refTable, refColumn, onDelete, onUpdate, existingConstraintNames = []) {
18432
18426
  const fkName = `${tableName}_${columnName}_fk`;
18433
18427
  let sql = `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD CONSTRAINT ${this.quoteIdentifier(fkName)} FOREIGN KEY (${this.quoteIdentifier(columnName)}) REFERENCES ${this.quoteIdentifier(refTable)}(${this.quoteIdentifier(refColumn)})`;
18434
18428
  if (onDelete)
18435
18429
  sql += ` ON DELETE ${onDelete.toUpperCase()}`;
18436
18430
  if (onUpdate)
18437
18431
  sql += ` ON UPDATE ${onUpdate.toUpperCase()}`;
18438
- return `${this.dropForeignKeysOn(tableName, columnName)}
18439
- ${sql};`;
18440
- }
18441
- dropForeignKeysOn(tableName, columnName) {
18442
- const table = this.quoteLiteral(tableName);
18443
- const column = this.quoteLiteral(columnName);
18444
- return `DO $$
18445
- DECLARE existing_name text;
18446
- BEGIN
18447
- FOR existing_name IN
18448
- SELECT con.conname
18449
- FROM pg_constraint con
18450
- JOIN pg_class rel ON rel.oid = con.conrelid
18451
- JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
18452
- WHERE con.contype = 'f'
18453
- AND rel.relname = ${table}
18454
- AND nsp.nspname = current_schema()
18455
- AND con.conkey = ARRAY[(
18456
- SELECT att.attnum FROM pg_attribute att
18457
- WHERE att.attrelid = rel.oid AND att.attname = ${column} AND att.attnum > 0
18458
- )]::smallint[]
18459
- LOOP
18460
- EXECUTE format('ALTER TABLE %I DROP CONSTRAINT %I', ${table}, existing_name);
18461
- END LOOP;
18462
- END $$;`;
18463
- }
18464
- quoteLiteral(value) {
18465
- return `'${value.replace(/'/g, "''")}'`;
18432
+ const drops = [...new Set(existingConstraintNames)].map((name) => `ALTER TABLE ${this.quoteIdentifier(tableName)} DROP CONSTRAINT ${this.quoteIdentifier(name)};`).join(`
18433
+ `);
18434
+ return drops ? `${drops}
18435
+ ${sql};` : `${sql};`;
18466
18436
  }
18467
18437
  addColumn(tableName, column) {
18468
18438
  const typeSql = this.getColumnType(column);
@@ -29124,6 +29094,37 @@ function canonicalStorageType(col, dialect) {
29124
29094
  return "TEXT";
29125
29095
  }
29126
29096
  }
29097
+ if (dialect === "mysql" || dialect === "vitess" || dialect === "singlestore") {
29098
+ switch (col.type) {
29099
+ case "string":
29100
+ return `VARCHAR(${col.maxLength ?? 255})`;
29101
+ case "text":
29102
+ return "TEXT";
29103
+ case "boolean":
29104
+ return "TINYINT(1)";
29105
+ case "integer":
29106
+ return "INTEGER";
29107
+ case "bigint":
29108
+ return "BIGINT";
29109
+ case "float":
29110
+ return "REAL";
29111
+ case "double":
29112
+ return "DOUBLE";
29113
+ case "decimal":
29114
+ return "DECIMAL";
29115
+ case "date":
29116
+ return "DATE";
29117
+ case "datetime":
29118
+ case "timestamptz":
29119
+ return "DATETIME";
29120
+ case "json":
29121
+ return "JSON";
29122
+ case "enum":
29123
+ return "ENUM";
29124
+ default:
29125
+ return col.type;
29126
+ }
29127
+ }
29127
29128
  return col.type;
29128
29129
  }
29129
29130
  function canonicalizeDefault(col) {
@@ -29230,6 +29231,8 @@ function referencesAreDifferent(r1, r2) {
29230
29231
  return true;
29231
29232
  if (!r1 || !r2)
29232
29233
  return false;
29234
+ if ((r1.constraintNames?.length ?? 0) > 1)
29235
+ return true;
29233
29236
  const act = (a2) => (a2 ?? "no action").toLowerCase();
29234
29237
  return r1.table !== r2.table || r1.column !== r2.column || act(r1.onDelete) !== act(r2.onDelete) || act(r1.onUpdate) !== act(r2.onUpdate);
29235
29238
  }
@@ -29477,7 +29480,7 @@ function generateDiffOperations(previous, next, opts = {}) {
29477
29480
  const currCol = currCols[colName];
29478
29481
  if (!currCol.references)
29479
29482
  continue;
29480
- const addFkStatement = driver.addForeignKey(curr.table, currCol.name, currCol.references.table, currCol.references.column, currCol.references.onDelete, currCol.references.onUpdate);
29483
+ const addFkStatement = driver.addForeignKey(curr.table, currCol.name, currCol.references.table, currCol.references.column, currCol.references.onDelete, currCol.references.onUpdate, prevCols[colName].references?.constraintNames);
29481
29484
  if (!addFkStatement)
29482
29485
  continue;
29483
29486
  tableChanges.push(addFkStatement);
@@ -31627,7 +31630,7 @@ function getPrefix() {
31627
31630
  }
31628
31631
  var prefix = getPrefix();
31629
31632
  // package.json
31630
- var version2 = "0.2.18";
31633
+ var version2 = "0.2.20";
31631
31634
 
31632
31635
  // bin/cli.ts
31633
31636
  init_actions();
@@ -3,7 +3,7 @@ export declare interface DialectDriver {
3
3
  createEnumType: (enumTypeName: string, values: string[]) => string
4
4
  createTable: (table: TablePlan) => string
5
5
  createIndex: (tableName: string, index: IndexPlan) => string
6
- addForeignKey: (tableName: string, columnName: string, refTable: string, refColumn: string, onDelete?: string, onUpdate?: string) => string
6
+ addForeignKey: (tableName: string, columnName: string, refTable: string, refColumn: string, onDelete?: string, onUpdate?: string, existingConstraintNames?: string[]) => string
7
7
  addColumn: (tableName: string, column: ColumnPlan) => string
8
8
  modifyColumn: (tableName: string, column: ColumnPlan) => string
9
9
  renameColumn: (tableName: string, from: string, to: string) => string
@@ -26,7 +26,7 @@ export declare class MySQLDriver implements DialectDriver {
26
26
  createEnumType(_enumTypeName: string, _values: string[]): string;
27
27
  createTable(table: TablePlan): string;
28
28
  createIndex(tableName: string, index: IndexPlan): string;
29
- addForeignKey(tableName: string, columnName: string, refTable: string, refColumn: string, onDelete?: string, onUpdate?: string): string;
29
+ addForeignKey(tableName: string, columnName: string, refTable: string, refColumn: string, onDelete?: string, onUpdate?: string, existingConstraintNames?: string[]): string;
30
30
  addColumn(tableName: string, column: ColumnPlan): string;
31
31
  modifyColumn(tableName: string, column: ColumnPlan): string;
32
32
  renameColumn(tableName: string, from: string, to: string): string;
@@ -3,7 +3,7 @@ export declare interface DialectDriver {
3
3
  createEnumType: (enumTypeName: string, values: string[]) => string
4
4
  createTable: (table: TablePlan) => string
5
5
  createIndex: (tableName: string, index: IndexPlan) => string
6
- addForeignKey: (tableName: string, columnName: string, refTable: string, refColumn: string, onDelete?: string, onUpdate?: string) => string
6
+ addForeignKey: (tableName: string, columnName: string, refTable: string, refColumn: string, onDelete?: string, onUpdate?: string, existingConstraintNames?: string[]) => string
7
7
  addColumn: (tableName: string, column: ColumnPlan) => string
8
8
  modifyColumn: (tableName: string, column: ColumnPlan) => string
9
9
  renameColumn: (tableName: string, from: string, to: string) => string
@@ -21,7 +21,7 @@ export declare class PostgresDriver implements DialectDriver {
21
21
  createEnumType(enumTypeName: string, values: string[]): string;
22
22
  createTable(table: TablePlan): string;
23
23
  createIndex(tableName: string, index: IndexPlan): string;
24
- addForeignKey(tableName: string, columnName: string, refTable: string, refColumn: string, onDelete?: string, onUpdate?: string): string;
24
+ addForeignKey(tableName: string, columnName: string, refTable: string, refColumn: string, onDelete?: string, onUpdate?: string, existingConstraintNames?: string[]): string;
25
25
  addColumn(tableName: string, column: ColumnPlan): string;
26
26
  modifyColumn(tableName: string, column: ColumnPlan): string;
27
27
  renameColumn(tableName: string, from: string, to: string): string;
@@ -91,7 +91,13 @@ export declare interface ColumnPlan {
91
91
  isNullable: boolean
92
92
  hasDefault: boolean
93
93
  defaultValue?: PrimitiveDefault
94
- references?: { table: string, column: string, onDelete?: OnForeignKeyAction, onUpdate?: OnForeignKeyAction }
94
+ references?: {
95
+ table: string
96
+ column: string
97
+ onDelete?: OnForeignKeyAction
98
+ onUpdate?: OnForeignKeyAction
99
+ constraintNames?: string[]
100
+ }
95
101
  enumValues?: string[]
96
102
  maxLength?: number
97
103
  enumTypeName?: string
package/dist/src/index.js CHANGED
@@ -17739,6 +17739,8 @@ function sqlTypeToNormalized(rawType, dialect, ctx) {
17739
17739
  if (/^(?:varchar|character varying|nvarchar|nchar|char|character|bpchar)/.test(base)) {
17740
17740
  const m = t.match(/\((\d+)\)/);
17741
17741
  const len = m ? Number(m[1]) : undefined;
17742
+ if (isMysqlLike(dialect))
17743
+ return "string";
17742
17744
  return len !== undefined && len > 255 ? "text" : "string";
17743
17745
  }
17744
17746
  if (/text|clob/.test(base))
@@ -17766,6 +17768,13 @@ function sqlTypeToNormalized(rawType, dialect, ctx) {
17766
17768
  function mysqlColumnType(dataType, columnType, enumValues) {
17767
17769
  return sqlTypeToNormalized(String(columnType || dataType || ""), "mysql", { enumValues });
17768
17770
  }
17771
+ function mysqlColumnMaxLength(columnType) {
17772
+ const match = String(columnType || "").trim().match(/^varchar\((\d+)\)/i);
17773
+ if (!match)
17774
+ return;
17775
+ const length = Number(match[1]);
17776
+ return Number.isSafeInteger(length) && length > 0 ? length : undefined;
17777
+ }
17769
17778
  function asAction(raw2) {
17770
17779
  const v = String(raw2 ?? "").toLowerCase().trim();
17771
17780
  if (v === "cascade" || v === "restrict" || v === "set null")
@@ -17880,7 +17889,10 @@ async function buildPgTable(qb, table, enumLabels) {
17880
17889
  WHERE t.relname = $1 AND t.relkind = 'r'
17881
17890
  ORDER BY i.relname, ord`, [table]);
17882
17891
  const fkRows = await qb.unsafe(`SELECT kcu.column_name, ccu.table_name AS ref_table, ccu.column_name AS ref_column,
17883
- rc.delete_rule, rc.update_rule
17892
+ tc.constraint_name, rc.delete_rule, rc.update_rule,
17893
+ (SELECT COUNT(*) FROM information_schema.key_column_usage count_kcu
17894
+ WHERE count_kcu.constraint_name = tc.constraint_name
17895
+ AND count_kcu.constraint_schema = tc.constraint_schema) AS constraint_column_count
17884
17896
  FROM information_schema.table_constraints tc
17885
17897
  JOIN information_schema.key_column_usage kcu ON kcu.constraint_name = tc.constraint_name
17886
17898
  JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name
@@ -17888,11 +17900,15 @@ async function buildPgTable(qb, table, enumLabels) {
17888
17900
  WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1 AND tc.table_schema = 'public'`, [table]);
17889
17901
  const fkByColumn = new Map;
17890
17902
  for (const fk of fkRows) {
17891
- fkByColumn.set(String(fk.column_name), {
17903
+ const name = String(fk.column_name);
17904
+ const existing = fkByColumn.get(name);
17905
+ const constraintNames = Number(fk.constraint_column_count) === 1 ? [...new Set([...existing?.constraintNames ?? [], String(fk.constraint_name)])] : existing?.constraintNames;
17906
+ fkByColumn.set(name, existing ? { ...existing, constraintNames } : {
17892
17907
  table: String(fk.ref_table),
17893
17908
  column: String(fk.ref_column ?? "id"),
17894
17909
  onDelete: asAction(fk.delete_rule),
17895
- onUpdate: asAction(fk.update_rule)
17910
+ onUpdate: asAction(fk.update_rule),
17911
+ constraintNames
17896
17912
  });
17897
17913
  }
17898
17914
  const { indexes, uniqueColumns } = groupPhysicalIndexes(table, idxRows.map((r) => ({
@@ -17931,18 +17947,27 @@ async function buildMysqlTable(qb, table) {
17931
17947
  WHERE table_name = ? AND table_schema = DATABASE()
17932
17948
  ORDER BY index_name, seq_in_index`, [table]);
17933
17949
  const fkRows = await qb.unsafe(`SELECT kcu.column_name, kcu.referenced_table_name AS ref_table, kcu.referenced_column_name AS ref_column,
17934
- rc.delete_rule, rc.update_rule
17950
+ kcu.constraint_name, rc.delete_rule, rc.update_rule,
17951
+ (SELECT COUNT(*) FROM information_schema.key_column_usage count_kcu
17952
+ WHERE count_kcu.constraint_name = kcu.constraint_name
17953
+ AND count_kcu.constraint_schema = kcu.constraint_schema) AS constraint_column_count
17935
17954
  FROM information_schema.key_column_usage kcu
17936
17955
  JOIN information_schema.referential_constraints rc
17937
17956
  ON rc.constraint_name = kcu.constraint_name AND rc.constraint_schema = kcu.table_schema
17938
17957
  WHERE kcu.table_name = ? AND kcu.table_schema = DATABASE() AND kcu.referenced_table_name IS NOT NULL`, [table]);
17939
17958
  const fkByColumn = new Map;
17940
17959
  for (const fk of fkRows) {
17941
- fkByColumn.set(String(fk.column_name ?? fk.COLUMN_NAME), {
17960
+ const name = String(fk.column_name ?? fk.COLUMN_NAME);
17961
+ const existing = fkByColumn.get(name);
17962
+ const columnCount = Number(fk.constraint_column_count ?? fk.CONSTRAINT_COLUMN_COUNT);
17963
+ const constraintName = String(fk.constraint_name ?? fk.CONSTRAINT_NAME);
17964
+ const constraintNames = columnCount === 1 ? [...new Set([...existing?.constraintNames ?? [], constraintName])] : existing?.constraintNames;
17965
+ fkByColumn.set(name, existing ? { ...existing, constraintNames } : {
17942
17966
  table: String(fk.ref_table ?? fk.REF_TABLE),
17943
17967
  column: String(fk.ref_column ?? fk.REF_COLUMN ?? "id"),
17944
17968
  onDelete: asAction(fk.delete_rule ?? fk.DELETE_RULE),
17945
- onUpdate: asAction(fk.update_rule ?? fk.UPDATE_RULE)
17969
+ onUpdate: asAction(fk.update_rule ?? fk.UPDATE_RULE),
17970
+ constraintNames
17946
17971
  });
17947
17972
  }
17948
17973
  const { indexes, uniqueColumns } = groupPhysicalIndexes(table, idxRows.map((r) => ({
@@ -17968,6 +17993,7 @@ async function buildMysqlTable(qb, table) {
17968
17993
  hasDefault,
17969
17994
  defaultValue: hasDefault ? normalizeRawDefault(rawDefault) : undefined,
17970
17995
  enumValues,
17996
+ maxLength: mysqlColumnMaxLength(columnType),
17971
17997
  references: fkByColumn.get(name)
17972
17998
  };
17973
17999
  });
@@ -18202,49 +18228,17 @@ class MySQLDriver {
18202
18228
  const columns = index.columns.map((c) => this.quoteIdentifier(c)).join(", ");
18203
18229
  return `CREATE ${kind}INDEX ${this.quoteIdentifier(idxName)} ON ${this.quoteIdentifier(tableName)} (${columns});`;
18204
18230
  }
18205
- addForeignKey(tableName, columnName, refTable, refColumn, onDelete, onUpdate) {
18231
+ addForeignKey(tableName, columnName, refTable, refColumn, onDelete, onUpdate, existingConstraintNames = []) {
18206
18232
  const fkName = `${tableName}_${columnName}_fk`;
18207
18233
  let sql = `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD CONSTRAINT ${this.quoteIdentifier(fkName)} FOREIGN KEY (${this.quoteIdentifier(columnName)}) REFERENCES ${this.quoteIdentifier(refTable)}(${this.quoteIdentifier(refColumn)})`;
18208
18234
  if (onDelete)
18209
18235
  sql += ` ON DELETE ${onDelete.toUpperCase()}`;
18210
18236
  if (onUpdate)
18211
18237
  sql += ` ON UPDATE ${onUpdate.toUpperCase()}`;
18212
- return `${this.dropForeignKeysOn(tableName, columnName)}
18213
- ${sql};`;
18214
- }
18215
- dropForeignKeysOn(tableName, columnName) {
18216
- const table = this.quoteLiteral(tableName);
18217
- const column = this.quoteLiteral(columnName);
18218
- return `SET @drop_fks := (
18219
- SELECT CONCAT(
18220
- 'ALTER TABLE \`', ${table}, '\` DROP FOREIGN KEY \`',
18221
- GROUP_CONCAT(kcu.CONSTRAINT_NAME SEPARATOR '\`, DROP FOREIGN KEY \`'),
18222
- '\`'
18223
- )
18224
- FROM information_schema.KEY_COLUMN_USAGE kcu
18225
- WHERE kcu.TABLE_SCHEMA = DATABASE()
18226
- AND kcu.TABLE_NAME = ${table}
18227
- AND kcu.COLUMN_NAME = ${column}
18228
- AND kcu.REFERENCED_TABLE_NAME IS NOT NULL
18229
- AND kcu.CONSTRAINT_NAME IN (
18230
- SELECT single.CONSTRAINT_NAME FROM (
18231
- SELECT inner_kcu.CONSTRAINT_NAME
18232
- FROM information_schema.KEY_COLUMN_USAGE inner_kcu
18233
- WHERE inner_kcu.TABLE_SCHEMA = DATABASE()
18234
- AND inner_kcu.TABLE_NAME = ${table}
18235
- AND inner_kcu.REFERENCED_TABLE_NAME IS NOT NULL
18236
- GROUP BY inner_kcu.CONSTRAINT_NAME
18237
- HAVING COUNT(*) = 1
18238
- ) AS single
18239
- )
18240
- );
18241
- SET @drop_fks := IFNULL(@drop_fks, 'SELECT 1');
18242
- PREPARE drop_fks_stmt FROM @drop_fks;
18243
- EXECUTE drop_fks_stmt;
18244
- DEALLOCATE PREPARE drop_fks_stmt;`;
18245
- }
18246
- quoteLiteral(value) {
18247
- return `'${value.replace(/'/g, "''")}'`;
18238
+ const drops = [...new Set(existingConstraintNames)].map((name) => `ALTER TABLE ${this.quoteIdentifier(tableName)} DROP FOREIGN KEY ${this.quoteIdentifier(name)};`).join(`
18239
+ `);
18240
+ return drops ? `${drops}
18241
+ ${sql};` : `${sql};`;
18248
18242
  }
18249
18243
  addColumn(tableName, column) {
18250
18244
  const typeSql = this.getColumnType(column);
@@ -18427,41 +18421,17 @@ class PostgresDriver {
18427
18421
  const where = index.where ? ` WHERE ${index.where}` : "";
18428
18422
  return `CREATE ${kind}INDEX IF NOT EXISTS ${this.quoteIdentifier(idxName)} ON ${this.quoteIdentifier(tableName)} (${columns})${where};`;
18429
18423
  }
18430
- addForeignKey(tableName, columnName, refTable, refColumn, onDelete, onUpdate) {
18424
+ addForeignKey(tableName, columnName, refTable, refColumn, onDelete, onUpdate, existingConstraintNames = []) {
18431
18425
  const fkName = `${tableName}_${columnName}_fk`;
18432
18426
  let sql = `ALTER TABLE ${this.quoteIdentifier(tableName)} ADD CONSTRAINT ${this.quoteIdentifier(fkName)} FOREIGN KEY (${this.quoteIdentifier(columnName)}) REFERENCES ${this.quoteIdentifier(refTable)}(${this.quoteIdentifier(refColumn)})`;
18433
18427
  if (onDelete)
18434
18428
  sql += ` ON DELETE ${onDelete.toUpperCase()}`;
18435
18429
  if (onUpdate)
18436
18430
  sql += ` ON UPDATE ${onUpdate.toUpperCase()}`;
18437
- return `${this.dropForeignKeysOn(tableName, columnName)}
18438
- ${sql};`;
18439
- }
18440
- dropForeignKeysOn(tableName, columnName) {
18441
- const table = this.quoteLiteral(tableName);
18442
- const column = this.quoteLiteral(columnName);
18443
- return `DO $$
18444
- DECLARE existing_name text;
18445
- BEGIN
18446
- FOR existing_name IN
18447
- SELECT con.conname
18448
- FROM pg_constraint con
18449
- JOIN pg_class rel ON rel.oid = con.conrelid
18450
- JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
18451
- WHERE con.contype = 'f'
18452
- AND rel.relname = ${table}
18453
- AND nsp.nspname = current_schema()
18454
- AND con.conkey = ARRAY[(
18455
- SELECT att.attnum FROM pg_attribute att
18456
- WHERE att.attrelid = rel.oid AND att.attname = ${column} AND att.attnum > 0
18457
- )]::smallint[]
18458
- LOOP
18459
- EXECUTE format('ALTER TABLE %I DROP CONSTRAINT %I', ${table}, existing_name);
18460
- END LOOP;
18461
- END $$;`;
18462
- }
18463
- quoteLiteral(value) {
18464
- return `'${value.replace(/'/g, "''")}'`;
18431
+ const drops = [...new Set(existingConstraintNames)].map((name) => `ALTER TABLE ${this.quoteIdentifier(tableName)} DROP CONSTRAINT ${this.quoteIdentifier(name)};`).join(`
18432
+ `);
18433
+ return drops ? `${drops}
18434
+ ${sql};` : `${sql};`;
18465
18435
  }
18466
18436
  addColumn(tableName, column) {
18467
18437
  const typeSql = this.getColumnType(column);
@@ -29123,6 +29093,37 @@ function canonicalStorageType(col, dialect) {
29123
29093
  return "TEXT";
29124
29094
  }
29125
29095
  }
29096
+ if (dialect === "mysql" || dialect === "vitess" || dialect === "singlestore") {
29097
+ switch (col.type) {
29098
+ case "string":
29099
+ return `VARCHAR(${col.maxLength ?? 255})`;
29100
+ case "text":
29101
+ return "TEXT";
29102
+ case "boolean":
29103
+ return "TINYINT(1)";
29104
+ case "integer":
29105
+ return "INTEGER";
29106
+ case "bigint":
29107
+ return "BIGINT";
29108
+ case "float":
29109
+ return "REAL";
29110
+ case "double":
29111
+ return "DOUBLE";
29112
+ case "decimal":
29113
+ return "DECIMAL";
29114
+ case "date":
29115
+ return "DATE";
29116
+ case "datetime":
29117
+ case "timestamptz":
29118
+ return "DATETIME";
29119
+ case "json":
29120
+ return "JSON";
29121
+ case "enum":
29122
+ return "ENUM";
29123
+ default:
29124
+ return col.type;
29125
+ }
29126
+ }
29126
29127
  return col.type;
29127
29128
  }
29128
29129
  function canonicalizeDefault(col) {
@@ -29229,6 +29230,8 @@ function referencesAreDifferent(r1, r2) {
29229
29230
  return true;
29230
29231
  if (!r1 || !r2)
29231
29232
  return false;
29233
+ if ((r1.constraintNames?.length ?? 0) > 1)
29234
+ return true;
29232
29235
  const act = (a2) => (a2 ?? "no action").toLowerCase();
29233
29236
  return r1.table !== r2.table || r1.column !== r2.column || act(r1.onDelete) !== act(r2.onDelete) || act(r1.onUpdate) !== act(r2.onUpdate);
29234
29237
  }
@@ -29476,7 +29479,7 @@ function generateDiffOperations(previous, next, opts = {}) {
29476
29479
  const currCol = currCols[colName];
29477
29480
  if (!currCol.references)
29478
29481
  continue;
29479
- const addFkStatement = driver.addForeignKey(curr.table, currCol.name, currCol.references.table, currCol.references.column, currCol.references.onDelete, currCol.references.onUpdate);
29482
+ const addFkStatement = driver.addForeignKey(curr.table, currCol.name, currCol.references.table, currCol.references.column, currCol.references.onDelete, currCol.references.onUpdate, prevCols[colName].references?.constraintNames);
29480
29483
  if (!addFkStatement)
29481
29484
  continue;
29482
29485
  tableChanges.push(addFkStatement);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bun-query-builder",
3
3
  "type": "module",
4
- "version": "0.2.18",
4
+ "version": "0.2.20",
5
5
  "description": "A simple yet performant query builder for TypeScript. Built with Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",