feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل
قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ. الخريطة: backend · payment_server · loction_server · ride_server · passenger_server · docker · dashboard · stress_test → الجذر siro_rider → apps/rider siro_driver → apps/driver siro_admin → dashboards/admin siro_service → dashboards/service android_bot → apps/android_bot socialBot → apps/socialBot نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب) لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً: كل ما يلي يصير فرقاً مقروءاً مقابل المصدر. لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز، سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh (ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و dashboards/transit-web). ⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة: 1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر): كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner. 2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist) يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً. 3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع → يجب ضمّ الحزم داخله أسوة بـ apps/rider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9909d9b4c1
commit
4d8414c96b
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
/**
|
||||
* scripts/backfill_blind_index.php
|
||||
*
|
||||
* يملأ أعمدة الفهرس الأعمى للسجلات الموجودة.
|
||||
*
|
||||
* الاستخدام:
|
||||
* php backfill_blind_index.php # تشغيل فعلي
|
||||
* php backfill_blind_index.php --dry-run # عرض ما سيحدث دون كتابة
|
||||
* php backfill_blind_index.php --table=driver --batch=200
|
||||
*
|
||||
* آمن للتشغيل والخدمة تعمل:
|
||||
* - لا يقرأ ولا يكتب أي عمود مشفّر، يكتب أعمدة الفهرس فقط.
|
||||
* - يعمل على دفعات مع فاصل قصير حتى لا يضغط القاعدة.
|
||||
* - قابل لإعادة التشغيل: يتخطى الصفوف المملوءة ما لم يُمرَّر --force.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit("This script runs from the command line only.\n");
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
require_once __DIR__ . '/../core/Security/BlindIndex.php';
|
||||
|
||||
$options = getopt('', ['dry-run', 'force', 'audit', 'verify::', 'table::', 'batch::']);
|
||||
$dryRun = isset($options['dry-run']);
|
||||
$force = isset($options['force']);
|
||||
$only = $options['table'] ?? null;
|
||||
$batch = max(50, (int) ($options['batch'] ?? 500));
|
||||
|
||||
/** @var EncryptionHelper $encryptionHelper */
|
||||
global $encryptionHelper;
|
||||
|
||||
try {
|
||||
$blind = new BlindIndex();
|
||||
} catch (RuntimeException $e) {
|
||||
exit("✘ " . $e->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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* --audit: يقارن كل فهرس مخزَّن بالفهرس المحسوب من القيمة المشفّرة.
|
||||
*
|
||||
* الهدف كشف "الانحراف": صف عُدِّل هاتفه أو بريده عبر مسار كتابة لا يُحدّث
|
||||
* الفهرس، فيصبح البحث يجد السجل بقيمته القديمة أو لا يجده إطلاقاً. الانحراف
|
||||
* صامت بطبيعته، ولن يظهر إلا حين يفشل بحث حقيقي.
|
||||
*/
|
||||
if (isset($options['audit'])) {
|
||||
$con = Database::get('main');
|
||||
$checks = [
|
||||
['driver', 'driver.phone', 'phone', 'phone_bidx'],
|
||||
['driver', 'driver.email', 'email', 'email_bidx'],
|
||||
['passengers', 'passengers.phone', 'phone', 'phone_bidx'],
|
||||
['passengers', 'passengers.email', 'email', 'email_bidx'],
|
||||
['adminUser', 'adminUser.phone', 'phone', 'phone_bidx'],
|
||||
];
|
||||
|
||||
$problems = 0;
|
||||
foreach ($checks as [$table, $scope, $source, $column]) {
|
||||
try {
|
||||
$rows = $con->query("SELECT id, `$source`, `$column` FROM `$table`")->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
echo " ! $table skipped: " . $e->getMessage() . "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$stale = $missing = 0;
|
||||
foreach ($rows as $row) {
|
||||
$plain = $encryptionHelper->decryptData($row[$source] ?? null);
|
||||
if ($plain === false || $plain === '') continue;
|
||||
|
||||
$expected = $blind->index($scope, $plain);
|
||||
if ($expected === null) continue;
|
||||
|
||||
if (empty($row[$column])) $missing++;
|
||||
elseif ($row[$column] !== $expected) $stale++;
|
||||
}
|
||||
|
||||
$problems += $stale + $missing;
|
||||
$verdict = ($stale + $missing) === 0 ? 'ok' : "MISSING=$missing STALE=$stale";
|
||||
echo sprintf(" %-24s %s\n", "$table.$column", $verdict);
|
||||
}
|
||||
|
||||
echo $problems === 0
|
||||
? "\n✔ every index matches its encrypted value.\n"
|
||||
: "\n✘ $problems row(s) out of sync — re-run with --force to rebuild.\n";
|
||||
exit($problems === 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
$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']],
|
||||
'national_bidx' => ['scope' => 'driver.national', 'columns' => ['national_number']],
|
||||
// نفس نطاق otpPhoneKey: يسمح بربط جداول التحقق بصاحب الحساب
|
||||
'phone_key' => ['scope' => 'otp.phone', 'columns' => ['phone'], 'prefix' => 'K:'],
|
||||
],
|
||||
'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']],
|
||||
'phone_key' => ['scope' => 'otp.phone', 'columns' => ['phone'], 'prefix' => 'K:'],
|
||||
],
|
||||
'adminUser' => [
|
||||
'phone_bidx' => ['scope' => 'adminUser.phone', 'columns' => ['phone']],
|
||||
'email_bidx' => ['scope' => 'adminUser.email', 'columns' => ['email']],
|
||||
],
|
||||
'users' => [
|
||||
'phone_bidx' => ['scope' => 'users.phone', 'columns' => ['phone']],
|
||||
'email_bidx' => ['scope' => 'users.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;
|
||||
if (!empty($spec['prefix'])) $index = $spec['prefix'] . $index;
|
||||
|
||||
$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";
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* scripts/migrate.php
|
||||
*
|
||||
* مُشغّل ترحيلات بسيط يعمل من سطر الأوامر داخل حاوية php.
|
||||
* يستخدم نفس إعدادات الاتصال في core/Database — لا تحتاج phpMyAdmin ولا
|
||||
* تمرير كلمة مرور القاعدة يدوياً.
|
||||
*
|
||||
* الاستخدام:
|
||||
* php scripts/migrate.php --status # ماذا سيُطبَّق وماذا هو مطبَّق
|
||||
* php scripts/migrate.php --dry-run # عرض العبارات دون تنفيذ
|
||||
* php scripts/migrate.php # التطبيق
|
||||
*
|
||||
* كل خطوة idempotent: تُفحص حالة المخطط قبل التنفيذ، فإعادة التشغيل آمنة
|
||||
* ولا تُنتج خطأ "Duplicate column".
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit("This script runs from the command line only.\n");
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
|
||||
$options = getopt('', ['dry-run', 'status']);
|
||||
$dryRun = isset($options['dry-run']);
|
||||
$status = isset($options['status']);
|
||||
|
||||
$con = Database::get('main');
|
||||
|
||||
/** هل يوجد العمود؟ */
|
||||
function hasColumn(PDO $con, string $table, string $column): bool
|
||||
{
|
||||
$st = $con->prepare(
|
||||
"SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?"
|
||||
);
|
||||
$st->execute([$table, $column]);
|
||||
return (int) $st->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
/** هل يوجد الفهرس؟ */
|
||||
function hasIndex(PDO $con, string $table, string $index): bool
|
||||
{
|
||||
$st = $con->prepare(
|
||||
"SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ?"
|
||||
);
|
||||
$st->execute([$table, $index]);
|
||||
return (int) $st->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
function tableExists(PDO $con, string $table): bool
|
||||
{
|
||||
$st = $con->prepare(
|
||||
"SELECT COUNT(*) FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?"
|
||||
);
|
||||
$st->execute([$table]);
|
||||
return (int) $st->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
// ── تعريف الترحيلات ──────────────────────────────────────────
|
||||
// كلها إضافية: أعمدة جديدة تقبل NULL. لا تُعدَّل ولا تُحذف بيانات قائمة،
|
||||
// ولا يقرأها أي كود قبل تعبئتها، فتطبيقها على قاعدة تعمل بلا أثر.
|
||||
$columns = [
|
||||
['driver', 'phone_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للبحث بالهاتف'"],
|
||||
['driver', 'email_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للبحث بالبريد'"],
|
||||
['driver', 'name_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للاسم بعد التطبيع'"],
|
||||
['passengers', 'phone_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للبحث بالهاتف'"],
|
||||
['passengers', 'email_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للبحث بالبريد'"],
|
||||
['passengers', 'name_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للاسم بعد التطبيع'"],
|
||||
['adminUser', 'phone_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للبحث بالهاتف'"],
|
||||
['adminUser', 'email_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للبحث بالبريد'"],
|
||||
// نسخة مشفّرة قابلة للاسترجاع من رقم الهاتف في جداول التحقق.
|
||||
// عمود phone_number صار يحمل مفتاح بحث أحادي الاتجاه (HMAC)، فلا يمكن
|
||||
// استرجاع الرقم منه — وخدمة العملاء تحتاجه لمتابعة من طلب رمزاً ولم
|
||||
// يُكمل تسجيله.
|
||||
['phone_verification', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"],
|
||||
['phone_verification_passenger', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"],
|
||||
['phone_verification_service', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"],
|
||||
['token_verification', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"],
|
||||
['token_verification_driver', 'phone_enc', "TEXT NULL DEFAULT NULL COMMENT 'الهاتف مشفّراً للاسترجاع'"],
|
||||
|
||||
// مفتاح ربط جداول التحقق: نفس صيغة otpPhoneKey، حتى يمكن ربط
|
||||
// phone_verification*.phone_number بصاحب الحساب دون مقارنة نصوص مشفّرة.
|
||||
['driver', 'phone_key', "VARCHAR(80) NULL DEFAULT NULL COMMENT 'مفتاح ربط جداول التحقق'"],
|
||||
['passengers', 'phone_key', "VARCHAR(80) NULL DEFAULT NULL COMMENT 'مفتاح ربط جداول التحقق'"],
|
||||
|
||||
// جداول ملاحظات خدمة العملاء تُربط بالسائق/الراكب عبر الهاتف المشفّر،
|
||||
// وهو ربط يفشل لحظة أن يصبح التشفير عشوائياً.
|
||||
['notesForDriverService', 'phone_key', "VARCHAR(80) NULL DEFAULT NULL COMMENT 'مفتاح ربط بالحساب'"],
|
||||
['notesForPassengerService', 'phone_key', "VARCHAR(80) NULL DEFAULT NULL COMMENT 'مفتاح ربط بالحساب'"],
|
||||
|
||||
// بقية الجداول التي يُبحث فيها بحقل مشفّر
|
||||
['users', 'email_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC لبريد موظف الخدمة'"],
|
||||
['users', 'phone_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC لهاتف موظف الخدمة'"],
|
||||
['driver', 'national_bidx', "CHAR(64) NULL DEFAULT NULL COMMENT 'HMAC للرقم الوطني'"],
|
||||
|
||||
// أعمدة موافقات المشرفين المفقودة في هذا النشر
|
||||
['adminUser', 'status', "VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT 'active | pending | suspended | rejected'"],
|
||||
['adminUser', 'approved_by', "VARCHAR(32) NULL DEFAULT NULL"],
|
||||
['adminUser', 'approved_at', "TIMESTAMP NULL DEFAULT NULL"],
|
||||
];
|
||||
|
||||
$indexes = [
|
||||
['driver', 'idx_driver_phone_bidx', 'phone_bidx'],
|
||||
['driver', 'idx_driver_email_bidx', 'email_bidx'],
|
||||
['driver', 'idx_driver_name_bidx', 'name_bidx'],
|
||||
['passengers', 'idx_passengers_phone_bidx', 'phone_bidx'],
|
||||
['passengers', 'idx_passengers_email_bidx', 'email_bidx'],
|
||||
['passengers', 'idx_passengers_name_bidx', 'name_bidx'],
|
||||
['adminUser', 'idx_adminuser_phone_bidx', 'phone_bidx'],
|
||||
['adminUser', 'idx_adminuser_email_bidx', 'email_bidx'],
|
||||
['users', 'idx_users_email_bidx', 'email_bidx'],
|
||||
['users', 'idx_users_phone_bidx', 'phone_bidx'],
|
||||
['driver', 'idx_driver_national_bidx', 'national_bidx'],
|
||||
['driver', 'idx_driver_phone_key', 'phone_key'],
|
||||
['passengers', 'idx_passengers_phone_key', 'phone_key'],
|
||||
['notesForDriverService', 'idx_notes_driver_phone_key', 'phone_key'],
|
||||
['notesForPassengerService', 'idx_notes_passenger_phone_key', 'phone_key'],
|
||||
];
|
||||
|
||||
$applied = 0;
|
||||
$skipped = 0;
|
||||
$missing = [];
|
||||
|
||||
echo "── Siro schema migration ──\n";
|
||||
echo "database: " . $con->query('SELECT DATABASE()')->fetchColumn() . "\n";
|
||||
if ($dryRun) echo "mode: DRY RUN (nothing will be executed)\n";
|
||||
if ($status) echo "mode: STATUS ONLY\n";
|
||||
echo "\n";
|
||||
|
||||
foreach ($columns as [$table, $column, $definition]) {
|
||||
if (!tableExists($con, $table)) {
|
||||
$missing[$table] = true;
|
||||
continue;
|
||||
}
|
||||
if (hasColumn($con, $table, $column)) {
|
||||
echo " = $table.$column already present\n";
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$sql = "ALTER TABLE `$table` ADD COLUMN `$column` $definition";
|
||||
if ($status || $dryRun) {
|
||||
echo " + would run: $sql\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->exec($sql);
|
||||
echo " + $table.$column added\n";
|
||||
$applied++;
|
||||
} catch (PDOException $e) {
|
||||
echo " ✘ $table.$column failed: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($indexes as [$table, $index, $column]) {
|
||||
if (!tableExists($con, $table) || !hasColumn($con, $table, $column)) continue;
|
||||
if (hasIndex($con, $table, $index)) {
|
||||
echo " = $table.$index already present\n";
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$sql = "ALTER TABLE `$table` ADD INDEX `$index` (`$column`)";
|
||||
if ($status || $dryRun) {
|
||||
echo " + would run: $sql\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$con->exec($sql);
|
||||
echo " + $table.$index created\n";
|
||||
$applied++;
|
||||
} catch (PDOException $e) {
|
||||
echo " ✘ $table.$index failed: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_keys($missing) as $table) {
|
||||
echo " ! table `$table` does not exist in this database — skipped\n";
|
||||
}
|
||||
|
||||
echo "\napplied: $applied · already in place: $skipped\n";
|
||||
|
||||
if (!$status && !$dryRun && $applied > 0) {
|
||||
echo "\nNext: populate the indexes\n";
|
||||
echo " php scripts/backfill_blind_index.php --dry-run\n";
|
||||
echo " php scripts/backfill_blind_index.php\n";
|
||||
}
|
||||
Reference in New Issue
Block a user