2 Commits

Author SHA1 Message Date
chaim e3a1be1844 release: v1.2.2 2026-03-29 20:25:54 +00:00
chaim fd279b6c8b fix: skip columns missing in target DB during raw SQL import
When importing entities with autoincrement (Case, Charge, Invoice),
validate each column exists in the target table before inserting.
Columns from modules not installed on the target are silently skipped
and reported in the import log summary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:25:54 +00:00
3 changed files with 68 additions and 14 deletions
@@ -309,9 +309,16 @@ class EntityImporter
return $newId; return $newId;
} }
/** @var array<string, string[]> Cache of existing columns per table */
private array $tableColumnsCache = [];
/** @var array<string, string[]> Skipped columns log per entity type */
private array $skippedColumnsLog = [];
private function insertWithRawSql(string $entityType, array $record, string $newId): void private function insertWithRawSql(string $entityType, array $record, string $newId): void
{ {
$tableName = $this->camelCaseToUnderscore($entityType); $tableName = $this->camelCaseToUnderscore($entityType);
$existingColumns = $this->getTableColumns($tableName);
// Convert camelCase fields to snake_case for SQL // Convert camelCase fields to snake_case for SQL
$columns = []; $columns = [];
@@ -325,15 +332,22 @@ class EntityImporter
continue; continue;
} }
// Skip non-storable array/object fields that come from getValueMap $column = $this->camelCaseToUnderscore($field);
if (is_array($value) || is_object($value)) {
// JSON fields should be stored as JSON strings // Skip columns that don't exist in the target table
if (is_array($value) || is_object($value)) { if (!in_array($column, $existingColumns)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE); if (!isset($this->skippedColumnsLog[$entityType][$column])) {
$this->skippedColumnsLog[$entityType][$column] = true;
} }
continue;
}
// JSON fields should be stored as JSON strings
if (is_array($value) || is_object($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
} }
$column = $this->camelCaseToUnderscore($field);
$columns[] = "`{$column}`"; $columns[] = "`{$column}`";
$values[] = $value; $values[] = $value;
$placeholders[] = '?'; $placeholders[] = '?';
@@ -344,14 +358,42 @@ class EntityImporter
$sql = "INSERT INTO `{$tableName}` ({$columnStr}) VALUES ({$placeholderStr})"; $sql = "INSERT INTO `{$tableName}` ({$columnStr}) VALUES ({$placeholderStr})";
try { $pdo = $this->entityManager->getPDO();
$pdo = $this->entityManager->getPDO(); $stmt = $pdo->prepare($sql);
$stmt = $pdo->prepare($sql); $stmt->execute($values);
$stmt->execute($values); }
} catch (\Exception $e) {
// If column mismatch, try with just known columns private function getTableColumns(string $tableName): array
throw $e; {
if (isset($this->tableColumnsCache[$tableName])) {
return $this->tableColumnsCache[$tableName];
} }
$pdo = $this->entityManager->getPDO();
$sth = $pdo->query("SHOW COLUMNS FROM `{$tableName}`");
$columns = [];
while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
$columns[] = $row['Field'];
}
$this->tableColumnsCache[$tableName] = $columns;
return $columns;
}
/**
* @return array<string, string[]> entityType => [skipped column names]
*/
public function getSkippedColumns(): array
{
$result = [];
foreach ($this->skippedColumnsLog as $entityType => $columns) {
$result[$entityType] = array_keys($columns);
}
return $result;
} }
private function resetAutoIncrement(string $entityType): void private function resetAutoIncrement(string $entityType): void
@@ -179,6 +179,18 @@ class ImportService
} }
} }
} }
// Report skipped columns (missing in target DB)
$skippedColumns = $this->entityImporter->getSkippedColumns();
if (!empty($skippedColumns)) {
$io->writeLine('');
$io->writeLine('WARNING: Skipped columns not found in target database:');
foreach ($skippedColumns as $type => $columns) {
$io->writeLine(" {$type}: " . implode(', ', $columns));
}
}
} }
private function entityTypeExists(string $entityType): bool private function entityTypeExists(string $entityType): bool
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "DataMigration", "name": "DataMigration",
"version": "1.2.1", "version": "1.2.2",
"acceptableVersions": [ "acceptableVersions": [
">=8.0.0" ">=8.0.0"
], ],