PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ] ); $stage = 'create_database'; try { $admin->exec("CREATE DATABASE `{$databaseName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); } catch (PDOException $exception) { if ($configuredDatabase === '' || !in_array((string) $exception->getCode(), ['42000', '1044'], true)) { throw $exception; } $temporaryTables = true; $databaseName = $configuredDatabase; } $stage = 'test'; $pdo = new PDO( sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4', $hostname, $port, $databaseName), $username, $password, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ] ); $tableKind = $temporaryTables ? 'CREATE TEMPORARY TABLE' : 'CREATE TABLE'; $pdo->exec("{$tableKind} projection_catalog ( medicine_code varchar(32) NOT NULL PRIMARY KEY, local_medicine_id bigint unsigned NOT NULL, name varchar(120) NOT NULL ) ENGINE=InnoDB"); $pdo->exec("{$tableKind} projection_mapping ( local_medicine_id bigint unsigned NOT NULL PRIMARY KEY, medicine_code varchar(32) NOT NULL UNIQUE, operator_id bigint unsigned NOT NULL, operator_name varchar(80) NOT NULL ) ENGINE=InnoDB"); foreach (['submissions', 'callbacks', 'business_links'] as $table) { $pdo->exec("{$tableKind} `{$table}` (id bigint unsigned NOT NULL PRIMARY KEY) ENGINE=InnoDB"); } $pdo->exec("INSERT INTO projection_catalog VALUES ('OLD001', 999, '旧投影')"); $pdo->exec("INSERT INTO projection_mapping VALUES (999, 'OLD001', 8, 'old-operator')"); $transaction = static function (callable $operation) use ($pdo): array { $pdo->beginTransaction(); try { $result = $operation(); $pdo->commit(); return $result; } catch (Throwable $exception) { if ($pdo->inTransaction()) { $pdo->rollBack(); } throw $exception; } }; $referenceCounter = static function () use ($pdo): array { return [ 'submissions' => (int) $pdo->query('SELECT COUNT(*) FROM submissions')->fetchColumn(), 'callbacks' => (int) $pdo->query('SELECT COUNT(*) FROM callbacks')->fetchColumn(), 'business_links' => (int) $pdo->query('SELECT COUNT(*) FROM business_links')->fetchColumn(), ]; }; $referenceLocker = static function () use ($pdo): void { foreach (['submissions', 'callbacks', 'business_links'] as $table) { $pdo->query("SELECT id FROM `{$table}` ORDER BY id FOR UPDATE")->fetchAll(); } }; $locker = static function () use ($pdo): void { $pdo->query('SELECT medicine_code FROM projection_catalog ORDER BY medicine_code FOR UPDATE')->fetchAll(); $pdo->query('SELECT local_medicine_id FROM projection_mapping ORDER BY local_medicine_id FOR UPDATE')->fetchAll(); }; $verifier = static function () use ($pdo): array { return [ 'catalog' => (int) $pdo->query('SELECT COUNT(*) FROM projection_catalog')->fetchColumn(), 'active_mappings' => (int) $pdo->query('SELECT COUNT(*) FROM projection_mapping')->fetchColumn(), 'unmapped' => (int) $pdo->query( 'SELECT COUNT(*) FROM projection_catalog c LEFT JOIN projection_mapping m ' . 'ON m.medicine_code = c.medicine_code WHERE m.local_medicine_id IS NULL' )->fetchColumn(), ]; }; $rows = [ ['local_medicine_id' => 1, 'medicine_code' => 'EJ000001', 'name' => '黄芪'], ['local_medicine_id' => 2, 'medicine_code' => 'EJ000002', 'name' => '党参'], ]; $replacer = static function (array $nextRows) use ($pdo): void { $pdo->exec('DELETE FROM projection_mapping'); $pdo->exec('DELETE FROM projection_catalog'); $catalog = $pdo->prepare( 'INSERT INTO projection_catalog (medicine_code,local_medicine_id,name) VALUES (?,?,?)' ); $mapping = $pdo->prepare( 'INSERT INTO projection_mapping (local_medicine_id,medicine_code,operator_id,operator_name) VALUES (?,?,0,?)' ); foreach ($nextRows as $row) { $catalog->execute([$row['medicine_code'], $row['local_medicine_id'], $row['name']]); $mapping->execute([$row['local_medicine_id'], $row['medicine_code'], 'system-bootstrap']); } }; $pdo->exec('INSERT INTO submissions VALUES (1)'); try { EjMedicineBootstrapService::replaceProjectionWith( $rows, $transaction, $referenceLocker, $referenceCounter, $locker, $replacer, $verifier ); throw new RuntimeException('nonzero MySQL reference gate unexpectedly allowed replacement'); } catch (RuntimeException $exception) { if (!str_contains($exception->getMessage(), '业务引用')) { throw $exception; } } $pdo->exec('DELETE FROM submissions'); if ((string) $pdo->query('SELECT medicine_code FROM projection_catalog')->fetchColumn() !== 'OLD001') { throw new RuntimeException('nonzero MySQL reference gate mutated the old projection'); } $result = EjMedicineBootstrapService::replaceProjectionWith( $rows, $transaction, $referenceLocker, $referenceCounter, $locker, $replacer, $verifier ); if ($result !== ['catalog' => 2, 'active_mappings' => 2, 'unmapped' => 0]) { throw new RuntimeException('successful MySQL projection replacement did not verify exactly'); } $operators = $pdo->query( 'SELECT CONCAT(operator_id, ":", operator_name) FROM projection_mapping ORDER BY local_medicine_id' )->fetchAll(PDO::FETCH_COLUMN); if ($operators !== ['0:system-bootstrap', '0:system-bootstrap']) { throw new RuntimeException('bootstrap mappings did not preserve the system operator identity'); } $pdo->exec('DELETE FROM projection_mapping'); $pdo->exec('DELETE FROM projection_catalog'); $pdo->exec("INSERT INTO projection_catalog VALUES ('OLD002', 998, '回滚旧投影')"); $pdo->exec("INSERT INTO projection_mapping VALUES (998, 'OLD002', 7, 'rollback-operator')"); try { EjMedicineBootstrapService::replaceProjectionWith( $rows, $transaction, $referenceLocker, $referenceCounter, $locker, static function (array $nextRows) use ($replacer): void { $replacer($nextRows); throw new RuntimeException('forced MySQL replacement failure'); }, $verifier ); throw new RuntimeException('forced MySQL replacement failure unexpectedly committed'); } catch (RuntimeException $exception) { if ($exception->getMessage() !== 'forced MySQL replacement failure') { throw $exception; } } $rolledBack = $pdo->query( 'SELECT c.medicine_code,c.local_medicine_id,c.name,m.operator_id,m.operator_name ' . 'FROM projection_catalog c JOIN projection_mapping m USING (medicine_code)' )->fetch(); if ($rolledBack !== [ 'medicine_code' => 'OLD002', 'local_medicine_id' => 998, 'name' => '回滚旧投影', 'operator_id' => 7, 'operator_name' => 'rollback-operator', ]) { throw new RuntimeException('MySQL rollback did not restore the old projection exactly'); } fwrite(STDOUT, $temporaryTables ? "medicine bootstrap MySQL integration passed: temporary_tables\n" : "medicine bootstrap MySQL integration passed: temporary_database\n"); } catch (PDOException $exception) { if ($stage === 'test') { throw $exception; } fwrite(STDOUT, sprintf( "medicine bootstrap MySQL integration skipped: temporary database unavailable stage=%s sqlstate=%s\n", $stage, (string) $exception->getCode() )); exit(0); } finally { $pdo = null; if ($admin instanceof PDO && !$temporaryTables) { try { $admin->exec("DROP DATABASE IF EXISTS `{$databaseName}`"); } catch (Throwable) { } } }