From 23325e3913b4482bb47260f343c416e4bc5e5b23 Mon Sep 17 00:00:00 2001 From: Donat Zenichev Date: Wed, 26 Nov 2025 15:36:15 +0100 Subject: [PATCH] MT#62868 Add timeout to database queries This patch adds timeout to the queries to avoid SEMS waiting forever for a response. Change-Id: I6933caf36fbeb00211386fb6a3415f9f739f7300 --- apps/db_reg_agent/DBRegAgent.cpp | 85 +++++++++-------- apps/dsm/mods/mod_mysql/ModMysql.cpp | 93 ++++++++++-------- core/DBUtils.h | 137 +++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 84 deletions(-) create mode 100644 core/DBUtils.h diff --git a/apps/db_reg_agent/DBRegAgent.cpp b/apps/db_reg_agent/DBRegAgent.cpp index 55b06919..642b9300 100644 --- a/apps/db_reg_agent/DBRegAgent.cpp +++ b/apps/db_reg_agent/DBRegAgent.cpp @@ -18,11 +18,12 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "DBUtils.h" #include "DBRegAgent.h" #include "AmSession.h" #include "AmEventDispatcher.h" @@ -194,9 +195,14 @@ int DBRegAgent::onLoad() try { - MainDBConnection.set_option(new mysqlpp::ReconnectOption(true)); - // matched instead of changed rows in result, so we know when to create DB entry - MainDBConnection.set_option(new mysqlpp::FoundRowsOption(true)); + std::initializer_list args = { + dbReconnectOption, // if present = dbReconnectOption(true) + dbConnectTimeoutOption(5), // seconds + dbReadTimeoutOption(5), // seconds + dbWriteTimeoutOption(5), // seconds + dbFoundRowsOption // if present = dbFoundRowsOption(true) + }; + apply_mysql_options(MainDBConnection, args); MainDBConnection.connect(mysql_db.c_str(), mysql_server.c_str(), mysql_user.c_str(), mysql_passwd.c_str()); if (!MainDBConnection) { @@ -204,9 +210,7 @@ int DBRegAgent::onLoad() return -1; } - ProcessorDBConnection.set_option(new mysqlpp::ReconnectOption(true)); - // matched instead of changed rows in result, so we know when to create DB entry - ProcessorDBConnection.set_option(new mysqlpp::FoundRowsOption(true)); + apply_mysql_options(ProcessorDBConnection, args); ProcessorDBConnection.connect(mysql_db.c_str(), mysql_server.c_str(), mysql_user.c_str(), mysql_passwd.c_str()); if (!ProcessorDBConnection) { @@ -615,14 +619,14 @@ void DBRegAgent::createRegistration(long object_id, if (NULL != uac_auth_i) { DBG("REGISTER: Enabling UAC Auth for new registration of type: <%s>\n", TYPE_TO_STRING(type)); - + // get a sessionEventHandler from uac_auth AmArg di_args, ret; AmArg a; a.setBorrowedPointer(reg); di_args.push(a); di_args.push(a); - + uac_auth_i->invoke("getHandler", di_args, ret); if (!ret.size()) { ERROR("Can not add auth handler to new registration!\n"); @@ -814,7 +818,7 @@ void DBRegAgent::process(AmEvent* ev) { if (ev->event_id == E_SYSTEM) { AmSystemEvent* sys_ev = dynamic_cast(ev); - if(sys_ev){ + if(sys_ev){ DBG("Session received system Event\n"); if (sys_ev->sys_event == AmSystemEvent::ServerShutdown) { stop(); @@ -951,7 +955,7 @@ void DBRegAgent::createDBRegistration(long object_id, const regType type, mysqlp WARN("creating registration in DB with query '%s' failed: '%s', type: %s\n", insert_query.c_str(), res.info(), TYPE_TO_STRING(type)); } - } catch (const mysqlpp::Exception& er) { + } catch (const mysqlpp::Exception& er) { // Catch-all for any MySQL++ exceptions ERROR("MySQL++ error: %s\n", er.what()); return; @@ -979,7 +983,7 @@ void DBRegAgent::deleteDBRegistration(long object_id, const regType type, mysqlp WARN("removing registration in DB with query '%s' failed: '%s', type: %s\n", insert_query.c_str(), res.info(), TYPE_TO_STRING(type)); } - } catch (const mysqlpp::Exception& er) { + } catch (const mysqlpp::Exception& er) { // Catch-all for any MySQL++ exceptions ERROR("MySQL++ error: %s\n", er.what()); return; @@ -1005,8 +1009,7 @@ void DBRegAgent::updateDBRegistration(mysqlpp::Connection& db_connection, } if (update_ts) { - query << ", last_registration=NOW(), " - "expiry=TIMESTAMPADD(SECOND,"+int2str(expiry)+", NOW())"; + query << ", last_registration=NOW(), expiry=TIMESTAMPADD(SECOND," + int2str(expiry) + ", NOW())"; } if (update_contacts) { @@ -1024,21 +1027,19 @@ void DBRegAgent::updateDBRegistration(mysqlpp::Connection& db_connection, mysqlpp::SimpleResult res = query.execute(); if (!res) { - WARN("updating registration in DB with query '%s' failed: '%s'\n", - query_str.c_str(), res.info()); + WARN("updating registration in DB with query '%s' failed: '%s'\n", query_str.c_str(), res.info()); } else { if (!res.rows()) { - // should not happen - DB entry is created on load or on createRegistration - DBG("creating registration DB entry for subscriber %ld, type: %s\n", object_id, TYPE_TO_STRING(type)); - createDBRegistration(object_id, type, db_connection); - query.reset(); - query << query_str; - - mysqlpp::SimpleResult res = query.execute(); - if (!res || !res.rows()) { - WARN("updating registration in DB with query '%s' failed: '%s'\n", - query_str.c_str(), res.info()); - } + // should not happen - DB entry is created on load or on createRegistration + DBG("creating registration DB entry for subscriber %ld, type: %s\n", object_id, TYPE_TO_STRING(type)); + createDBRegistration(object_id, type, db_connection); + query.reset(); + query << query_str; + + mysqlpp::SimpleResult res = query.execute(); + if (!res || !res.rows()) { + WARN("updating registration in DB with query '%s' failed: '%s'\n", query_str.c_str(), res.info()); + } } } @@ -1054,7 +1055,7 @@ void DBRegAgent::updateDBRegistration(mysqlpp::Connection& db_connection, void DBRegAgent::onSipReplyEvent(AmSipReplyEvent* ev) { if (!ev) return; - DBG("received SIP reply event for '%s'\n", + DBG("received SIP reply event for '%s'\n", #ifdef HAS_OFFER_ANSWER ev->reply.from_tag.c_str() #else @@ -1220,7 +1221,7 @@ void DBRegAgent::onSipReplyEvent(AmSipReplyEvent* ev) { void DBRegAgent::run() { DBG("DBRegAgent thread: waiting 2 sec for server startup ...\n"); sleep(2); - + mysqlpp::Connection::thread_start(); if (enable_ratelimiting) { @@ -1380,7 +1381,7 @@ void DBRegAgent::setRegistrationTimer(long object_id, "minimum_reregister_interval=%f)\n", t_expiry_min, t_expiry_max, reg_start_ts, expiry, reregister_interval, minimum_reregister_interval); - + registration_scheduler.insert_timer_abs(timer, t_expiry_min * 1000000, t_expiry_max * 1000000); } } else { @@ -1735,7 +1736,7 @@ void DBRegAgentProcessorThread::rateLimitWait() { memcpy(&last_check, ¤t, sizeof(struct timeval)); double seconds_passed = (double)time_passed.tv_sec + (double)time_passed.tv_usec / 1000000.0; - allowance += seconds_passed * + allowance += seconds_passed * (double) DBRegAgent::ratelimit_rate / (double)DBRegAgent::ratelimit_per; if (allowance > (double)DBRegAgent::ratelimit_rate) @@ -1756,7 +1757,7 @@ void DBRegAgentProcessorThread::rateLimitWait() { void DBRegAgentProcessorThread::run() { DBG("DBRegAgentProcessorThread thread started\n"); - + // register us as SIP event receiver for MOD_NAME_processor AmEventDispatcher::instance()->addEventQueue(MOD_NAME "_processor",this); @@ -1780,14 +1781,14 @@ void DBRegAgentProcessorThread::run() { mysqlpp::Connection::thread_end(); - DBG("DBRegAgentProcessorThread thread stopped\n"); + DBG("DBRegAgentProcessorThread thread stopped\n"); } void DBRegAgentProcessorThread::process(AmEvent* ev) { if (ev->event_id == E_SYSTEM) { AmSystemEvent* sys_ev = dynamic_cast(ev); - if(sys_ev){ + if(sys_ev){ DBG("Session received system Event\n"); if (sys_ev->sys_event == AmSystemEvent::ServerShutdown) { DBG("stopping processor thread\n"); @@ -1822,32 +1823,32 @@ void DBRegAgent::run_tests() { gettimeofday(&now, 0); RegTimer rt; - rt.expires = now.tv_sec + 10; + rt.expires = now.tv_sec + 10; rt.cb=test_cb; registration_scheduler.insert_timer(&rt); RegTimer rt2; - rt2.expires = now.tv_sec + 5; + rt2.expires = now.tv_sec + 5; rt2.cb=test_cb; registration_scheduler.insert_timer(&rt2); RegTimer rt3; - rt3.expires = now.tv_sec + 15; + rt3.expires = now.tv_sec + 15; rt3.cb=test_cb; registration_scheduler.insert_timer(&rt3); RegTimer rt4; - rt4.expires = now.tv_sec - 1; + rt4.expires = now.tv_sec - 1; rt4.cb=test_cb; registration_scheduler.insert_timer(&rt4); RegTimer rt5; - rt5.expires = now.tv_sec + 100000; + rt5.expires = now.tv_sec + 100000; rt5.cb=test_cb; registration_scheduler.insert_timer(&rt5); RegTimer rt6; - rt6.expires = now.tv_sec + 100; + rt6.expires = now.tv_sec + 100; rt6.cb=test_cb; registration_scheduler.insert_timer_leastloaded(&rt6, now.tv_sec+5, now.tv_sec+50); @@ -1856,7 +1857,7 @@ void DBRegAgent::run_tests() { gettimeofday(&now, 0); RegTimer rt7; - rt6.expires = now.tv_sec + 980; + rt6.expires = now.tv_sec + 980; rt6.cb=test_cb; registration_scheduler.insert_timer_leastloaded(&rt6, now.tv_sec+9980, now.tv_sec+9990); diff --git a/apps/dsm/mods/mod_mysql/ModMysql.cpp b/apps/dsm/mods/mod_mysql/ModMysql.cpp index c9b237ed..8438074f 100644 --- a/apps/dsm/mods/mod_mysql/ModMysql.cpp +++ b/apps/dsm/mods/mod_mysql/ModMysql.cpp @@ -28,6 +28,7 @@ #include "ModMysql.h" #include "log.h" #include "AmUtils.h" +#include "DBUtils.h" #include "DSMSession.h" #include "AmSession.h" @@ -110,6 +111,7 @@ mysqlpp::Connection* getMyDSMSessionConnection(DSMSession* sc_sess) { sc_sess->SET_STRERROR("No connection to database (not mysqlpp::Connection)"); return NULL; } + apply_mysql_options(*res, dbConnectTimeoutOption(5), dbReadTimeoutOption(5), dbWriteTimeoutOption(5)); return res; } @@ -222,8 +224,9 @@ EXEC_ACTION_START(SCMyConnectAction) { } EXEC_ACTION_END; EXEC_ACTION_START(SCMyDisconnectAction) { - mysqlpp::Connection* conn = + mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); + if (NULL == conn) return false; @@ -241,13 +244,14 @@ EXEC_ACTION_START(SCMyDisconnectAction) { } EXEC_ACTION_END; EXEC_ACTION_START(SCMyResolveQueryParams) { - sc_sess->var["db.qstr"] = + sc_sess->var["db.qstr"] = replaceQueryParams(arg, sc_sess, event_params); } EXEC_ACTION_END; EXEC_ACTION_START(SCMyExecuteAction) { - mysqlpp::Connection* conn = + mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); + if (NULL == conn) return false; string qstr = replaceQueryParams(arg, sc_sess, event_params); @@ -267,24 +271,25 @@ EXEC_ACTION_START(SCMyExecuteAction) { } } catch (const mysqlpp::Exception& e) { - ERROR("DB query '%s' failed: '%s'\n", + ERROR("DB query '%s' failed: '%s'\n", qstr.c_str(), e.what()); - sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); + sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } } EXEC_ACTION_END; EXEC_ACTION_START(SCMyQueryAction) { - mysqlpp::Connection* conn = + mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); - if (NULL == conn) + + if (NULL == conn) return false; string qstr = replaceQueryParams(arg, sc_sess, event_params); try { mysqlpp::Query query = conn->query(qstr.c_str()); - mysqlpp::StoreQueryResult res = query.store(); + mysqlpp::StoreQueryResult res = query.store(); if (res) { // MySQL++ does not allow working with pointers here, so copy construct it DSMMyStoreQueryResult* m_res = new DSMMyStoreQueryResult(res); @@ -314,15 +319,16 @@ EXEC_ACTION_START(SCMyQueryAction) { CONST_ACTION_2P(SCMyQueryGetResultAction, ',', true); EXEC_ACTION_START(SCMyQueryGetResultAction) { - mysqlpp::Connection* conn = + mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); - if (NULL == conn) + + if (NULL == conn) return false; string qstr = replaceQueryParams(par1, sc_sess, event_params); try { mysqlpp::Query query = conn->query(qstr.c_str()); - mysqlpp::StoreQueryResult res = query.store(); + mysqlpp::StoreQueryResult res = query.store(); if (res) { unsigned int rowindex_i = 0; string rowindex = resolveVars(par2, sess, sc_sess, event_params); @@ -456,8 +462,9 @@ EXEC_ACTION_START(SCMyUseResultAction) { bool playDBAudio(AmSession* sess, DSMSession* sc_sess, DSMCondition::EventType event, map* event_params, const string& par1, const string& par2, bool looped, bool front) { - mysqlpp::Connection* conn = + mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); + if (NULL == conn) EXEC_ACTION_STOP; @@ -465,20 +472,20 @@ bool playDBAudio(AmSession* sess, DSMSession* sc_sess, DSMCondition::EventType e try { mysqlpp::Query query = conn->query(qstr.c_str()); - mysqlpp::UseQueryResult res = query.use(); + mysqlpp::UseQueryResult res = query.use(); if (res) { mysqlpp::Row row = res.fetch_row(); if (!row) { - sc_sess->SET_ERRNO(DSM_ERRNO_MY_NOROW); - sc_sess->SET_STRERROR("result does not have row"); - EXEC_ACTION_STOP; + sc_sess->SET_ERRNO(DSM_ERRNO_MY_NOROW); + sc_sess->SET_STRERROR("result does not have row"); + EXEC_ACTION_STOP; } FILE *t_file = tmpfile(); if (NULL == t_file) { - sc_sess->SET_ERRNO(DSM_ERRNO_FILE); - sc_sess->SET_STRERROR("tmpfile() failed: "+string(strerror(errno))); - EXEC_ACTION_STOP; + sc_sess->SET_ERRNO(DSM_ERRNO_FILE); + sc_sess->SET_STRERROR("tmpfile() failed: "+string(strerror(errno))); + EXEC_ACTION_STOP; } fwrite(row.at(0).data(), 1, row.at(0).size(), t_file); @@ -486,9 +493,9 @@ bool playDBAudio(AmSession* sess, DSMSession* sc_sess, DSMCondition::EventType e DSMDisposableAudioFile* a_file = new DSMDisposableAudioFile(); if (a_file->fpopen(par2, AmAudioFile::Read, t_file)) { - sc_sess->SET_ERRNO(DSM_ERRNO_FILE); - sc_sess->SET_STRERROR("fpopen failed!"); - EXEC_ACTION_STOP; + sc_sess->SET_ERRNO(DSM_ERRNO_FILE); + sc_sess->SET_STRERROR("fpopen failed!"); + EXEC_ACTION_STOP; } a_file->loop = looped; @@ -496,15 +503,15 @@ bool playDBAudio(AmSession* sess, DSMSession* sc_sess, DSMCondition::EventType e sc_sess->addToPlaylist(new AmPlaylistItem(a_file, NULL), front); sc_sess->transferOwnership(a_file); - sc_sess->CLR_ERRNO; + sc_sess->CLR_ERRNO; } else { sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); - sc_sess->SET_STRERROR("query does not have result"); + sc_sess->SET_STRERROR("query does not have result"); } } catch (const mysqlpp::Exception& e) { - ERROR("DB query '%s' failed: '%s'\n", + ERROR("DB query '%s' failed: '%s'\n", qstr.c_str(), e.what()); - sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); + sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } @@ -531,40 +538,41 @@ EXEC_ACTION_START(SCMyPlayDBAudioLoopedAction) { CONST_ACTION_2P(SCMyGetFileFromDBAction, ',', true); EXEC_ACTION_START(SCMyGetFileFromDBAction) { - mysqlpp::Connection* conn = + mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); - if (NULL == conn) + + if (NULL == conn) return false; string qstr = replaceQueryParams(par1, sc_sess, event_params); string fname = resolveVars(par2, sess, sc_sess, event_params); try { mysqlpp::Query query = conn->query(qstr.c_str()); - mysqlpp::UseQueryResult res = query.use(); + mysqlpp::UseQueryResult res = query.use(); if (res) { mysqlpp::Row row = res.fetch_row(); if (!row) { - sc_sess->SET_ERRNO(DSM_ERRNO_MY_NOROW); - sc_sess->SET_STRERROR("result does not have row"); - return false; + sc_sess->SET_ERRNO(DSM_ERRNO_MY_NOROW); + sc_sess->SET_STRERROR("result does not have row"); + return false; } FILE *t_file = fopen(fname.c_str(), "wb"); if (NULL == t_file) { - sc_sess->SET_ERRNO(DSM_ERRNO_FILE); - sc_sess->SET_STRERROR("fopen() failed for file '"+fname+"': "+string(strerror(errno))); - return false; + sc_sess->SET_ERRNO(DSM_ERRNO_FILE); + sc_sess->SET_STRERROR("fopen() failed for file '"+fname+"': "+string(strerror(errno))); + return false; } fwrite(row.at(0).data(), 1, row.at(0).size(), t_file); fclose(t_file); - sc_sess->CLR_ERRNO; + sc_sess->CLR_ERRNO; } else { sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); - sc_sess->SET_STRERROR("query does not have result"); + sc_sess->SET_STRERROR("query does not have result"); } } catch (const mysqlpp::Exception& e) { - ERROR("DB query '%s' failed: '%s'\n", + ERROR("DB query '%s' failed: '%s'\n", qstr.c_str(), e.what()); sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); @@ -574,9 +582,10 @@ EXEC_ACTION_START(SCMyGetFileFromDBAction) { CONST_ACTION_2P(SCMyPutFileToDBAction, ',', true); EXEC_ACTION_START(SCMyPutFileToDBAction) { - mysqlpp::Connection* conn = + mysqlpp::Connection* conn = getMyDSMSessionConnection(sc_sess); - if (NULL == conn) + + if (NULL == conn) return false; string qstr = replaceQueryParams(par1, sc_sess, event_params); @@ -628,9 +637,9 @@ EXEC_ACTION_START(SCMyPutFileToDBAction) { } } catch (const mysqlpp::Exception& e) { - ERROR("DB query '%s' failed: '%s'\n", + ERROR("DB query '%s' failed: '%s'\n", par1.c_str(), e.what()); - sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); + sc_sess->SET_ERRNO(DSM_ERRNO_MY_QUERY); sc_sess->SET_STRERROR(e.what()); sc_sess->var["db.ereason"] = e.what(); } diff --git a/core/DBUtils.h b/core/DBUtils.h new file mode 100644 index 00000000..f7df1172 --- /dev/null +++ b/core/DBUtils.h @@ -0,0 +1,137 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +/* +Utilization examples: +With list of arguments: +``` +std::initializer_list args = { + dbReconnectOption, + dbConnectTimeoutOption(5), + dbReadTimeoutOption(5), + dbWriteTimeoutOption(5), + dbFoundRowsOption +}; +apply_mysql_options(conn1, args); +apply_mysql_options(conn2, args); +apply_mysql_options(conn3, args); +``` +As a variadic function: +``` +apply_mysql_options(conn, dbConnectTimeoutOption(5), dbReadTimeoutOption(5)); +``` +Variadic function, only setting reconnectionOption: +``` +apply_mysql_options(conn, dbReconnectOption); +``` +Variadic function, no time set(default: 5) and out of order: +``` +apply_mysql_options(conn, dbFoundRowsOption, dbConnectTimeoutOption); +``` +Note that ```apply_mysql_options(conn)``` should do nothing. +*/ +enum class DbOptionId { Reconnect, ConnectTimeout, ReadTimeout, WriteTimeout, FoundRows }; +using Value = std::variant; +using Kv = std::pair; +struct Key +{ + DbOptionId id; + template + auto operator()(T&& v) const { + return Kv{ id, Value{std::forward(v)} }; + } +}; +inline constexpr int defaultTimeoutSec = 5; +inline constexpr Key dbReconnectOption{DbOptionId::Reconnect}; +inline constexpr Key dbConnectTimeoutOption{DbOptionId::ConnectTimeout}; +inline constexpr Key dbReadTimeoutOption{DbOptionId::ReadTimeout}; +inline constexpr Key dbWriteTimeoutOption{DbOptionId::WriteTimeout}; +inline constexpr Key dbFoundRowsOption{DbOptionId::FoundRows}; +using DBArgOption = std::variant; +template +concept ArgLike = std::is_same_v, Key> || std::is_same_v, Kv>; +template +inline void set_opt(mysqlpp::Connection& conn, Args&&... args) { + auto p = std::make_unique(std::forward(args)...); + conn.set_option(p.release()); +} +// Flags (no value). Example: passing `dbReconnectOption` alone sets reconnect=true. +inline void process_arg(mysqlpp::Connection& conn, const Key& k) { + DbOptionId id = k.id; + switch(id) { + case DbOptionId::Reconnect: + set_opt(conn, true); + break; + case DbOptionId::FoundRows: + set_opt(conn, true); + break; + case DbOptionId::ConnectTimeout: + set_opt(conn, defaultTimeoutSec); + break; + case DbOptionId::ReadTimeout: + set_opt(conn, defaultTimeoutSec); + break; + case DbOptionId::WriteTimeout: + set_opt(conn, defaultTimeoutSec); + break; + default: + ERROR("Unknown DB id(%d) Option without value.\n", static_cast(id)); + break; + } +} +template +inline void set_as(mysqlpp::Connection& conn, const Value& val, MakeUnique make_unique) { + if (const T* p = std::get_if(&val)) { + conn.set_option(make_unique(*p).release()); + } +} +// Key/value pairs. Example: dbConnectTimeoutOption(5) sets a 5s connect timeout. +inline void process_arg(mysqlpp::Connection& conn, const Kv& kv) { + const auto& [id, val] = kv; + switch(id) { + case DbOptionId::ConnectTimeout: + set_as(conn, val, [&](int x) { return std::make_unique(x); }); + break; + case DbOptionId::ReadTimeout: + set_as(conn, val, [&](int x) { return std::make_unique(x); }); + break; + case DbOptionId::WriteTimeout: + set_as(conn, val, [&](int x) { return std::make_unique(x); }); + break; + case DbOptionId::Reconnect: + set_as(conn, val, [&](bool x) { return std::make_unique(x); }); + break; + case DbOptionId::FoundRows: + set_as(conn, val, [&](bool x) { return std::make_unique(x); }); + break; + default: + std::string valueStr; + if (auto p = std::get_if(&val)) { + valueStr = *p; + } else if (auto p = std::get_if(&val)) { + valueStr = std::to_string(*p); + } else if (auto p = std::get_if(&val)) { + valueStr = *p ? "true" : "false"; + } else { + valueStr = ""; + } + ERROR("Unknown DB id(%d) Option with value (%s).\n", static_cast(id), valueStr.c_str()); + break; + } +} +template +inline void apply_mysql_options(mysqlpp::Connection& conn, Args&&... args) { + (process_arg(conn, std::forward(args)), ...); +} +inline void apply_mysql_options(mysqlpp::Connection& conn, + std::initializer_list args) { + for (const auto& a : args) { + std::visit([&](const auto& x) { process_arg(conn, x); }, a); + } +} \ No newline at end of file