133 خطوط
4.5 KiB
PHP
133 خطوط
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use RuntimeException;
|
|
|
|
class ImportSqliteDatabase extends Command
|
|
{
|
|
protected $signature = 'db:import-sqlite
|
|
{path=database/database.sqlite : Path to the legacy SQLite database}
|
|
{--chunk=500 : Rows copied per batch}';
|
|
|
|
protected $description = 'Import all compatible data from the legacy SQLite database into an empty MySQL database';
|
|
|
|
public function handle(): int
|
|
{
|
|
if (DB::getDefaultConnection() !== 'mysql') {
|
|
$this->error('The default DB_CONNECTION must be mysql.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$path = $this->absolutePath((string) $this->argument('path'));
|
|
|
|
if (! is_file($path)) {
|
|
$this->error("SQLite database not found: {$path}");
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$chunkSize = max(1, (int) $this->option('chunk'));
|
|
config(['database.connections.sqlite_import.database' => $path]);
|
|
DB::purge('sqlite_import');
|
|
|
|
$migrationExitCode = Artisan::call('migrate', ['--force' => true]);
|
|
$this->output->write(Artisan::output());
|
|
|
|
if ($migrationExitCode !== self::SUCCESS) {
|
|
$this->error('MySQL migrations failed; no SQLite data was copied.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$sourceTables = array_map(
|
|
static fn (object $table): string => $table->name,
|
|
DB::connection('sqlite_import')->select(
|
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'"
|
|
)
|
|
);
|
|
$targetTables = array_map(
|
|
static fn (object $table): string => $table->name,
|
|
DB::connection('mysql')->select(
|
|
"SELECT table_name AS name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE'"
|
|
)
|
|
);
|
|
$tables = array_values(array_intersect($sourceTables, $targetTables));
|
|
$tables = array_values(array_diff($tables, ['migrations', 'sqlite_sequence']));
|
|
|
|
$this->assertTargetIsEmpty($tables);
|
|
DB::connection('mysql')->statement('SET FOREIGN_KEY_CHECKS=0');
|
|
|
|
try {
|
|
foreach ($tables as $table) {
|
|
$copied = $this->copyTable($table, $chunkSize);
|
|
$this->line("{$table}: {$copied} rows");
|
|
}
|
|
} finally {
|
|
DB::connection('mysql')->statement('SET FOREIGN_KEY_CHECKS=1');
|
|
}
|
|
|
|
$this->newLine();
|
|
$this->info('SQLite data imported into MySQL successfully. The SQLite file was left unchanged.');
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function assertTargetIsEmpty(array $tables): void
|
|
{
|
|
foreach ($tables as $table) {
|
|
if (DB::connection('mysql')->table($table)->exists()) {
|
|
throw new RuntimeException(
|
|
"MySQL table [{$table}] is not empty. Import into a new database to prevent duplicate or overwritten data."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function copyTable(string $table, int $chunkSize): int
|
|
{
|
|
$sourceColumns = Schema::connection('sqlite_import')->getColumnListing($table);
|
|
$targetColumns = Schema::connection('mysql')->getColumnListing($table);
|
|
$columns = array_values(array_intersect($sourceColumns, $targetColumns));
|
|
|
|
if ($columns === []) {
|
|
$this->warn("{$table}: skipped because it has no compatible columns");
|
|
|
|
return 0;
|
|
}
|
|
|
|
$copied = 0;
|
|
DB::connection('sqlite_import')->table($table)->select($columns)->orderBy($columns[0])->chunk(
|
|
$chunkSize,
|
|
function ($rows) use ($table, $columns, &$copied): void {
|
|
$payload = $rows->map(static function ($row) use ($columns): array {
|
|
$values = (array) $row;
|
|
|
|
return array_intersect_key($values, array_flip($columns));
|
|
})->all();
|
|
|
|
if ($payload !== []) {
|
|
DB::connection('mysql')->table($table)->insert($payload);
|
|
$copied += count($payload);
|
|
}
|
|
}
|
|
);
|
|
|
|
return $copied;
|
|
}
|
|
|
|
private function absolutePath(string $path): string
|
|
{
|
|
if (preg_match('/^(?:[A-Za-z]:[\\\\\/]|\/)/', $path) === 1) {
|
|
return $path;
|
|
}
|
|
|
|
return base_path($path);
|
|
}
|
|
}
|