Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d5fe6c21a0 | |||
| 54e8c77325 | |||
| 35cda6e91c | |||
| ba00e7ecf6 | |||
| e3a1be1844 | |||
| fd279b6c8b | |||
| 73b5907539 | |||
| edeef544d4 |
@@ -39,7 +39,8 @@ class Snapshot implements Action
|
||||
$dumpFile = $snapshotDir . '/database.sql';
|
||||
|
||||
$cmd = sprintf(
|
||||
'mysqldump --host=%s --port=%s --user=%s --password=%s --single-transaction --quick %s > %s 2>&1',
|
||||
'mysqldump --host=%s --port=%s --user=%s --password=%s'
|
||||
. ' --ssl=false --no-tablespaces --single-transaction --quick %s > %s 2>&1',
|
||||
escapeshellarg($host),
|
||||
escapeshellarg($port),
|
||||
escapeshellarg($user),
|
||||
|
||||
@@ -309,9 +309,19 @@ class EntityImporter
|
||||
return $newId;
|
||||
}
|
||||
|
||||
/** @var array<string, string[]> Cache of existing column names per table */
|
||||
private array $tableColumnsCache = [];
|
||||
|
||||
/** @var array<string, array<string, array>> Cache of column metadata per table */
|
||||
private array $tableColumnMetaCache = [];
|
||||
|
||||
/** @var array<string, string[]> Skipped columns log per entity type */
|
||||
private array $skippedColumnsLog = [];
|
||||
|
||||
private function insertWithRawSql(string $entityType, array $record, string $newId): void
|
||||
{
|
||||
$tableName = $this->camelCaseToUnderscore($entityType);
|
||||
$existingColumns = $this->getTableColumns($tableName);
|
||||
|
||||
// Convert camelCase fields to snake_case for SQL
|
||||
$columns = [];
|
||||
@@ -325,15 +335,25 @@ class EntityImporter
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip non-storable array/object fields that come from getValueMap
|
||||
if (is_array($value) || is_object($value)) {
|
||||
$column = $this->camelCaseToUnderscore($field);
|
||||
|
||||
// Skip columns that don't exist in the target table
|
||||
if (!in_array($column, $existingColumns)) {
|
||||
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);
|
||||
// Coerce value to match target column type
|
||||
$value = $this->coerceValue($tableName, $column, $value);
|
||||
|
||||
$columns[] = "`{$column}`";
|
||||
$values[] = $value;
|
||||
$placeholders[] = '?';
|
||||
@@ -344,14 +364,85 @@ class EntityImporter
|
||||
|
||||
$sql = "INSERT INTO `{$tableName}` ({$columnStr}) VALUES ({$placeholderStr})";
|
||||
|
||||
try {
|
||||
$pdo = $this->entityManager->getPDO();
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($values);
|
||||
} catch (\Exception $e) {
|
||||
// If column mismatch, try with just known columns
|
||||
throw $e;
|
||||
}
|
||||
|
||||
private function getTableColumns(string $tableName): array
|
||||
{
|
||||
if (isset($this->tableColumnsCache[$tableName])) {
|
||||
return $this->tableColumnsCache[$tableName];
|
||||
}
|
||||
|
||||
$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<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
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "DataMigration",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.4",
|
||||
"acceptableVersions": [
|
||||
">=8.0.0"
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user