'/^Deadlock found\b/i', 1205 => '/^Lock wait timeout exceeded\b/i', 1020 => '/^Record has changed since last read\b/i', 1062 => '/^Duplicate entry\b/i', 1193 => '/^Unknown system variable\b/i', 1146 => '/^Table .+ doesn\'t exist/i', 1040 => '/^Too many connections\b/i', 2006 => '/\bserver has gone away\b/i', 2013 => '/^Lost connection to (?:MySQL )?server\b/i', 1064 => '/^You have an error in your SQL syntax\b/i', 1054 => '/^Unknown column\b/i', 1412 => '/^Table definition has changed\b/i', ); public static function errno_of( string $message ): int { foreach ( self::MESSAGES as $errno => $pattern ) { if ( 1 === preg_match( $pattern, $message ) ) { return $errno; } } return 0; } public function __construct( public readonly string $detail, public readonly int $errno = 0, public readonly string $table = '', public readonly string $verb = '' ) { parent::__construct( 'Database error', $errno ); } public static function sanitize_detail( string $message ): string { $clean = (string) preg_replace( '/\'(?:[^\'\\\\]|\\\\.)*\'|"(?:[^"\\\\]|\\\\.)*"|`[^`]*`/s', '?', $message ); return mb_substr( $clean, 0, 200 ); } public function is_retryable(): bool { return in_array( $this->errno, self::RETRYABLE, true ); } }