['_mirror_afs_order_time', 'id'], 'idx_afs_shop_order_time_id' => ['undefined8', '_mirror_afs_order_time', 'id'], ]; public function ensure(ConnectionInterface $db): void { if (! in_array($db->getDriverName(), ['mysql', 'mariadb'], true)) { return; } $schema = Schema::connection($db->getName()); if (! $schema->hasTable(self::TABLE) || ! $schema->hasColumns(self::TABLE, ['id', 'undefined7', 'undefined8'])) { return; } $statements = []; if (! $schema->hasColumn(self::TABLE, self::DATE_COLUMN)) { $expression = $this->dateExpression(); // MariaDB requires the virtual column and its indexes in separate // ALTER statements to allow concurrent writes with LOCK=NONE. $statements[] = 'ADD COLUMN `' . self::DATE_COLUMN . '` VARCHAR(19) CHARACTER SET ascii COLLATE ascii_bin GENERATED ALWAYS AS (' . $expression . ') VIRTUAL'; } $indexes = []; foreach (self::INDEXES as $name => $columns) { if (! $schema->hasIndex(self::TABLE, $name)) { $indexes[] = 'ADD INDEX `' . $name . '` (`' . implode('`, `', $columns) . '`)'; } } if ($indexes !== []) $statements[] = implode(', ', $indexes); if ($statements !== []) { // Fail rather than silently copying/locking this multi-million-row table. $wait = (int) $db->selectOne('SELECT @@SESSION.lock_wait_timeout AS seconds')->seconds; $db->statement('SET SESSION lock_wait_timeout = 5'); try { foreach ($statements as $statement) { $db->statement('ALTER TABLE `' . self::TABLE . '` ' . $statement . ', ALGORITHM=INPLACE, LOCK=NONE'); } } finally { $db->statement('SET SESSION lock_wait_timeout = ' . $wait); } } } public function dateExpression(): string { $raw = "REPLACE(TRIM(`undefined7`), '/', '-')"; $pattern = '^[0-9]{1,4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])' . '([[:space:]]+([01]?[0-9]|2[0-3])(:[0-5]?[0-9]){0,2}([.][0-9]+)?)?$'; // Guard malformed strings so a future bad source value cannot break an // indexed-column update in strict mode. Fractional seconds were already // ignored by the original format. CHAR preserves its calendar semantics // (e.g. 2026-02-31), unlike coercing the result into a DATETIME column. return "CASE WHEN {$raw} REGEXP '{$pattern}' THEN CAST(STR_TO_DATE(" . "SUBSTRING_INDEX({$raw}, '.', 1), '%Y-%m-%d %H:%i:%s') AS CHAR(19)) ELSE NULL END"; } }