first commit
This commit is contained in:
153
app/Console/Commands/RetryCopyCommand.php
Normal file
153
app/Console/Commands/RetryCopyCommand.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?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 [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user