( trx: DbTransaction, data: ReplaceRowsData, table: TableDefinition, requestId: string )
| 385 | * Callers gate it before opening the tx — see `replaceTableRows` and `importReplaceRows`. |
| 386 | */ |
| 387 | export async function replaceTableRowsWithTx( |
| 388 | trx: DbTransaction, |
| 389 | data: ReplaceRowsData, |
| 390 | table: TableDefinition, |
| 391 | requestId: string |
| 392 | ): Promise<ReplaceRowsResult> { |
| 393 | if (data.tableId !== table.id) { |
| 394 | throw new Error(`Table ID mismatch: ${data.tableId} vs ${table.id}`) |
| 395 | } |
| 396 | if (data.workspaceId !== table.workspaceId) { |
| 397 | throw new Error(`Workspace ID mismatch: ${data.workspaceId} does not own table ${data.tableId}`) |
| 398 | } |
| 399 | |
| 400 | for (let i = 0; i < data.rows.length; i++) { |
| 401 | const row = data.rows[i] |
| 402 | |
| 403 | const sizeValidation = validateRowSize(row) |
| 404 | if (!sizeValidation.valid) { |
| 405 | throw new Error(`Row ${i + 1}: ${sizeValidation.errors.join(', ')}`) |
| 406 | } |
| 407 | |
| 408 | const schemaValidation = coerceRowToSchema(row, table.schema) |
| 409 | if (!schemaValidation.valid) { |
| 410 | throw new Error(`Row ${i + 1}: ${schemaValidation.errors.join(', ')}`) |
| 411 | } |
| 412 | } |
| 413 | |
| 414 | const uniqueColumns = getUniqueColumns(table.schema) |
| 415 | if (uniqueColumns.length > 0 && data.rows.length > 0) { |
| 416 | const seen = new Map<string, Map<string, number>>() |
| 417 | for (const col of uniqueColumns) { |
| 418 | seen.set(col.name, new Map()) |
| 419 | } |
| 420 | for (let i = 0; i < data.rows.length; i++) { |
| 421 | const row = data.rows[i] |
| 422 | for (const col of uniqueColumns) { |
| 423 | const value = row[col.name] |
| 424 | if (value === null || value === undefined) continue |
| 425 | const normalized = typeof value === 'string' ? value.toLowerCase() : JSON.stringify(value) |
| 426 | const map = seen.get(col.name)! |
| 427 | if (map.has(normalized)) { |
| 428 | throw new Error( |
| 429 | `Row ${i + 1}: Column "${col.name}" must be unique. Value "${String(value)}" duplicates row ${map.get(normalized)! + 1} in batch` |
| 430 | ) |
| 431 | } |
| 432 | map.set(normalized, i) |
| 433 | } |
| 434 | } |
| 435 | } |
| 436 | |
| 437 | const now = new Date() |
| 438 | |
| 439 | const totalRowWork = Math.max(0, table.rowCount ?? 0) + data.rows.length |
| 440 | const statementMs = scaledStatementTimeoutMs(totalRowWork, { |
| 441 | baseMs: 120_000, |
| 442 | perRowMs: 3, |
| 443 | }) |
| 444 |
no test coverage detected