@@ -1368,6 +1368,73 @@ export async function deleteColumn(
13681368 return { ...table , schema : updatedSchema , metadata : updatedMetadata , updatedAt : now }
13691369}
13701370
1371+ /**
1372+ * Deletes multiple columns from a table in a single transaction.
1373+ * Avoids the race condition of calling deleteColumn multiple times in parallel.
1374+ */
1375+ export async function deleteColumns (
1376+ data : { tableId : string ; columnNames : string [ ] } ,
1377+ requestId : string
1378+ ) : Promise < TableDefinition > {
1379+ const table = await getTableById ( data . tableId )
1380+ if ( ! table ) {
1381+ throw new Error ( 'Table not found' )
1382+ }
1383+
1384+ const schema = table . schema
1385+ const namesToDelete = new Set < string > ( )
1386+ const notFound : string [ ] = [ ]
1387+
1388+ for ( const name of data . columnNames ) {
1389+ const col = schema . columns . find ( ( c ) => c . name . toLowerCase ( ) === name . toLowerCase ( ) )
1390+ if ( ! col ) {
1391+ notFound . push ( name )
1392+ } else {
1393+ namesToDelete . add ( col . name )
1394+ }
1395+ }
1396+
1397+ if ( notFound . length > 0 ) {
1398+ throw new Error ( `Columns not found: ${ notFound . join ( ', ' ) } ` )
1399+ }
1400+
1401+ const remaining = schema . columns . filter ( ( c ) => ! namesToDelete . has ( c . name ) )
1402+ if ( remaining . length === 0 ) {
1403+ throw new Error ( 'Cannot delete all columns from a table' )
1404+ }
1405+
1406+ const updatedSchema : TableSchema = { columns : remaining }
1407+
1408+ const metadata = table . metadata as TableMetadata | null
1409+ let updatedMetadata = metadata
1410+ if ( metadata ?. columnWidths ) {
1411+ const widths = { ...metadata . columnWidths }
1412+ for ( const n of namesToDelete ) delete widths [ n ]
1413+ updatedMetadata = { ...metadata , columnWidths : widths }
1414+ }
1415+
1416+ const now = new Date ( )
1417+
1418+ await db . transaction ( async ( trx ) => {
1419+ await trx
1420+ . update ( userTableDefinitions )
1421+ . set ( { schema : updatedSchema , metadata : updatedMetadata , updatedAt : now } )
1422+ . where ( eq ( userTableDefinitions . id , data . tableId ) )
1423+
1424+ for ( const name of namesToDelete ) {
1425+ await trx . execute (
1426+ sql `UPDATE user_table_rows SET data = data - ${ name } WHERE table_id = ${ data . tableId } AND data ? ${ name } `
1427+ )
1428+ }
1429+ } )
1430+
1431+ logger . info (
1432+ `[${ requestId } ] Deleted columns [${ [ ...namesToDelete ] . join ( ', ' ) } ] from table ${ data . tableId } `
1433+ )
1434+
1435+ return { ...table , schema : updatedSchema , metadata : updatedMetadata , updatedAt : now }
1436+ }
1437+
13711438/**
13721439 * Changes the type of a column. Validates that existing data is compatible.
13731440 *
0 commit comments