add csv analysis option

This commit is contained in:
Mikhail Chusavitin
2026-01-21 12:42:28 +03:00
parent d21da6d49e
commit ee02d4cc28
3 changed files with 184 additions and 28 deletions

View File

@@ -427,6 +427,9 @@ public function insertMultipleRows(string $schema, string $table, array $rows, a
];
}
/**
* Форматирует ошибку импорта с детальной информацией
*/
/**
* Форматирует ошибку импорта с детальной информацией
*/
@@ -444,10 +447,9 @@ private function formatImportError(\PDOException $e, int $lineNumber, array $row
$value = $rowData[$field] ?? 'N/A';
$columnType = $columnTypes[$field]['type'] ?? 'unknown';
// Проверяем, не слишком ли большое число
if (is_numeric($value)) {
$valueLength = strlen((string)$value);
return $baseMsg . "Поле '{$field}' ({$columnType}): значение '{$value}' (длина: {$valueLength}) слишком большое или имеет неверный формат. " .
return $baseMsg . "Поле '{$field}' ({$columnType}): значение '{$value}' (длина: {$valueLength}) слишком большое. " .
"Данные строки: " . $this->formatRowData($rowData);
}
@@ -457,15 +459,44 @@ private function formatImportError(\PDOException $e, int $lineNumber, array $row
return $baseMsg . "Данные обрезаны. Строка: " . $this->formatRowData($rowData);
}
// Foreign Key constraint
// Foreign Key constraint - УЛУЧШЕННАЯ ДИАГНОСТИКА
if ($code === '23000' && str_contains($message, 'foreign key constraint')) {
// Пытаемся извлечь имя FK constraint
if (preg_match('/CONSTRAINT `([^`]+)`/', $message, $matches)) {
$constraintName = $matches[1];
// Из имени constraint часто можно понять поле (например: fk_table_field)
// Пробуем найти поле в rowData которое может быть причиной
$suspectFields = [];
foreach ($rowData as $fieldName => $fieldValue) {
if ($fieldValue !== null && $fieldValue !== '') {
// Если имя constraint содержит имя поля
if (stripos($constraintName, $fieldName) !== false) {
$suspectFields[] = "{$fieldName}='{$fieldValue}'";
}
}
}
if (!empty($suspectFields)) {
return $baseMsg . "❌ СВЯЗАННОЕ ЗНАЧЕНИЕ НЕ НАЙДЕНО: " . implode(', ', $suspectFields) .
". Добавьте это значение в связанную таблицу или исправьте в CSV. " .
"Constraint: {$constraintName}";
}
return $baseMsg . "❌ ОШИБКА СВЯЗИ (FK): {$constraintName}. " .
"Проверьте значения полей-ссылок. Данные: " . $this->formatRowData($rowData);
}
// Старый формат ошибки
if (preg_match('/FOREIGN KEY $$`([^`]+)`$$/', $message, $matches)) {
$field = $matches[1];
$value = $rowData[$field] ?? 'N/A';
return $baseMsg . "Поле '{$field}' = '{$value}': значение не существует в связанной таблице. " .
"Данные: " . $this->formatRowData($rowData);
return $baseMsg . "Поле '{$field}' = '{$value}': значение НЕ СУЩЕСТВУЕТ в связанной таблице. " .
"Создайте это значение в справочнике или исправьте CSV.";
}
return $baseMsg . "Нарушена связь с другой таблицей. Данные: " . $this->formatRowData($rowData);
return $baseMsg . "❌ ОШИБКА СВЯЗИ: одно из значений не существует в связанной таблице. " .
"Данные: " . $this->formatRowData($rowData);
}
// Duplicate key
@@ -473,8 +504,7 @@ private function formatImportError(\PDOException $e, int $lineNumber, array $row
if (preg_match('/Duplicate entry \'([^\']+)\' for key \'([^\']+)\'/', $message, $matches)) {
$value = $matches[1];
$key = $matches[2];
return $baseMsg . "Дубликат: значение '{$value}' (ключ '{$key}') уже существует. " .
"Данные: " . $this->formatRowData($rowData);
return $baseMsg . "Дубликат: значение '{$value}' (ключ '{$key}') уже существует.";
}
return $baseMsg . "Дубликат записи. Данные: " . $this->formatRowData($rowData);
}
@@ -483,14 +513,13 @@ private function formatImportError(\PDOException $e, int $lineNumber, array $row
if (str_contains($message, "cannot be null") || str_contains($message, "doesn't have a default value")) {
if (preg_match('/Column \'([^\']+)\'/', $message, $matches)) {
$field = $matches[1];
return $baseMsg . "Поле '{$field}' обязательно для заполнения. " .
"Данные: " . $this->formatRowData($rowData);
return $baseMsg . "Поле '{$field}' обязательно для заполнения (не может быть пустым).";
}
return $baseMsg . "Не заполнены обязательные поля. Данные: " . $this->formatRowData($rowData);
return $baseMsg . "Не заполнены обязательные поля.";
}
// Общая ошибка БД
return $baseMsg . "Ошибка БД: " . $message . ". Данные: " . $this->formatRowData($rowData);
return $baseMsg . "Ошибка БД: " . $message;
}
/**
@@ -544,7 +573,31 @@ private function formatRowData(array $rowData): string
// Если не удалось распарсить - возвращаем как есть
return $date;
}
// ✅ ДОБАВЬТЕ ЭТИ ДВА МЕТОДА СЮДА:
/**
* Получает информацию о Foreign Keys таблицы
*/
public function getForeignKeyInfo(string $schema, string $table): array
{
$sql = "
SELECT
COLUMN_NAME,
CONSTRAINT_NAME,
REFERENCED_TABLE_SCHEMA,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = :schema
AND TABLE_NAME = :table
AND REFERENCED_TABLE_NAME IS NOT NULL
";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':schema' => $schema, ':table' => $table]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
private function isNumericType(string $dataType): bool
{