( tableId: string, rows: RowData[], schema: TableSchema, executor: UniqueCheckExecutor = db )
| 519 | * default `db` connection only observes committed state. |
| 520 | */ |
| 521 | export async function checkBatchUniqueConstraintsDb( |
| 522 | tableId: string, |
| 523 | rows: RowData[], |
| 524 | schema: TableSchema, |
| 525 | executor: UniqueCheckExecutor = db |
| 526 | ): Promise<{ valid: boolean; errors: Array<{ row: number; errors: string[] }> }> { |
| 527 | const uniqueColumns = getUniqueColumns(schema) |
| 528 | const rowErrors: Array<{ row: number; errors: string[] }> = [] |
| 529 | |
| 530 | if (uniqueColumns.length === 0) { |
| 531 | return { valid: true, errors: [] } |
| 532 | } |
| 533 | |
| 534 | // Build a set of all unique values for each column to check against DB. |
| 535 | // Keyed by the stable column id (the row-data storage key). |
| 536 | const valuesByColumn = new Map<string, { values: Set<string>; column: ColumnDefinition }>() |
| 537 | |
| 538 | for (const column of uniqueColumns) { |
| 539 | valuesByColumn.set(getColumnId(column), { values: new Set(), column }) |
| 540 | } |
| 541 | |
| 542 | // Collect all unique values from the batch and check for duplicates within the batch |
| 543 | const batchValueMap = new Map<string, Map<string, number>>() // columnId -> (normalizedValue -> firstRowIndex) |
| 544 | |
| 545 | for (const column of uniqueColumns) { |
| 546 | batchValueMap.set(getColumnId(column), new Map()) |
| 547 | } |
| 548 | |
| 549 | for (let i = 0; i < rows.length; i++) { |
| 550 | const rowData = rows[i] |
| 551 | const currentRowErrors: string[] = [] |
| 552 | |
| 553 | for (const column of uniqueColumns) { |
| 554 | const key = getColumnId(column) |
| 555 | const value = rowData[key] |
| 556 | if (value === null || value === undefined) continue |
| 557 | |
| 558 | const normalizedValue = |
| 559 | typeof value === 'string' ? value.toLowerCase() : JSON.stringify(value) |
| 560 | |
| 561 | // Check for duplicate within batch |
| 562 | const columnValueMap = batchValueMap.get(key)! |
| 563 | if (columnValueMap.has(normalizedValue)) { |
| 564 | const firstRowIndex = columnValueMap.get(normalizedValue)! |
| 565 | currentRowErrors.push( |
| 566 | `Column "${column.name}" must be unique. Value "${value}" duplicates row ${firstRowIndex + 1} in batch` |
| 567 | ) |
| 568 | } else { |
| 569 | columnValueMap.set(normalizedValue, i) |
| 570 | valuesByColumn.get(key)!.values.add(normalizedValue) |
| 571 | } |
| 572 | } |
| 573 | |
| 574 | if (currentRowErrors.length > 0) { |
| 575 | rowErrors.push({ row: i, errors: currentRowErrors }) |
| 576 | } |
| 577 | } |
| 578 |
no test coverage detected