Add a way to set the character set when creating DbClient objects (#486)

This commit is contained in:
An Tao 2020-06-20 20:21:14 +08:00 committed by GitHub
parent 56b5d03fed
commit 3424d3f2c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 154 additions and 29 deletions

View File

@ -47,6 +47,9 @@
//is_fast: false by default, if it is true, the client is faster but user can't call
//any synchronous interface of it.
"is_fast": false,
//client_encoding: The character set used by the client. it is empty string by default which
//means use the default character set.
//"client_encoding": "",
//connection_number: 1 by default, if the 'is_fast' is true, the number is the number of
//connections per IO thread, otherwise it is the total number of all connections.
"connection_number": 1
@ -114,7 +117,7 @@
//is_recursive: true by default. If it is set to false, files in sub directories can't be accessed.
"is_recursive": true,
//filters: string array, the filters applied to the location.
"filters":[]
"filters": []
}],
//max_connections: maximum connections number,100000 by default
"max_connections": 100000,
@ -227,8 +230,11 @@
}],
//custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
"custom_config": {
"realm" : "drogonRealm",
"opaque" : "drogonOpaque",
"credentials" : [ {"user" : "drogon", "password": "dr0g0n"} ]
"realm": "drogonRealm",
"opaque": "drogonOpaque",
"credentials": [{
"user": "drogon",
"password": "dr0g0n"
}]
}
}

View File

@ -722,6 +722,12 @@ void create_model::createModel(const std::string &path,
connStr += " password=";
connStr += password;
}
auto characterSet = config.get("client_encoding", "").asString();
if (!characterSet.empty())
{
connStr += " client_encoding=";
connStr += characterSet;
}
auto schema = config.get("schema", "public").asString();
DbClientPtr client = drogon::orm::DbClient::newPgClient(connStr, 1);
@ -822,6 +828,12 @@ void create_model::createModel(const std::string &path,
connStr += " password=";
connStr += password;
}
auto characterSet = config.get("client_encoding", "").asString();
if (!characterSet.empty())
{
connStr += " client_encoding=";
connStr += characterSet;
}
DbClientPtr client = drogon::orm::DbClient::newMysqlClient(connStr, 1);
std::cout << "Connect to server..." << std::endl;
if (forceOverwrite_)

View File

@ -47,6 +47,9 @@
//is_fast: false by default, if it is true, the client is faster but user can't call
//any synchronous interface of it.
"is_fast": false,
//client_encoding: The character set used by the client. it is empty string by default which
//means use the default character set.
//"client_encoding": "",
//connection_number: 1 by default, if the 'is_fast' is true, the number is the number of
//connections per IO thread, otherwise it is the total number of all connections.
"connection_number": 1
@ -114,7 +117,7 @@
//is_recursive: true by default. If it is set to false, files in sub directories can't be accessed.
"is_recursive": true,
//filters: string array, the filters applied to the location.
"filters":[]
"filters": []
}],
//max_connections: maximum connections number,100000 by default
"max_connections": 100000,

View File

