]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#2688] Added read-timeout and write-timeout
authorMarcin Siodelski <marcin@isc.org>
Wed, 11 Jan 2023 21:28:12 +0000 (22:28 +0100)
committerAndrei Pavel <andrei@isc.org>
Thu, 13 Jul 2023 10:27:55 +0000 (13:27 +0300)
New parameters have been added for MySQL connection.

src/lib/database/dbaccess_parser.cc
src/lib/database/tests/dbaccess_parser_unittest.cc
src/lib/database/testutils/schema.cc
src/lib/database/testutils/schema.h
src/lib/mysql/mysql_connection.cc
src/lib/mysql/mysql_connection.h
src/lib/mysql/tests/mysql_connection_unittest.cc

index caee2362822ce8bfa82e1a596faa1d83d0003d86..e3d14b485f045f22a977f2f47545ff6d5e5dcdd3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2023 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -48,7 +48,9 @@ DbAccessParser::parse(std::string& access_string,
     DatabaseConnection::ParameterMap values_copy = values_;
 
     int64_t lfc_interval = 0;
-    int64_t timeout = 0;
+    int64_t connect_timeout = 0;
+    int64_t read_timeout = 0;
+    int64_t write_timeout = 0;
     int64_t port = 0;
     int64_t max_reconnect_tries = 0;
     int64_t reconnect_wait_time = 0;
@@ -68,9 +70,19 @@ DbAccessParser::parse(std::string& access_string,
                     boost::lexical_cast<std::string>(lfc_interval);
 
             } else if (param.first == "connect-timeout") {
-                timeout = param.second->intValue();
+                connect_timeout = param.second->intValue();
                 values_copy[param.first] =
-                    boost::lexical_cast<std::string>(timeout);
+                    boost::lexical_cast<std::string>(connect_timeout);
+
+            } else if (param.first == "read-timeout") {
+                read_timeout = param.second->intValue();
+                values_copy[param.first] =
+                    boost::lexical_cast<std::string>(read_timeout);
+
+            } else if (param.first == "write-timeout") {
+                write_timeout = param.second->intValue();
+                values_copy[param.first] =
+                    boost::lexical_cast<std::string>(write_timeout);
 
             } else if (param.first == "max-reconnect-tries") {
                 max_reconnect_tries = param.second->intValue();
@@ -148,11 +160,27 @@ DbAccessParser::parse(std::string& access_string,
                   << " (" << value->getPosition() << ")");
     }
 
-    // d. Check that the timeout is within a reasonable range.
-    if ((timeout < 0) ||
-        (timeout > std::numeric_limits<uint32_t>::max())) {
+    // d. Check that the timeouts are within a reasonable range.
+    if ((connect_timeout < 0) ||
+        (connect_timeout > std::numeric_limits<uint32_t>::max())) {
         ConstElementPtr value = database_config->get("connect-timeout");
-        isc_throw(DbConfigError, "connect-timeout value: " << timeout
+        isc_throw(DbConfigError, "connect-timeout value: " << connect_timeout
+                  << " is out of range, expected value: 0.."
+                  << std::numeric_limits<uint32_t>::max()
+                  << " (" << value->getPosition() << ")");
+    }
+    if ((read_timeout < 0) ||
+        (read_timeout > std::numeric_limits<uint32_t>::max())) {
+        ConstElementPtr value = database_config->get("read-timeout");
+        isc_throw(DbConfigError, "read-timeout value: " << read_timeout
+                  << " is out of range, expected value: 0.."
+                  << std::numeric_limits<uint32_t>::max()
+                  << " (" << value->getPosition() << ")");
+    }
+    if ((write_timeout < 0) ||
+        (write_timeout > std::numeric_limits<uint32_t>::max())) {
+        ConstElementPtr value = database_config->get("write-timeout");
+        isc_throw(DbConfigError, "write-timeout value: " << write_timeout
                   << " is out of range, expected value: 0.."
                   << std::numeric_limits<uint32_t>::max()
                   << " (" << value->getPosition() << ")");
index dca15890f5f5ff596ed7f9d950a99778d9ce2dd1..b51b6f4b80da1ec8234169417742a5c14c02e838 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2020 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2023 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -167,6 +167,8 @@ private:
      bool quoteValue(const std::string& parameter) const {
          return ((parameter != "persist") && (parameter != "lfc-interval") &&
                  (parameter != "connect-timeout") &&
+                 (parameter != "read-timeout") &&
+                 (parameter != "write-timeout") &&
                  (parameter != "port") &&
                  (parameter != "max-row-errors") &&
                  (parameter != "readonly"));
@@ -354,8 +356,8 @@ TEST_F(DbAccessParserTest, largeLFCInterval) {
 }
 
 // This test checks that the parser accepts the valid value of the
-// timeout parameter.
-TEST_F(DbAccessParserTest, validTimeout) {
+// connect-timeout parameter.
+TEST_F(DbAccessParserTest, validConnectTimeout) {
     const char* config[] = {"type", "memfile",
                             "name", "/opt/var/lib/kea/kea-leases6.csv",
                             "connect-timeout", "3600",
@@ -372,8 +374,8 @@ TEST_F(DbAccessParserTest, validTimeout) {
 }
 
 // This test checks that the parser rejects the negative value of the
-// timeout parameter.
-TEST_F(DbAccessParserTest, negativeTimeout) {
+// connect-timeout parameter.
+TEST_F(DbAccessParserTest, negativeConnectTimeout) {
     const char* config[] = {"type", "memfile",
                             "name", "/opt/var/lib/kea/kea-leases6.csv",
                             "connect-timeout", "-1",
@@ -388,8 +390,8 @@ TEST_F(DbAccessParserTest, negativeTimeout) {
 }
 
 // This test checks that the parser rejects a too large (greater than
-// the max uint32_t) value of the timeout parameter.
-TEST_F(DbAccessParserTest, largeTimeout) {
+// the max uint32_t) value of the connecttimeout parameter.
+TEST_F(DbAccessParserTest, largeConnectTimeout) {
     const char* config[] = {"type", "memfile",
                             "name", "/opt/var/lib/kea/kea-leases6.csv",
                             "connect-timeout", "4294967296",
@@ -403,6 +405,106 @@ TEST_F(DbAccessParserTest, largeTimeout) {
     EXPECT_THROW(parser.parse(json_elements), DbConfigError);
 }
 
+// This test checks that the parser accepts the valid value of the
+// read-timeout parameter.
+TEST_F(DbAccessParserTest, validReadTimeout) {
+    const char* config[] = {"type", "memfile",
+                            "name", "/opt/var/lib/kea/kea-leases6.csv",
+                            "read-timeout", "3600",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    TestDbAccessParser parser;
+    EXPECT_NO_THROW(parser.parse(json_elements));
+    checkAccessString("Valid read timeout", parser.getDbAccessParameters(),
+                      config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// read-timeout parameter.
+TEST_F(DbAccessParserTest, negativeReadTimeout) {
+    const char* config[] = {"type", "memfile",
+                            "name", "/opt/var/lib/kea/kea-leases6.csv",
+                            "read-timeout", "-1",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    TestDbAccessParser parser;
+    EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint32_t) value of the read-timeout parameter.
+TEST_F(DbAccessParserTest, largeReadTimeout) {
+    const char* config[] = {"type", "memfile",
+                            "name", "/opt/var/lib/kea/kea-leases6.csv",
+                            "read-timeout", "4294967296",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    TestDbAccessParser parser;
+    EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser accepts the valid value of the
+// write-timeout parameter.
+TEST_F(DbAccessParserTest, validWriteTimeout) {
+    const char* config[] = {"type", "memfile",
+                            "name", "/opt/var/lib/kea/kea-leases6.csv",
+                            "write-timeout", "3600",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    TestDbAccessParser parser;
+    EXPECT_NO_THROW(parser.parse(json_elements));
+    checkAccessString("Valid write timeout", parser.getDbAccessParameters(),
+                      config);
+}
+
+// This test checks that the parser rejects the negative value of the
+// write-timeout parameter.
+TEST_F(DbAccessParserTest, negativeWriteTimeout) {
+    const char* config[] = {"type", "memfile",
+                            "name", "/opt/var/lib/kea/kea-leases6.csv",
+                            "write-timeout", "-1",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    TestDbAccessParser parser;
+    EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
+// This test checks that the parser rejects a too large (greater than
+// the max uint32_t) value of the write-timeout parameter.
+TEST_F(DbAccessParserTest, largeWriteTimeout) {
+    const char* config[] = {"type", "memfile",
+                            "name", "/opt/var/lib/kea/kea-leases6.csv",
+                            "write-timeout", "4294967296",
+                            NULL};
+
+    string json_config = toJson(config);
+    ConstElementPtr json_elements = Element::fromJSON(json_config);
+    EXPECT_TRUE(json_elements);
+
+    TestDbAccessParser parser;
+    EXPECT_THROW(parser.parse(json_elements), DbConfigError);
+}
+
 // This test checks that the parser accepts the valid value of the
 // port parameter.
 TEST_F(DbAccessParserTest, validPort) {
index d6aef69a3e46e01f17a2bf8b0b20e38a6ea6f370..a794de6e6f56769919b982626fa7dfeeef197134 100644 (file)
@@ -34,6 +34,13 @@ const char* INVALID_PASSWORD = "password=invalid";
 const char* VALID_TIMEOUT = "connect-timeout=10";
 const char* INVALID_TIMEOUT_1 = "connect-timeout=foo";
 const char* INVALID_TIMEOUT_2 = "connect-timeout=-17";
+const char* INVALID_TIMEOUT_3 = "connect-timeout=0";
+const char* VALID_READ_TIMEOUT = "read-timeout=11";
+const char* VALID_READ_TIMEOUT_ZERO = "read-timeout=0";
+const char* INVALID_READ_TIMEOUT_1 = "read-timeout=bar";
+const char* VALID_WRITE_TIMEOUT = "write-timeout=12";
+const char* VALID_WRITE_TIMEOUT_ZERO = "write-timeout=0";
+const char* INVALID_WRITE_TIMEOUT_1 = "write-timeout=baz";
 const char* VALID_READONLY_DB = "readonly=true";
 const char* INVALID_READONLY_DB = "readonly=5";
 const char* VALID_CERT = "cert-file=" TEST_CA_DIR "/kea-client.crt";
index 9e10148f379fe3409911aaff03bfb10c7a59c218..f278baa521b8afcde1dc35e6e266498bb6e48ebf 100644 (file)
@@ -30,6 +30,13 @@ extern const char* INVALID_PASSWORD;
 extern const char* VALID_TIMEOUT;
 extern const char* INVALID_TIMEOUT_1;
 extern const char* INVALID_TIMEOUT_2;
+extern const char* INVALID_TIMEOUT_3;
+extern const char* VALID_READ_TIMEOUT;
+extern const char* VALID_READ_TIMEOUT_ZERO;
+extern const char* INVALID_READ_TIMEOUT_1;
+extern const char* VALID_WRITE_TIMEOUT;
+extern const char* VALID_WRITE_TIMEOUT_ZERO;
+extern const char* INVALID_WRITE_TIMEOUT_1;
 extern const char* VALID_READONLY_DB;
 extern const char* INVALID_READONLY_DB;
 extern const char* VALID_CERT;
index fc65cce45b9061e6456c447559089e92e92b7138..aadb89bab705502ff97c5f13b674af73ab3a911f 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2023 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -65,31 +65,12 @@ MySqlConnection::openDatabase() {
     }
 
     unsigned int port = 0;
-    string sport;
-    try {
-        sport = getParameter("port");
-    } catch (...) {
-        // No port parameter, we are going to use the default port.
-        sport = "";
-    }
-
-    if (sport.size() > 0) {
-        // Port was given, so try to convert it to an integer.
-
-        try {
-            port = boost::lexical_cast<unsigned int>(sport);
-        } catch (...) {
-            // Port given but could not be converted to an unsigned int.
-            // Just fall back to the default value.
-            port = 0;
-        }
-
-        // The port is only valid when it is in the 0..65535 range.
-        // Again fall back to the default when the given value is invalid.
-        if (port > numeric_limits<uint16_t>::max()) {
-            port = 0;
-        }
-    }
+    setIntParameterValue("port", 0, numeric_limits<unsigned int>::max(), port);
+    // The port is only valid when it is in the 0..65535 range.
+    // Again fall back to the default when the given value is invalid.
+     if (port > numeric_limits<uint16_t>::max()) {
+         port = 0;
+     }
 
     const char* user = NULL;
     string suser;
@@ -120,40 +101,18 @@ MySqlConnection::openDatabase() {
     }
 
     unsigned int connect_timeout = MYSQL_DEFAULT_CONNECTION_TIMEOUT;
-    string stimeout;
+    unsigned int read_timeout = 0;
+    unsigned int write_timeout = 0;
     try {
-        stimeout = getParameter("connect-timeout");
-    } catch (...) {
-        // No timeout parameter, we are going to use the default timeout.
-        stimeout = "";
-    }
-
-    if (stimeout.size() > 0) {
-        // Timeout was given, so try to convert it to an integer.
-
-        try {
-            connect_timeout = boost::lexical_cast<unsigned int>(stimeout);
-        } catch (...) {
-            // Timeout given but could not be converted to an unsigned int. Set
-            // the connection timeout to an invalid value to trigger throwing
-            // of an exception.
-            connect_timeout = 0;
-        }
-
         // The timeout is only valid if greater than zero, as depending on the
         // database, a zero timeout might signify something like "wait
         // indefinitely".
-        //
-        // The check below also rejects a value greater than the maximum
-        // integer value.  The lexical_cast operation used to obtain a numeric
-        // value from a string can get confused if trying to convert a negative
-        // integer to an unsigned int: instead of throwing an exception, it may
-        // produce a large positive value.
-        if ((connect_timeout == 0) ||
-            (connect_timeout > numeric_limits<int>::max())) {
-            isc_throw(DbInvalidTimeout, "database connection timeout (" <<
-                      stimeout << ") must be an integer greater than 0");
-        }
+        setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout);
+        setIntParameterValue("read-timeout", 0, numeric_limits<int>::max(), read_timeout);
+        setIntParameterValue("write-timeout", 0, numeric_limits<int>::max(), write_timeout);
+
+    } catch (const std::exception& ex) {
+        isc_throw(DbInvalidTimeout, ex.what());
     }
 
     const char* ca_file(0);
@@ -241,6 +200,26 @@ MySqlConnection::openDatabase() {
                   mysql_error(mysql_));
     }
 
+    // Set the read timeout if it has been specified. Otherwise, the timeout is
+    // not used.
+    if (read_timeout > 0) {
+        result = mysql_options(mysql_, MYSQL_OPT_READ_TIMEOUT, &read_timeout);
+        if (result != 0) {
+            isc_throw(DbOpenError, "unable to set database read timeout: " <<
+                      mysql_error(mysql_));
+        }
+    }
+
+    // Set the write timeout if it has been specified. Otherwise, the timeout
+    // is not used.
+    if (write_timeout > 0) {
+        result = mysql_options(mysql_, MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
+        if (result != 0) {
+            isc_throw(DbOpenError, "unable to set database write timeout: " <<
+                      mysql_error(mysql_));
+        }
+    }
+
     // If TLS is enabled set it. If something should go wrong it will happen
     // later at the mysql_real_connect call.
     if (tls_) {
@@ -517,5 +496,37 @@ MySqlConnection::rollback() {
     }
 }
 
+template<typename T>
+void
+MySqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) {
+    string svalue;
+    try {
+        svalue = getParameter(name);
+    } catch (...) {
+        // Do nothing if the parameter is not present.
+    }
+    if (svalue.empty()) {
+        return;
+    }
+    try {
+        // Try to convert the value.
+        auto parsed_value = boost::lexical_cast<T>(svalue);
+        // Check if the value is within the specified range.
+        if ((parsed_value < min) || (parsed_value > max)) {
+            isc_throw(BadValue, "bad " << svalue << " value");
+        }
+        // Everything is fine. Return the parsed value.
+        value = parsed_value;
+
+    } catch (...) {
+        // We may end up here when lexical_cast fails or when the
+        // parsed value is not within the desired range. In both
+        // cases let's throw the same general error.
+        isc_throw(BadValue, name << " parameter (" <<
+                  svalue << ") must be an integer between "
+                  << min << " and " << max);
+    }
+}
+
 } // namespace db
 } // namespace isc
index 50ebc28d081560768ff82717af68aa22848ef4eb..f68a42c5dec4e57ed4ae2051d52c8e00516d86d3 100644 (file)
@@ -712,6 +712,27 @@ public:
         return (cipher ? std::string(cipher) : "");
     }
 
+private:
+
+    /// @brief Convenience function parsing and setting an integer parameter,
+    /// if it exists.
+    ///
+    /// If the parameter is not present, this function doesn't change the @c value.
+    /// Otherwise, it tries to convert the parameter to the type @c T. Finally,
+    /// it checks if the converted number is within the specified range.
+    ///
+    /// @param name Parameter name.
+    /// @param min Expected minimal value.
+    /// @param max Expected maximal value.
+    /// @param [out] value Reference to a value returning the parsed parameter.
+    /// @tparam T Parameter type.
+    /// @throw BadValue if the parameter is not a valid number or if it is out
+    /// of range.
+    template<typename T>
+    void setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value);
+
+public:
+
     /// @brief Prepared statements
     ///
     /// This field is public, because it is used heavily from MySqlConnection
index 19d1a6ae72a6eaf52b9d3f0ab121ece8dad029b4..46a8cb0abc7baa3e18cba559de406336706b7f52 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2018-2023 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -609,6 +609,126 @@ TEST_F(MySqlConnectionTest, transactions) {
     EXPECT_THROW(conn_.rollback(), isc::Unexpected);
 }
 
+// Tests that valid connection timeout is accepted.
+TEST_F(MySqlConnectionTest, connectionTimeout) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, VALID_TIMEOUT);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+    auto mysql = static_cast<MYSQL*>(conn.mysql_);
+    ASSERT_TRUE(mysql);
+    unsigned int timeout = 123;
+    EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout));
+    EXPECT_EQ(10, timeout);
+}
+
+// Tests that invalid timeout value type causes an error.
+TEST_F(MySqlConnectionTest, connectionTimeoutInvalid) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, INVALID_TIMEOUT_1);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a negative connection timeout value causes an error.
+TEST_F(MySqlConnectionTest, connectionTimeoutInvalid2) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, INVALID_TIMEOUT_2);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a zero connection timeout value causes an error.
+TEST_F(MySqlConnectionTest, connectionTimeoutInvalid3) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, INVALID_TIMEOUT_3);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a valid read timeout is accepted.
+TEST_F(MySqlConnectionTest, readTimeout) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, VALID_READ_TIMEOUT);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+    auto mysql = static_cast<MYSQL*>(conn.mysql_);
+    ASSERT_TRUE(mysql);
+    unsigned int timeout = 123;
+    EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_READ_TIMEOUT, &timeout));
+    EXPECT_EQ(11, timeout);
+}
+
+// Tests that a zero read timeout is accepted.
+TEST_F(MySqlConnectionTest, readTimeoutZero) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, VALID_READ_TIMEOUT_ZERO);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+    auto mysql = static_cast<MYSQL*>(conn.mysql_);
+    ASSERT_TRUE(mysql);
+    unsigned int timeout = 123;
+    EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_READ_TIMEOUT, &timeout));
+    EXPECT_EQ(0, timeout);
+}
+
+// Tests that an invalid read timeout causes an error.
+TEST_F(MySqlConnectionTest, readTimeoutInvalid) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, INVALID_READ_TIMEOUT_1);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
+// Tests that a valid write timeout is accepted.
+TEST_F(MySqlConnectionTest, writeTimeout) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, VALID_WRITE_TIMEOUT);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+    auto mysql = static_cast<MYSQL*>(conn.mysql_);
+    ASSERT_TRUE(mysql);
+    unsigned int timeout = 123;
+    EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout));
+    EXPECT_EQ(12, timeout);
+}
+
+// Tests that a zero write timeout is accepted.
+TEST_F(MySqlConnectionTest, writeTimeoutZero) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, VALID_WRITE_TIMEOUT_ZERO);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    ASSERT_NO_THROW_LOG(conn.openDatabase());
+
+    auto mysql = static_cast<MYSQL*>(conn.mysql_);
+    ASSERT_TRUE(mysql);
+    unsigned int timeout = 123;
+    EXPECT_EQ(0, mysql_get_option(mysql, MYSQL_OPT_WRITE_TIMEOUT, &timeout));
+    EXPECT_EQ(0, timeout);
+}
+
+// Tests that an invalid write timeout causes an error.
+TEST_F(MySqlConnectionTest, writeTimeoutInvalid) {
+    std::string conn_str = connectionString(MYSQL_VALID_TYPE, VALID_NAME,
+                                            VALID_HOST_TCP, VALID_USER,
+                                            VALID_PASSWORD, INVALID_WRITE_TIMEOUT_1);
+    MySqlConnection conn(DatabaseConnection::parse(conn_str));
+    EXPECT_THROW(conn.openDatabase(), DbInvalidTimeout);
+}
+
 TEST_F(MySqlConnectionWithPrimaryKeyTest, select) {
     select();
 }