From 54e8c773253dbcccf0abb6c5433485233fd52c18 Mon Sep 17 00:00:00 2001 From: Chaim Marcus Date: Sun, 29 Mar 2026 20:31:11 +0000 Subject: [PATCH] fix: proper type coercion for raw SQL import columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read column metadata (type, nullable, default) from SHOW COLUMNS and coerce values accordingly: empty string/false → 0 for NOT NULL numeric columns, null for nullable ones. Fixes is_internal import error. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../DataMigration/Services/EntityImporter.php | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/files/custom/Espo/Modules/DataMigration/Services/EntityImporter.php b/files/custom/Espo/Modules/DataMigration/Services/EntityImporter.php index 5bd0a3d..280dffb 100644 --- a/files/custom/Espo/Modules/DataMigration/Services/EntityImporter.php +++ b/files/custom/Espo/Modules/DataMigration/Services/EntityImporter.php @@ -309,9 +309,12 @@ class EntityImporter return $newId; } - /** @var array Cache of existing columns per table */ + /** @var array Cache of existing column names per table */ private array $tableColumnsCache = []; + /** @var array> Cache of column metadata per table */ + private array $tableColumnMetaCache = []; + /** @var array Skipped columns log per entity type */ private array $skippedColumnsLog = []; @@ -348,10 +351,8 @@ class EntityImporter $value = json_encode($value, JSON_UNESCAPED_UNICODE); } - // Fix empty strings for integer/bool columns (MySQL strict mode) - if ($value === '' || $value === false) { - $value = null; - } + // Coerce value to match target column type + $value = $this->coerceValue($tableName, $column, $value); $columns[] = "`{$column}`"; $values[] = $value; @@ -377,16 +378,59 @@ class EntityImporter $pdo = $this->entityManager->getPDO(); $sth = $pdo->query("SHOW COLUMNS FROM `{$tableName}`"); $columns = []; + $meta = []; while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) { $columns[] = $row['Field']; + $meta[$row['Field']] = [ + 'type' => $row['Type'], + 'nullable' => $row['Null'] === 'YES', + 'default' => $row['Default'], + ]; } $this->tableColumnsCache[$tableName] = $columns; + $this->tableColumnMetaCache[$tableName] = $meta; return $columns; } + /** + * Coerce a value to be compatible with the target column type. + */ + private function coerceValue(string $tableName, string $column, mixed $value): mixed + { + $meta = $this->tableColumnMetaCache[$tableName][$column] ?? null; + + if ($meta === null) { + return $value; + } + + $type = strtolower($meta['type']); + $isNumeric = str_contains($type, 'int') || str_contains($type, 'decimal') + || str_contains($type, 'float') || str_contains($type, 'double') + || str_contains($type, 'tinyint') || str_contains($type, 'smallint') + || str_contains($type, 'bigint'); + + if ($isNumeric && ($value === '' || $value === false)) { + if ($meta['nullable']) { + return null; + } + + return 0; + } + + if (!$meta['nullable'] && $value === null) { + if ($isNumeric) { + return 0; + } + + return $meta['default'] ?? ''; + } + + return $value; + } + /** * @return array entityType => [skipped column names] */