@ -13,8 +13,11 @@
"schema": "public",
//user: User name
"user": "",
//passwd: Password
"passwd": "",
//password or passwd: Password
"password": "",
//client_encoding: The character set used by drogon_ctl. it is empty string by default which
//means use the default character set.
//"client_encoding": "",
//table: An array of tables to be modelized. if the array is empty, all revealed tables are modelized.
"tables": [],
"relationships": {

View File

@ -1092,7 +1092,8 @@ class HttpAppFramework : public trantor::NonCopyable
const size_t connectionNum = 1,
const std::string &filename = "",
const std::string &name = "default",
const bool isFast = false) = 0;
const bool isFast = false,
const std::string &characterSet = "") = 0;
/// Get the DNS resolver
/**

View File

@ -470,6 +470,11 @@ static void loadDbClients(const Json::Value &dbClients)
auto name = client.get("name", "default").asString();
auto filename = client.get("filename", "").asString();
auto isFast = client.get("is_fast", false).asBool();
auto characterSet = client.get("characterSet", "").asString();
if (characterSet.empty())
{
characterSet = client.get("client_encoding", "").asString();
}
drogon::app().createDbClient(type,
host,
(unsigned short)port,
@ -479,7 +484,8 @@ static void loadDbClients(const Json::Value &dbClients)
connNum,
filename,
name,
isFast);
isFast,
characterSet);
}
}
static void loadListeners(const Json::Value &listeners)

View File

@ -51,7 +51,8 @@ class DbClientManager : public trantor::NonCopyable
const size_t connectionNum,
const std::string &filename,
const std::string &name,
const bool isFast);
const bool isFast,
const std::string &characterSet);
bool areAllDbClientsAvailable() const noexcept;
private:

View File

@ -36,7 +36,8 @@ void DbClientManager::createDbClient(const std::string &dbType,
const size_t connectionNum,
const std::string &filename,
const std::string &name,
const bool isFast)
const bool isFast,
const std::string &characterSet)
{
LOG_FATAL << "No database is supported by drogon, please install the "
"database development library first.";

View File

@ -886,7 +886,8 @@ HttpAppFramework &HttpAppFrameworkImpl::createDbClient(
const size_t connectionNum,
const std::string &filename,
const std::string &name,
const bool isFast)
const bool isFast,
const std::string &characterSet)
{
assert(!running_);
dbClientManagerPtr_->createDbClient(dbType,
@ -898,7 +899,8 @@ HttpAppFramework &HttpAppFrameworkImpl::createDbClient(
connectionNum,
filename,
name,
isFast);
isFast,
characterSet);
return *this;
}

View File

@ -425,7 +425,8 @@ class HttpAppFrameworkImpl : public HttpAppFramework
const size_t connectionNum = 1,
const std::string &filename = "",
const std::string &name = "default",
const bool isFast = false) override;
const bool isFast = false,
const std::string &characterSet = "") override;
inline static HttpAppFrameworkImpl &instance()
{

View File

@ -59,6 +59,7 @@ class DbClient : public trantor::NonCopyable
* as the operating system name of the user running the application.
* - password: Password to be used if the server demands password
* authentication.
* - client_encoding: The character set to be used on database connections.
*
* For other key words on PostgreSQL, see the PostgreSQL documentation.
* Only a pair of key values is valid for Sqlite3, and its keyword is

View File

@ -94,7 +94,8 @@ void DbClientManager::createDbClient(const std::string &dbType,
const size_t connectionNum,
const std::string &filename,
const std::string &name,
const bool isFast)
const bool isFast,
const std::string &characterSet)
{
auto connStr = utils::formattedString("host=%s port=%u dbname=%s user=%s",
host.c_str(),
@ -108,6 +109,11 @@ void DbClientManager::createDbClient(const std::string &dbType,
}
std::string type = dbType;
std::transform(type.begin(), type.end(), type.begin(), tolower);
if (!characterSet.empty())
{
connStr += " client_encoding=";
connStr += characterSet;
}
DbInfo info;
info.connectionInfo_ = connStr;
info.connectionNumber_ = connectionNum;

View File

@ -40,6 +40,7 @@ enum class ConnectStatus
{
None = 0,
Connecting,
SettingCharacterSet,
Ok,
Bad
};

View File

@ -94,6 +94,10 @@ MysqlConnection::MysqlConnection(trantor::EventLoop *loop,
{
passwd_ = value;
}
else if (key == "client_encoding")
{
characterSet_ = value;
}
}
loop_->queueInLoop([this]() {
MYSQL *ret;
@ -200,15 +204,27 @@ void MysqlConnection::handleTimeout()
return;
}
// I don't think the programe can run to here.
status_ = ConnectStatus::Ok;
if (okCallback_)
if (characterSet_.empty())
{
auto thisPtr = shared_from_this();
okCallback_(thisPtr);
status_ = ConnectStatus::Ok;
if (okCallback_)
{
auto thisPtr = shared_from_this();
okCallback_(thisPtr);
}
}
else
{
startSetCharacterSet();
return;
}
}
setChannel();
}
else if (status_ == ConnectStatus::SettingCharacterSet)
{
continueSetCharacterSet(status);
}
else if (status_ == ConnectStatus::Ok)
{
}
@ -241,11 +257,19 @@ void MysqlConnection::handleEvent()
handleClosed();
return;
}
status_ = ConnectStatus::Ok;
if (okCallback_)
if (characterSet_.empty())
{
auto thisPtr = shared_from_this();
okCallback_(thisPtr);
status_ = ConnectStatus::Ok;
if (okCallback_)
{
auto thisPtr = shared_from_this();
okCallback_(thisPtr);
}
}
else
{
startSetCharacterSet();
return;
}
}
setChannel();
@ -320,8 +344,62 @@ void MysqlConnection::handleEvent()
return;
}
}
else if (status_ == ConnectStatus::SettingCharacterSet)
{
continueSetCharacterSet(status);
}
}
void MysqlConnection::continueSetCharacterSet(int status)
{
int err;
waitStatus_ = mysql_set_character_set_cont(&err, mysqlPtr_.get(), status);
if (waitStatus_ == 0)
{
if (err)
{
LOG_ERROR << "Error(" << err << ") \""
<< mysql_error(mysqlPtr_.get()) << "\"";
LOG_ERROR << "Failed to mysql_real_connect()";
handleClosed();
return;
}
status_ = ConnectStatus::Ok;
if (okCallback_)
{
auto thisPtr = shared_from_this();
okCallback_(thisPtr);
}
}
setChannel();
}
void MysqlConnection::startSetCharacterSet()
{
int err;
waitStatus_ = mysql_set_character_set_start(&err,
mysqlPtr_.get(),
characterSet_.data());
if (waitStatus_ == 0)
{
if (err)
{
LOG_ERROR << "error";
loop_->queueInLoop(
[thisPtr = shared_from_this()] { thisPtr->outputError(); });
return;
}
status_ = ConnectStatus::Ok;
if (okCallback_)
{
auto thisPtr = shared_from_this();
okCallback_(thisPtr);
}
}
else
{
status_ = ConnectStatus::SettingCharacterSet;
}
setChannel();
}
void MysqlConnection::execSqlInLoop(
string_view &&sql,
size_t paraNum,

View File

@ -96,10 +96,11 @@ class MysqlConnection : public DbConnection,
std::vector<int> &&format,
ResultCallback &&rcb,
std::function<void(const std::exception_ptr &)> &&exceptCallback);
void startSetCharacterSet();
void continueSetCharacterSet(int status);
std::unique_ptr<trantor::Channel> channelPtr_;
std::shared_ptr<MYSQL> mysqlPtr_;
std::string characterSet_;
void handleTimeout();
void handleClosed();

View File

@ -1967,15 +1967,17 @@ int main(int argc, char *argv[])
trantor::Logger::setLogLevel(trantor::Logger::kDebug);
#if USE_POSTGRESQL
auto postgre_client = DbClient::newPgClient(
"host=127.0.0.1 port=5432 dbname=postgres user=postgres", 1);
"host=127.0.0.1 port=5432 dbname=postgres user=postgres "
"client_encoding=utf8",
1);
while (!postgre_client->hasAvailableConnections())
{
std::this_thread::sleep_for(1s);
}
#endif
#if USE_MYSQL
auto mysql_client =
DbClient::newMysqlClient("host=localhost port=3306 user=root", 1);
auto mysql_client = DbClient::newMysqlClient(
"host=localhost port=3306 user=root client_encoding=utf8mb4", 1);
while (!mysql_client->hasAvailableConnections())
{
std::this_thread::sleep_for(1s);