dml_type) { 'INSERT' => $this->buildDeleteSQL($log), 'UPDATE' => $this->buildRevertUpdateSQL($log), 'DELETE' => $this->buildReinsertSQL($log), }; } /** * 복구 실행 (트랜잭션) */ public function executeRollback(int $auditId): array { $log = TriggerAuditLog::findOrFail($auditId); $sql = $this->generateRollbackSQL($auditId); DB::statement('SET @disable_audit_trigger = 1'); try { DB::transaction(function () use ($sql) { DB::statement($sql); }); return [ 'success' => true, 'audit_id' => $auditId, 'table_name' => $log->table_name, 'row_id' => $log->row_id, 'original_dml' => $log->dml_type, 'rollback_sql' => $sql, ]; } catch (\Throwable $e) { return [ 'success' => false, 'audit_id' => $auditId, 'error' => $e->getMessage(), 'rollback_sql' => $sql, ]; } finally { DB::statement('SET @disable_audit_trigger = NULL'); } } /** * 특정 레코드의 특정 시점 상태 조회 */ public function getRecordStateAt(string $table, string $rowId, Carbon $at): ?array { $log = TriggerAuditLog::where('table_name', $table) ->where('row_id', $rowId) ->where('created_at', '<=', $at) ->orderByDesc('created_at') ->first(); if (! $log) { return null; } return match ($log->dml_type) { 'INSERT', 'UPDATE' => $log->new_values, 'DELETE' => null, }; } /** * INSERT 복구: 삽입된 레코드 삭제 */ private function buildDeleteSQL(TriggerAuditLog $log): string { $rowId = DB::getPdo()->quote($log->row_id); return "DELETE FROM `{$log->table_name}` WHERE `id` = {$rowId} LIMIT 1"; } /** * UPDATE 복구: old_values로 되돌림 */ private function buildRevertUpdateSQL(TriggerAuditLog $log): string { if (empty($log->old_values)) { throw new \RuntimeException("No old_values for audit #{$log->id}"); } $pdo = DB::getPdo(); $sets = collect($log->old_values) ->map(fn ($val, $col) => "`{$col}` = ".($val === null ? 'NULL' : $pdo->quote((string) $val))) ->implode(', '); $rowId = $pdo->quote($log->row_id); return "UPDATE `{$log->table_name}` SET {$sets} WHERE `id` = {$rowId} LIMIT 1"; } /** * DELETE 복구: old_values로 재삽입 */ private function buildReinsertSQL(TriggerAuditLog $log): string { if (empty($log->old_values)) { throw new \RuntimeException("No old_values for audit #{$log->id}"); } $pdo = DB::getPdo(); $cols = collect($log->old_values)->keys()->map(fn ($c) => "`{$c}`")->implode(', '); $vals = collect($log->old_values)->values() ->map(fn ($v) => $v === null ? 'NULL' : $pdo->quote((string) $v)) ->implode(', '); return "INSERT INTO `{$log->table_name}` ({$cols}) VALUES ({$vals})"; } }