option('chunk')); $show = max(0, (int) $this->option('show')); $legacyConnection = (string) $this->option('legacy-connection'); $legacyTable = (string) $this->option('legacy-table'); $newTable = (string) $this->option('new-table'); $artworkId = $this->option('artwork-id') !== null ? (int) $this->option('artwork-id') : null; $json = (bool) $this->option('json'); if ($artworkId !== null && $artworkId <= 0) { $this->error('The --artwork-id option must be a positive integer.'); return self::FAILURE; } if (! $this->tableExists($legacyConnection, $legacyTable)) { $this->error("Legacy table {$legacyConnection}.{$legacyTable} does not exist or the connection is unavailable."); return self::FAILURE; } if (! $this->tableExists(null, $newTable)) { $this->error("Current table {$newTable} does not exist."); return self::FAILURE; } if (! $this->columnExists($legacyConnection, $legacyTable, 'user_id') || ! $this->columnExists($legacyConnection, $legacyTable, 'id')) { $this->error("Legacy table {$legacyConnection}.{$legacyTable} must contain id and user_id columns."); return self::FAILURE; } if (! $this->columnExists(null, $newTable, 'user_id') || ! $this->columnExists(null, $newTable, 'id')) { $this->error("Current table {$newTable} must contain id and user_id columns."); return self::FAILURE; } $legacyCountQuery = DB::connection($legacyConnection)->table($legacyTable); if ($artworkId !== null) { $legacyCountQuery->where('id', $artworkId); } $total = (int) $legacyCountQuery->count(); if ($total === 0) { $message = $artworkId === null ? "No rows found in {$legacyConnection}.{$legacyTable}." : "Legacy artwork #{$artworkId} was not found in {$legacyConnection}.{$legacyTable}."; $this->warn($message); return $artworkId === null ? self::SUCCESS : self::FAILURE; } $this->info(sprintf( 'Comparing %d legacy %s.%s row(s) against %s in chunks of %d...', $total, $legacyConnection, $legacyTable, $newTable, $chunkSize, )); $summary = [ 'checked' => 0, 'matched' => 0, 'mismatched' => 0, 'missing_in_new' => 0, ]; $discrepancies = []; $legacyQuery = DB::connection($legacyConnection) ->table($legacyTable) ->select(['id', 'user_id']) ->orderBy('id'); if ($artworkId !== null) { $legacyQuery->where('id', $artworkId); } $legacyQuery->chunkById($chunkSize, function ($rows) use (&$summary, &$discrepancies, $show, $newTable): void { $artworkIds = $rows->pluck('id')->map(static fn (mixed $id): int => (int) $id)->all(); $newRows = DB::table($newTable) ->select(['id', 'user_id', 'title']) ->whereIn('id', $artworkIds) ->get() ->keyBy(static fn (object $row): int => (int) $row->id); foreach ($rows as $row) { $summary['checked']++; $legacyUserId = $this->normalizeNullableInt($row->user_id ?? null); $currentRow = $newRows->get((int) $row->id); if ($currentRow === null) { $summary['missing_in_new']++; $this->rememberDiscrepancy( $discrepancies, $show, (int) $row->id, $legacyUserId, null, null, 'missing_in_new', ); continue; } $newUserId = $this->normalizeNullableInt($currentRow->user_id ?? null); if ($legacyUserId === $newUserId) { $summary['matched']++; continue; } $summary['mismatched']++; $this->rememberDiscrepancy( $discrepancies, $show, (int) $row->id, $legacyUserId, $newUserId, (string) ($currentRow->title ?? ''), 'user_id_mismatch', ); } if ($this->output->isVerbose()) { $this->line(sprintf( ' audited=%d matched=%d mismatched=%d missing_in_new=%d', $summary['checked'], $summary['matched'], $summary['mismatched'], $summary['missing_in_new'], )); } }, 'id'); if ($json) { $this->line(json_encode([ 'summary' => $summary, 'discrepancies' => $discrepancies, ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); return ($summary['mismatched'] === 0 && $summary['missing_in_new'] === 0) ? self::SUCCESS : self::FAILURE; } $this->table( ['Checked', 'Matched', 'Mismatched', 'Missing In New'], [[ $summary['checked'], $summary['matched'], $summary['mismatched'], $summary['missing_in_new'], ]], ); if ($discrepancies !== []) { $this->newLine(); $this->warn(sprintf( 'Showing %d discrepancy row(s)%s.', count($discrepancies), ($summary['mismatched'] + $summary['missing_in_new']) > count($discrepancies) ? sprintf(' out of %d total', $summary['mismatched'] + $summary['missing_in_new']) : '', )); $this->table( ['Artwork ID', 'Legacy user_id', 'New user_id', 'Status', 'Title'], array_map(static fn (array $row): array => [ $row['artwork_id'], $row['legacy_user_id'], $row['new_user_id'], $row['status'], $row['title'], ], $discrepancies), ); } else { $this->info('No user_id mismatches were found.'); } return ($summary['mismatched'] === 0 && $summary['missing_in_new'] === 0) ? self::SUCCESS : self::FAILURE; } private function tableExists(?string $connection, string $table): bool { try { return $connection === null ? DB::getSchemaBuilder()->hasTable($table) : DB::connection($connection)->getSchemaBuilder()->hasTable($table); } catch (\Throwable) { return false; } } private function columnExists(?string $connection, string $table, string $column): bool { try { return $connection === null ? DB::getSchemaBuilder()->hasColumn($table, $column) : DB::connection($connection)->getSchemaBuilder()->hasColumn($table, $column); } catch (\Throwable) { return false; } } private function normalizeNullableInt(mixed $value): ?int { if ($value === null || $value === '') { return null; } return (int) $value; } /** * @param array $discrepancies */ private function rememberDiscrepancy( array &$discrepancies, int $show, int $artworkId, ?int $legacyUserId, ?int $newUserId, ?string $title, string $status, ): void { if (count($discrepancies) >= $show) { return; } $discrepancies[] = [ 'artwork_id' => $artworkId, 'legacy_user_id' => $legacyUserId === null ? '[null]' : (string) $legacyUserId, 'new_user_id' => $newUserId === null ? '[missing]' : (string) $newUserId, 'title' => $title ?? '', 'status' => $status, ]; } }