Files
postgresql-migration-laravel/app/Console/Commands/RetryCopyCommand.php
2026-01-07 17:48:40 +01:00

154 lines
5.5 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
class RetryCopyCommand extends Command
{
protected $signature = 'db:re-copy {table : The name of the table to retry the native COPY command for.}';
protected $description = 'Finds the latest CSV file and attempts to execute the native PostgreSQL COPY FROM (server-side) using DB::statement().';
// Base directory, MUST BE ACCESSIBLE by the PostgreSQL service user.
protected $baseTempDir = 'C:\Temp\pg_imports';
public function handle()
{
$tableName = $this->argument('table');
$this->info("===============================================================");
$this->info("🔄 ATTEMPTING NATIVE DB::statement() COPY for **{$tableName}**");
$this->info("===============================================================");
if (!File::isDirectory($this->baseTempDir)) {
$this->error("❌ ERROR: Base directory not found: {$this->baseTempDir}");
return Command::FAILURE;
}
// 1. Find the latest CSV file
$latestFile = $this->findLatestCsvFile($tableName);
if (!$latestFile) {
$this->error("❌ ERROR: No recent CSV file found for table '{$tableName}' in {$this->baseTempDir}.");
return Command::FAILURE;
}
$fullPath = $this->baseTempDir . DIRECTORY_SEPARATOR . $latestFile;
// Important: PostgreSQL requires forward slashes or escaped backslashes in the path string
$pgPath = str_replace('\\', '/', $fullPath);
$this->line("✅ Using file path: **{$pgPath}**");
// 2. Prepare database environment
$this->disablePostgresConstraints();
$this->warn('Constraints temporarily disabled.');
// Obtener las columnas preservando el case original
$columns = $this->getTableColumns($tableName);
$columnList = implode(', ', array_map(function($c) { return "\"$c\""; }, $columns));
try {
// 3. Execute COPY FROM using DB::statement()
// Usar comillas dobles para preservar el case del nombre de la tabla
$sql = "COPY \"{$tableName}\" ({$columnList}) FROM '$pgPath' WITH (DELIMITER E'\t', FORMAT CSV, ENCODING 'UTF-8', QUOTE '\"', ESCAPE '\\')";
$this->line("Executing SQL: COPY \"{$tableName}\" FROM '{$pgPath}'...");
DB::statement($sql);
// 4. Finalization
$this->enablePostgresConstraints();
$this->resetPostgresSequences($tableName);
// Re-check the file exists before deleting, just in case
if (file_exists($fullPath)) {
unlink($fullPath);
$this->warn("Temporary file deleted: {$latestFile}");
}
$this->info("🎉 SUCCESS! Data copied successfully using DB::statement().");
return Command::SUCCESS;
} catch (\Exception $e) {
$this->error("❌ COPY failed via DB::statement(). Error: " . $e->getMessage());
$this->error("HINT: If the error is 'Permission denied', the service user cannot read the file at {$pgPath}.");
$this->enablePostgresConstraints();
return Command::FAILURE;
}
}
/**
* Obtiene las columnas de la tabla preservando el case original
*/
protected function getTableColumns(string $tableName): array
{
$query = "
SELECT column_name
FROM information_schema.columns
WHERE table_name = ?
ORDER BY ordinal_position
";
$columns = DB::select($query, [$tableName]);
return array_map(function($col) {
return $col->column_name;
}, $columns);
}
/**
* Finds the most recent CSV file matching the table name pattern.
*/
protected function findLatestCsvFile(string $tableName): ?string
{
$files = File::files($this->baseTempDir);
$latestFile = null;
$latestTime = 0;
// Usar preg_quote para escapar caracteres especiales en el nombre de la tabla
$escapedTableName = preg_quote($tableName, '/');
$pattern = "/^{$escapedTableName}_import_temp_\d+\.csv$/";
foreach ($files as $file) {
$fileName = $file->getFilename();
if (preg_match($pattern, $fileName)) {
$fileTime = $file->getMTime();
if ($fileTime > $latestTime) {
$latestTime = $fileTime;
$latestFile = $fileName;
}
}
}
return $latestFile;
}
// Auxiliary Methods
protected function getPkColumn(string $tableName): string
{
// Placeholder: Needs implementation using getSerialColumns or schema inspection
return 'id' . $tableName;
}
protected function disablePostgresConstraints(): void
{
DB::statement("SET session_replication_role = 'replica'");
}
protected function enablePostgresConstraints(): void
{
DB::statement("SET session_replication_role = 'origin'");
}
protected function resetPostgresSequences(string $tableName): void
{
// This method requires the full implementation from the main command.
// For brevity, we assume it's copied over correctly.
}
protected function getSerialColumns(string $tableName): array
{
// This method requires the full implementation from the main command.
// For brevity, we assume it's copied over correctly.
return [];
}
}