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 []; } }