getMessage() . "\n"); } $con = Database::get('main'); /** * --verify=<قيمة>: يبحث عن رقم/بريد عبر الفهرس ويطبع النتيجة، للتأكد من أن * البحث يعمل فعلاً بعد التعبئة دون فتح واجهة أو قاعدة بيانات. * لا يطبع أي بيانات حساسة كاملة. */ if (isset($options['verify'])) { $needle = (string) $options['verify']; if ($needle === '') exit("Usage: --verify=0791234567\n"); $con = Database::get('main'); $found = false; foreach ([['driver', 'driver.phone', 'phone_bidx'], ['driver', 'driver.email', 'email_bidx'], ['passengers', 'passengers.phone', 'phone_bidx'], ['passengers', 'passengers.email', 'email_bidx']] as [$table, $scope, $column]) { $idx = $blind->index($scope, $needle); if (!$idx) continue; $st = $con->prepare("SELECT id FROM `$table` WHERE `$column` = ? LIMIT 5"); $st->execute([$idx]); $ids = $st->fetchAll(PDO::FETCH_COLUMN); if ($ids) { $found = true; echo "✔ $table via $column → " . count($ids) . " match(es): " . implode(', ', $ids) . "\n"; } } if (!$found) { echo "✘ no match for that value in any indexed column.\n"; echo " Check that the value is correct and that the backfill has run.\n"; } exit; } $targets = [ 'driver' => [ 'phone_bidx' => ['scope' => 'driver.phone', 'columns' => ['phone']], 'email_bidx' => ['scope' => 'driver.email', 'columns' => ['email']], 'name_bidx' => ['scope' => 'driver.name', 'columns' => ['first_name', 'last_name']], ], 'passengers' => [ 'phone_bidx' => ['scope' => 'passengers.phone', 'columns' => ['phone']], 'email_bidx' => ['scope' => 'passengers.email', 'columns' => ['email']], 'name_bidx' => ['scope' => 'passengers.name', 'columns' => ['first_name', 'last_name']], ], 'adminUser' => [ 'phone_bidx' => ['scope' => 'adminUser.phone', 'columns' => ['phone']], 'email_bidx' => ['scope' => 'adminUser.email', 'columns' => ['email']], ], ]; echo $dryRun ? "── DRY RUN — nothing will be written ──\n" : "── Backfilling blind indexes ──\n"; foreach ($targets as $table => $fields) { if ($only && $only !== $table) continue; echo "\n[$table]\n"; $sourceColumns = []; foreach ($fields as $spec) { foreach ($spec['columns'] as $c) $sourceColumns[$c] = true; } $select = 'id, ' . implode(', ', array_keys($sourceColumns)); $where = $force ? '' : ' WHERE ' . implode(' OR ', array_map( fn($f) => "`$f` IS NULL", array_keys($fields) )); try { $rows = $con->query("SELECT $select FROM `$table`$where")->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { echo " ✘ skipped: " . $e->getMessage() . "\n"; continue; } $total = count($rows); echo " rows to process: $total\n"; if ($total === 0) continue; $updated = 0; $failed = 0; foreach (array_chunk($rows, $batch) as $chunk) { if (!$dryRun) $con->beginTransaction(); foreach ($chunk as $row) { $set = []; $params = [':id' => $row['id']]; foreach ($fields as $column => $spec) { // فك التشفير لقراءة القيمة الأصلية — القيمة المخزَّنة لا تتغير. $parts = []; foreach ($spec['columns'] as $src) { $plain = $encryptionHelper->decryptData($row[$src] ?? null); if ($plain === false) { $failed++; $parts = []; break; } if ($plain !== '') $parts[] = $plain; } if (!$parts) continue; $value = implode(' ', $parts); $index = $blind->index($spec['scope'], $value); if ($index === null) continue; $set[] = "`$column` = :$column"; $params[":$column"] = $index; } if (!$set) continue; if ($dryRun) { $updated++; continue; } $stmt = $con->prepare("UPDATE `$table` SET " . implode(', ', $set) . " WHERE id = :id"); $stmt->execute($params); $updated++; } if (!$dryRun) $con->commit(); usleep(50_000); // نفس متعمّد حتى لا تُحتكر القاعدة أثناء الخدمة echo " … $updated/$total\n"; } echo " ✔ indexed: $updated" . ($failed ? " (undecryptable values skipped: $failed)" : '') . "\n"; } echo "\nDone." . ($dryRun ? " (dry run — re-run without --dry-run to apply)" : '') . "\n";