public static function normalizeDSN($dsn, $user = null, $pass = null)
    {
        // Try to dissect DSN into parts
        $parts = is_array($dsn) ? $dsn : parse_url($dsn);

        // If parts are usable, convert DSN format
        if ($parts !== false && isset($parts['host'], $parts['path'])) {
            // DSN is using URL-like format, so we need to convert it
            $dsn =
                $parts['scheme'].
                ':host='.$parts['host'].
                (isset($parts['port']) ? ';port='.$parts['port'] : '').
                ';dbname='.substr($parts['path'], 1);
            $user = $user !== null ? $user : (isset($parts['user']) ? $parts['user'] : null);
            $pass = $pass !== null ? $pass : (isset($parts['pass']) ? $parts['pass'] : null);
        }

        // If it's still array, then simply use it
        if (is_array($dsn)) {
            return $dsn;
        }

        // If it's string, then find driver
        if (is_string($dsn)) {
            if (strpos($dsn, ':') === false) {
                throw new Exception([
                    "Your DSN format is invalid. Must be in 'driver:host;options' format",
                    'dsn' => $dsn,
                ]);
            }
            list($driver, $rest) = explode(':', $dsn, 2);
            $driver = strtolower($driver);
        } else {
            // currently impossible to be like this, but we don't want ugly exceptions here
            $driver = $rest = null;
        }

        return ['dsn' => $dsn, 'user' => $user, 'pass' => $pass, 'driver' => $driver, 'rest' => $rest];
    }