drogon/drogon_ctl/create_model.cc

1354 lines
47 KiB
C++

/**
*
* create_model.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "create_model.h"
#include "cmd.h"
#include <drogon/config.h>
#include <drogon/utils/Utilities.h>
#include <drogon/HttpViewData.h>
#include <drogon/DrTemplateBase.h>
#include <trantor/utils/Logger.h>
#include <json/json.h>
#include <iostream>
#include <fstream>
#include <regex>
#include <algorithm>
#ifndef _WIN32
#include <unistd.h>
#include <dirent.h>
#include <dlfcn.h>
#else
#include <io.h>
#endif
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
using namespace drogon_ctl;
static std::string toLower(const std::string &str)
{
auto ret = str;
std::transform(ret.begin(), ret.end(), ret.begin(), tolower);
return ret;
}
static std::string escapeConnString(const std::string &str)
{
bool beQuoted = str.empty() || (str.find(' ') != std::string::npos);
std::string escaped;
escaped.reserve(str.size());
for (auto ch : str)
{
if (ch == '\'')
escaped.push_back('\\');
else if (ch == '\\')
escaped.push_back('\\');
escaped.push_back(ch);
}
if (beQuoted)
return "'" + escaped + "'";
return escaped;
}
static std::map<std::string, std::vector<ConvertMethod>> getConvertMethods(
const Json::Value &convertColumns)
{
std::map<std::string, std::vector<ConvertMethod>> ret;
auto enabled = convertColumns.get("enabled", false).asBool();
if (!enabled)
{
return ret;
} // endif
auto items = convertColumns["items"];
if (items.isNull())
{
return ret;
} // endif
if (!items.isArray())
{
std::cerr << "items of convert must be an array" << std::endl;
exit(1);
} // endif
for (auto &convertColumn : items)
{
try
{
ConvertMethod c(convertColumn);
ret[c.tableName()].push_back(c);
} // try
catch (const std::runtime_error &e)
{
std::cerr << e.what() << std::endl;
exit(1);
} // catch
} // for
return ret;
}
static std::map<std::string, std::vector<Relationship>> getRelationships(
const Json::Value &relationships)
{
std::map<std::string, std::vector<Relationship>> ret;
auto enabled = relationships.get("enabled", false).asBool();
if (!enabled)
return ret;
auto items = relationships["items"];
if (items.isNull())
return ret;
if (!items.isArray())
{
std::cerr << "items must be an array\n";
exit(1);
}
for (auto &relationship : items)
{
try
{
Relationship r(relationship);
ret[r.originalTableName()].push_back(r);
if (r.enableReverse() &&
(r.originalTableName() != r.targetTableName() ||
r.originalTableAlias() != r.targetTableAlias()))
{
auto reverse = r.reverse();
ret[reverse.originalTableName()].push_back(reverse);
}
}
catch (const std::runtime_error &e)
{
std::cerr << e.what() << std::endl;
exit(1);
}
}
return ret;
}
bool drogon_ctl::ConvertMethod::shouldConvert(const std::string &tableName,
const std::string &colName) const
{
if (tableName == "*")
{
return colName == colName_;
}
else
{
return (tableName == tableName_ && colName == colName_);
} // endif
}
#if USE_POSTGRESQL
void create_model::createModelClassFromPG(
const std::string &path,
const DbClientPtr &client,
const std::string &tableName,
const std::string &schema,
const Json::Value &restfulApiConfig,
const std::vector<Relationship> &relationships,
const std::vector<ConvertMethod> &convertMethods)
{
auto className = nameTransform(tableName, true);
HttpViewData data;
data["className"] = className;
data["tableName"] = toLower(tableName);
data["hasPrimaryKey"] = (int)0;
data["primaryKeyName"] = "";
data["dbName"] = dbname_;
data["rdbms"] = std::string("postgresql");
data["relationships"] = relationships;
data["convertMethods"] = convertMethods;
if (schema != "public")
{
data["schema"] = schema;
}
std::vector<ColumnInfo> cols;
*client << "SELECT * \
FROM information_schema.columns \
WHERE table_schema = $1 \
AND table_name = $2"
<< schema << tableName << Mode::Blocking >>
[&](const Result &r) {
if (r.size() == 0)
{
std::cout << " ---Can't create model from the table "
<< tableName
<< ", please check privileges on the table."
<< std::endl;
return;
}
for (Result::SizeType i = 0; i < r.size(); ++i)
{
auto row = r[i];
ColumnInfo info;
info.index_ = i;
info.dbType_ = "pg";
info.colName_ = row["column_name"].as<std::string>();
info.colTypeName_ = nameTransform(info.colName_, true);
info.colValName_ = nameTransform(info.colName_, false);
auto isNullAble = row["is_nullable"].as<std::string>();
info.notNull_ = isNullAble == "YES" ? false : true;
auto type = row["data_type"].as<std::string>();
info.colDatabaseType_ = type;
if (type == "smallint")
{
info.colType_ = "short";
info.colLength_ = 2;
}
else if (type == "integer")
{
info.colType_ = "int32_t";
info.colLength_ = 4;
}
else if (type == "bigint")
{
info.colType_ = "int64_t";
info.colLength_ = 8;
}
else if (type == "real")
{
info.colType_ = "float";
info.colLength_ = sizeof(float);
}
else if (type == "double precision")
{
info.colType_ = "double";
info.colLength_ = sizeof(double);
}
else if (type == "character varying")
{
info.colType_ = "std::string";
if (!row["character_maximum_length"].isNull())
info.colLength_ =
row["character_maximum_length"].as<ssize_t>();
}
else if (type == "boolean")
{
info.colType_ = "bool";
info.colLength_ = 1;
}
else if (type == "date")
{
info.colType_ = "::trantor::Date";
}
else if (type.find("timestamp") != std::string::npos)
{
info.colType_ = "::trantor::Date";
}
else if (type == "bytea")
{
info.colType_ = "std::vector<char>";
}
else if (type.find("numeric") != std::string::npos)
{
info.colType_ = "std::string";
}
else
{
info.colType_ = "std::string";
}
auto defaultVal = row["column_default"].as<std::string>();
if (!defaultVal.empty())
{
info.hasDefaultVal_ = true;
if (defaultVal.find("nextval(") == 0)
{
info.isAutoVal_ = true;
}
}
auto isIdentity = row["is_identity"].as<std::string>();
if (isIdentity == "YES")
{
info.isAutoVal_ = true;
}
cols.push_back(std::move(info));
}
} >>
[](const DrogonDbException &e) {
std::cerr << e.base().what() << std::endl;
exit(1);
};
size_t pkNumber = 0;
*client << "SELECT \
pg_constraint.conname AS pk_name,\
pg_constraint.conkey AS pk_vector \
FROM pg_constraint \
INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid \
WHERE \
pg_class.relname = $1 \
AND pg_constraint.contype = 'p'"
<< tableName << Mode::Blocking >>
[&](bool isNull,
const std::string &pkName,
const std::vector<std::shared_ptr<short>> &pk) {
if (!isNull)
{
pkNumber = pk.size();
}
} >>
[](const DrogonDbException &e) {
std::cerr << e.base().what() << std::endl;
exit(1);
};
data["hasPrimaryKey"] = (int)pkNumber;
if (pkNumber == 1)
{
*client << "SELECT \
pg_attribute.attname AS colname,\
pg_type.typname AS typename,\
pg_constraint.contype AS contype \
FROM pg_constraint \
INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid \
INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid \
AND pg_attribute.attnum = pg_constraint.conkey [ 1 ] \
INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid \
WHERE pg_class.relname = $1 and pg_constraint.contype='p'"
<< tableName << Mode::Blocking >>
[&](bool isNull,
const std::string &colName,
const std::string &type) {
if (isNull)
return;
data["primaryKeyName"] = colName;
for (auto &col : cols)
{
if (col.colName_ == colName)
{
col.isPrimaryKey_ = true;
data["primaryKeyType"] = col.colType_;
}
}
} >>
[](const DrogonDbException &e) {
std::cerr << e.base().what() << std::endl;
exit(1);
};
}
else if (pkNumber > 1)
{
std::vector<std::string> pkNames, pkTypes, pkValNames;
for (size_t i = 1; i <= pkNumber; ++i)
{
*client << "SELECT \
pg_attribute.attname AS colname,\
pg_type.typname AS typename,\
pg_constraint.contype AS contype \
FROM pg_constraint \
INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid \
INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid \
AND pg_attribute.attnum = pg_constraint.conkey [ $1 ] \
INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid \
WHERE pg_class.relname = $2 and pg_constraint.contype='p'"
<< (int)i << tableName << Mode::Blocking >>
[&](bool isNull, std::string colName, const std::string &type) {
if (isNull)
return;
pkNames.push_back(colName);
pkValNames.push_back(nameTransform(colName, false));
for (auto &col : cols)
{
if (col.colName_ == colName)
{
col.isPrimaryKey_ = true;
pkTypes.push_back(col.colType_);
}
}
} >>
[](const DrogonDbException &e) {
std::cerr << e.base().what() << std::endl;
exit(1);
};
}
data["primaryKeyName"] = pkNames;
data["primaryKeyType"] = pkTypes;
data["primaryKeyValNames"] = pkValNames;
}
data["columns"] = cols;
std::ofstream headerFile(path + "/" + className + ".h", std::ofstream::out);
std::ofstream sourceFile(path + "/" + className + ".cc",
std::ofstream::out);
auto templ = DrTemplateBase::newTemplate("model_h.csp");
headerFile << templ->genText(data);
templ = DrTemplateBase::newTemplate("model_cc.csp");
sourceFile << templ->genText(data);
createRestfulAPIController(data, restfulApiConfig);
}
void create_model::createModelFromPG(
const std::string &path,
const DbClientPtr &client,
const std::string &schema,
const Json::Value &restfulApiConfig,
std::map<std::string, std::vector<Relationship>> &relationships,
std::map<std::string, std::vector<ConvertMethod>> &convertMethods)
{
*client << "SELECT a.oid,"
"a.relname AS name,"
"b.description AS comment "
"FROM pg_class a "
"LEFT OUTER JOIN pg_description b ON b.objsubid = 0 AND a.oid = "
"b.objoid "
"WHERE a.relnamespace = (SELECT oid FROM pg_namespace WHERE "
"nspname = $1) "
"AND a.relkind = 'r' ORDER BY a.relname"
<< schema << Mode::Blocking >>
[&](bool isNull,
size_t oid,
std::string &&tableName,
const std::string &comment) {
if (!isNull)
{
std::cout << "table name:" << tableName << std::endl;
createModelClassFromPG(path,
client,
tableName,
schema,
restfulApiConfig,
relationships[tableName],
convertMethods[tableName]);
}
} >>
[](const DrogonDbException &e) {
std::cerr << e.base().what() << std::endl;
exit(1);
};
}
#endif
#if USE_MYSQL
void create_model::createModelClassFromMysql(
const std::string &path,
const DbClientPtr &client,
const std::string &tableName,
const Json::Value &restfulApiConfig,
const std::vector<Relationship> &relationships,
const std::vector<ConvertMethod> &convertMethods)
{
auto className = nameTransform(tableName, true);
HttpViewData data;
data["className"] = className;
data["tableName"] = toLower(tableName);
data["hasPrimaryKey"] = (int)0;
data["primaryKeyName"] = "";
data["dbName"] = dbname_;
data["rdbms"] = std::string("mysql");
data["relationships"] = relationships;
data["convertMethods"] = convertMethods;
std::vector<ColumnInfo> cols;
int i = 0;
*client << "desc " + tableName << Mode::Blocking >>
[&i, &cols](bool isNull,
const std::string &field,
const std::string &type,
const std::string &isNullAble,
const std::string &key,
const std::string &defaultVal,
const std::string &extra) {
if (!isNull)
{
ColumnInfo info;
info.index_ = i;
info.dbType_ = "mysql";
info.colName_ = field;
info.colTypeName_ = nameTransform(info.colName_, true);
info.colValName_ = nameTransform(info.colName_, false);
info.notNull_ = isNullAble == "YES" ? false : true;
info.colDatabaseType_ = type;
info.isPrimaryKey_ = key == "PRI" ? true : false;
if (type.find("tinyint") == 0)
{
info.colType_ = "int8_t";
info.colLength_ = 1;
}
else if (type.find("smallint") == 0)
{
info.colType_ = "int16_t";
info.colLength_ = 2;
}
else if (type.find("int") == 0)
{
info.colType_ = "int32_t";
info.colLength_ = 4;
}
else if (type.find("bigint") == 0)
{
info.colType_ = "int64_t";
info.colLength_ = 8;
}
else if (type.find("float") == 0)
{
info.colType_ = "float";
info.colLength_ = sizeof(float);
}
else if (type.find("double") == 0)
{
info.colType_ = "double";
info.colLength_ = sizeof(double);
}
else if (type.find("date") == 0 || type.find("datetime") == 0 ||
type.find("timestamp") == 0)
{
info.colType_ = "::trantor::Date";
}
else if (type.find("blob") != std::string::npos)
{
info.colType_ = "std::vector<char>";
}
else if (type.find("varchar") != std::string::npos)
{
info.colType_ = "std::string";
auto pos1 = type.find("(");
auto pos2 = type.find(")");
if (pos1 != std::string::npos &&
pos2 != std::string::npos && pos2 - pos1 > 1)
{
info.colLength_ =
std::stoll(type.substr(pos1 + 1, pos2 - pos1 - 1));
}
}
else
{
info.colType_ = "std::string";
}
if (type.find("unsigned") != std::string::npos &&
info.colType_ != "std::string")
{
info.colType_ = "u" + info.colType_;
}
if (!defaultVal.empty())
{
info.hasDefaultVal_ = true;
}
if (extra.find("auto_") == 0)
{
info.isAutoVal_ = true;
}
cols.push_back(std::move(info));
++i;
}
} >>
[](const DrogonDbException &e) {
std::cerr << e.base().what() << std::endl;
exit(1);
};
std::vector<std::string> pkNames, pkTypes, pkValNames;
for (auto const &col : cols)
{
if (col.isPrimaryKey_)
{
pkNames.push_back(col.colName_);
pkValNames.push_back(nameTransform(col.colName_, false));
pkTypes.push_back(col.colType_);
}
}
data["hasPrimaryKey"] = (int)pkNames.size();
if (pkNames.size() == 1)
{
data["primaryKeyName"] = pkNames[0];
data["primaryKeyType"] = pkTypes[0];
}
else if (pkNames.size() > 1)
{
data["primaryKeyName"] = pkNames;
data["primaryKeyType"] = pkTypes;
data["primaryKeyValNames"] = pkValNames;
}
data["columns"] = cols;
std::ofstream headerFile(path + "/" + className + ".h", std::ofstream::out);
std::ofstream sourceFile(path + "/" + className + ".cc",
std::ofstream::out);
auto templ = DrTemplateBase::newTemplate("model_h.csp");
headerFile << templ->genText(data);
templ = DrTemplateBase::newTemplate("model_cc.csp");
sourceFile << templ->genText(data);
createRestfulAPIController(data, restfulApiConfig);
}
void create_model::createModelFromMysql(
const std::string &path,
const DbClientPtr &client,
const Json::Value &restfulApiConfig,
std::map<std::string, std::vector<Relationship>> &relationships,
std::map<std::string, std::vector<ConvertMethod>> &convertMethods)
{
*client << "show tables" << Mode::Blocking >> [&](bool isNull,
std::string &&tableName) {
if (!isNull)
{
std::cout << "table name:" << tableName << std::endl;
createModelClassFromMysql(path,
client,
tableName,
restfulApiConfig,
relationships[tableName],
convertMethods[tableName]);
}
} >> [](const DrogonDbException &e) {
std::cerr << e.base().what() << std::endl;
exit(1);
};
}
#endif
#if USE_SQLITE3
void create_model::createModelClassFromSqlite3(
const std::string &path,
const DbClientPtr &client,
const std::string &tableName,
const Json::Value &restfulApiConfig,
const std::vector<Relationship> &relationships,
const std::vector<ConvertMethod> &convertMethods)
{
HttpViewData data;
auto className = nameTransform(tableName, true);
data["className"] = className;
data["tableName"] = toLower(tableName);
data["hasPrimaryKey"] = (int)0;
data["primaryKeyName"] = "";
data["dbName"] = std::string("sqlite3");
data["rdbms"] = std::string("sqlite3");
data["relationships"] = relationships;
data["convertMethods"] = convertMethods;
std::vector<ColumnInfo> cols;
std::string sql = "PRAGMA table_info(" + tableName + ");";
*client << sql << Mode::Blocking >> [&](const Result &result) {
size_t index = 0;
for (auto &row : result)
{
bool notnull = row["notnull"].as<bool>();
bool primary = row["pk"].as<int>();
auto type = row["type"].as<std::string>();
std::transform(type.begin(), type.end(), type.begin(), tolower);
ColumnInfo info;
info.index_ = index++;
info.dbType_ = "sqlite3";
info.colName_ = row["name"].as<std::string>();
info.colTypeName_ = nameTransform(info.colName_, true);
info.colValName_ = nameTransform(info.colName_, false);
info.notNull_ = notnull;
info.colDatabaseType_ = type;
info.isPrimaryKey_ = primary;
if (primary)
{
if (type == "integer")
{
info.isAutoVal_ = true;
}
else
{
*client << "SELECT sql FROM sqlite_master WHERE name=? and "
"(type='table' or type='view');"
<< tableName << Mode::Blocking >>
[&](bool isNull, std::string sql) {
if (!isNull)
{
std::transform(sql.begin(),
sql.end(),
sql.begin(),
tolower);
if (sql.find("autoincrement") !=
std::string::npos)
{
info.isAutoVal_ = true;
}
}
} >>
[](const DrogonDbException &e) {
};
}
}
auto defaultVal = row["dflt_value"].as<std::string>();
if (!defaultVal.empty())
{
info.hasDefaultVal_ = true;
}
if (type.find("int") != std::string::npos)
{
info.colType_ = "uint64_t";
info.colLength_ = 8;
}
else if (type.find("char") != std::string::npos || type == "text" ||
type == "clob")
{
info.colType_ = "std::string";
}
else if (type.find("double") != std::string::npos ||
type == "real" || type == "float")
{
info.colType_ = "double";
info.colLength_ = sizeof(double);
}
else if (type == "bool")
{
info.colType_ = "bool";
info.colLength_ = 1;
}
else if (type == "blob")
{
info.colType_ = "std::vector<char>";
}
else if (type == "datetime" || type == "date")
{
info.colType_ = "::trantor::Date";
}
else
{
info.colType_ = "std::string";
}
cols.push_back(std::move(info));
}
} >> [](const DrogonDbException &e) {
std::cerr << e.base().what() << std::endl;
exit(1);
};
std::vector<std::string> pkNames, pkTypes, pkValNames;
for (auto const &col : cols)
{
if (col.isPrimaryKey_)
{
pkNames.push_back(col.colName_);
pkTypes.push_back(col.colType_);
pkValNames.push_back(nameTransform(col.colName_, false));
}
}
data["hasPrimaryKey"] = (int)pkNames.size();
if (pkNames.size() == 1)
{
data["primaryKeyName"] = pkNames[0];
data["primaryKeyType"] = pkTypes[0];
}
else if (pkNames.size() > 1)
{
data["primaryKeyName"] = pkNames;
data["primaryKeyType"] = pkTypes;
data["primaryKeyValNames"] = pkValNames;
}
data["columns"] = cols;
std::ofstream headerFile(path + "/" + className + ".h", std::ofstream::out);
std::ofstream sourceFile(path + "/" + className + ".cc",
std::ofstream::out);
auto templ = DrTemplateBase::newTemplate("model_h.csp");
headerFile << templ->genText(data);
templ = DrTemplateBase::newTemplate("model_cc.csp");
sourceFile << templ->genText(data);
createRestfulAPIController(data, restfulApiConfig);
}
void create_model::createModelFromSqlite3(
const std::string &path,
const DbClientPtr &client,
const Json::Value &restfulApiConfig,
std::map<std::string, std::vector<Relationship>> &relationships,
std::map<std::string, std::vector<ConvertMethod>> &convertMethods)
{
*client << "SELECT name FROM sqlite_master WHERE name!='sqlite_sequence' "
"and (type='table' or type='view') ORDER BY name;"
<< Mode::Blocking >>
[&](bool isNull, std::string &&tableName) mutable {
if (!isNull)
{
std::cout << "table name:" << tableName << std::endl;
createModelClassFromSqlite3(path,
client,
tableName,
restfulApiConfig,
relationships[tableName],
convertMethods[tableName]);
}
} >>
[](const DrogonDbException &e) {
std::cerr << e.base().what() << std::endl;
exit(1);
};
}
#endif
void create_model::createModel(const std::string &path,
const Json::Value &config,
const std::string &singleModelName)
{
auto dbType = config.get("rdbms", "no dbms").asString();
std::transform(dbType.begin(), dbType.end(), dbType.begin(), tolower);
auto restfulApiConfig = config["restful_api_controllers"];
auto relationships = getRelationships(config["relationships"]);
auto convertMethods = getConvertMethods(config["convert"]);
if (dbType == "postgresql")
{
#if USE_POSTGRESQL
std::cout << "postgresql" << std::endl;
auto host = config.get("host", "127.0.0.1").asString();
auto port = config.get("port", 5432).asUInt();
auto dbname = config.get("dbname", "").asString();
if (dbname == "")
{
std::cerr << "Please configure dbname in " << path << "/model.json "
<< std::endl;
exit(1);
}
dbname_ = dbname;
auto user = config.get("user", "").asString();
if (user == "")
{
std::cerr << "Please configure user in " << path << "/model.json "
<< std::endl;
exit(1);
}
auto password = config.get("passwd", "").asString();
if (password.empty())
{
password = config.get("password", "").asString();
}
auto connStr =
utils::formattedString("host=%s port=%u dbname=%s user=%s",
escapeConnString(host).c_str(),
port,
escapeConnString(dbname).c_str(),
escapeConnString(user).c_str());
if (!password.empty())
{
connStr += " password=";
connStr += escapeConnString(password);
}
auto characterSet = config.get("client_encoding", "").asString();
if (!characterSet.empty())
{
connStr += " client_encoding=";
connStr += escapeConnString(characterSet);
}
auto schema = config.get("schema", "public").asString();
DbClientPtr client = drogon::orm::DbClient::newPgClient(connStr, 1);
std::cout << "Connect to server..." << std::endl;
if (forceOverwrite_)
{
std::this_thread::sleep_for(2s);
}
else
{
std::cout << "Source files in the " << path
<< " folder will be overwritten, continue(y/n)?\n";
auto in = getchar();
(void)getchar(); // get the return key
if (in != 'Y' && in != 'y')
{
std::cout << "Abort!" << std::endl;
exit(0);
}
}
if (singleModelName.empty())
{
auto tables = config["tables"];
if (!tables || tables.size() == 0)
createModelFromPG(path,
client,
schema,
restfulApiConfig,
relationships,
convertMethods);
else
{
for (int i = 0; i < (int)tables.size(); ++i)
{
auto tableName = tables[i].asString();
std::transform(tableName.begin(),
tableName.end(),
tableName.begin(),
tolower);
std::cout << "table name:" << tableName << std::endl;
createModelClassFromPG(path,
client,
tableName,
schema,
restfulApiConfig,
relationships[tableName],
convertMethods[tableName]);
}
}
}
else
{
createModelClassFromPG(path,
client,
singleModelName,
schema,
restfulApiConfig,
relationships[singleModelName],
convertMethods[singleModelName]);
}
#else
std::cerr
<< "Drogon does not support PostgreSQL, please install PostgreSQL "
"development environment before installing drogon"
<< std::endl;
#endif
}
else if (dbType == "mysql")
{
#if USE_MYSQL
std::cout << "mysql" << std::endl;
auto host = config.get("host", "127.0.0.1").asString();
auto port = config.get("port", 5432).asUInt();
auto dbname = config.get("dbname", "").asString();
if (dbname == "")
{
std::cerr << "Please configure dbname in " << path << "/model.json "
<< std::endl;
exit(1);
}
dbname_ = dbname;
auto user = config.get("user", "").asString();
if (user == "")
{
std::cerr << "Please configure user in " << path << "/model.json "
<< std::endl;
exit(1);
}
auto password = config.get("passwd", "").asString();
if (password.empty())
{
password = config.get("password", "").asString();
}
auto connStr =
utils::formattedString("host=%s port=%u dbname=%s user=%s",
escapeConnString(host).c_str(),
port,
escapeConnString(dbname).c_str(),
escapeConnString(user).c_str());
if (!password.empty())
{
connStr += " password=";
connStr += escapeConnString(password);
}
auto characterSet = config.get("client_encoding", "").asString();
if (!characterSet.empty())
{
connStr += " client_encoding=";
connStr += escapeConnString(characterSet);
}
DbClientPtr client = drogon::orm::DbClient::newMysqlClient(connStr, 1);
std::cout << "Connect to server..." << std::endl;
if (forceOverwrite_)
{
std::this_thread::sleep_for(2s);
}
else
{
std::cout << "Source files in the " << path
<< " folder will be overwritten, continue(y/n)?\n";
auto in = getchar();
(void)getchar(); // get the return key
if (in != 'Y' && in != 'y')
{
std::cout << "Abort!" << std::endl;
exit(0);
}
}
if (singleModelName.empty())
{
auto tables = config["tables"];
if (!tables || tables.size() == 0)
createModelFromMysql(path,
client,
restfulApiConfig,
relationships,
convertMethods);
else
{
for (int i = 0; i < (int)tables.size(); ++i)
{
auto tableName = tables[i].asString();
std::transform(tableName.begin(),
tableName.end(),
tableName.begin(),
tolower);
std::cout << "table name:" << tableName << std::endl;
createModelClassFromMysql(path,
client,
tableName,
restfulApiConfig,
relationships[tableName],
convertMethods[tableName]);
}
}
}
else
{
createModelClassFromMysql(path,
client,
singleModelName,
restfulApiConfig,
relationships[singleModelName],
convertMethods[singleModelName]);
}
#else
std::cerr << "Drogon does not support Mysql, please install MariaDB "
"development environment before installing drogon"
<< std::endl;
#endif
}
else if (dbType == "sqlite3")
{
#if USE_SQLITE3
auto filename = config.get("filename", "").asString();
if (filename == "")
{
std::cerr << "Please configure filename in " << path
<< "/model.json " << std::endl;
exit(1);
}
std::string connStr = "filename=" + escapeConnString(filename);
DbClientPtr client =
drogon::orm::DbClient::newSqlite3Client(connStr, 1);
std::cout << "Connect..." << std::endl;
if (forceOverwrite_)
{
std::this_thread::sleep_for(1s);
}
else
{
std::cout << "Source files in the " << path
<< " folder will be overwritten, continue(y/n)?\n";
auto in = getchar();
(void)getchar(); // get the return key
if (in != 'Y' && in != 'y')
{
std::cout << "Abort!" << std::endl;
exit(0);
}
}
if (singleModelName.empty())
{
auto tables = config["tables"];
if (!tables || tables.size() == 0)
createModelFromSqlite3(path,
client,
restfulApiConfig,
relationships,
convertMethods);
else
{
for (int i = 0; i < (int)tables.size(); ++i)
{
auto tableName = tables[i].asString();
std::transform(tableName.begin(),
tableName.end(),
tableName.begin(),
tolower);
std::cout << "table name:" << tableName << std::endl;
createModelClassFromSqlite3(path,
client,
tableName,
restfulApiConfig,
relationships[tableName],
convertMethods[tableName]);
}
}
}
else
{
createModelClassFromSqlite3(path,
client,
singleModelName,
restfulApiConfig,
relationships[singleModelName],
convertMethods[singleModelName]);
}
#else
std::cerr << "Drogon does not support Sqlite3, please install Sqlite3 "
"development environment before installing drogon"
<< std::endl;
#endif
}
else if (dbType == "no dbms")
{
std::cerr << "Please configure Model in " << path << "/model.json "
<< std::endl;
exit(1);
}
else
{
std::cerr << "Does not support " << dbType << std::endl;
exit(1);
}
}
void create_model::createModel(const std::string &path,
const std::string &singleModelName)
{
#ifndef _WIN32
DIR *dp;
if ((dp = opendir(path.c_str())) == NULL)
{
std::cerr << "No such file or directory : " << path << std::endl;
return;
}
closedir(dp);
#endif
auto configFile = path + "/model.json";
#ifdef _WIN32
if (_access(configFile.c_str(), 0) != 0)
#else
if (access(configFile.c_str(), 0) != 0)
#endif
{
std::cerr << "Config file " << configFile << " not found!" << std::endl;
exit(1);
}
#ifdef _WIN32
if (_access(configFile.c_str(), 04) != 0)
#else
if (access(configFile.c_str(), R_OK) != 0)
#endif
{
std::cerr << "No permission to read config file " << configFile
<< std::endl;
exit(1);
}
std::ifstream infile(configFile.c_str(), std::ifstream::in);
if (infile)
{
Json::Value configJsonRoot;
try
{
infile >> configJsonRoot;
createModel(path, configJsonRoot, singleModelName);
}
catch (const std::exception &exception)
{
std::cerr << "Configuration file format error! in " << configFile
<< ":" << std::endl;
std::cerr << exception.what() << std::endl;
exit(1);
}
}
}
void create_model::handleCommand(std::vector<std::string> &parameters)
{
std::cout << "Create model" << std::endl;
if (parameters.size() == 0)
{
std::cerr << "Missing Model path name!" << std::endl;
}
std::string singleModelName;
for (auto iter = parameters.begin(); iter != parameters.end(); ++iter)
{
if ((*iter).find("--table=") == 0)
{
singleModelName = (*iter).substr(8);
parameters.erase(iter);
break;
}
}
for (auto iter = parameters.begin(); iter != parameters.end(); ++iter)
{
if ((*iter) == "-f")
{
forceOverwrite_ = true;
parameters.erase(iter);
break;
}
}
for (auto const &path : parameters)
{
createModel(path, singleModelName);
}
}
void create_model::createRestfulAPIController(
const DrTemplateData &tableInfo,
const Json::Value &restfulApiConfig)
{
if (restfulApiConfig.isNull())
return;
if (!restfulApiConfig.get("enabled", false).asBool())
{
return;
}
auto genBaseOnly =
restfulApiConfig.get("generate_base_only", false).asBool();
auto modelClassName = tableInfo.get<std::string>("className");
std::regex regex("\\*");
auto resource = std::regex_replace(
restfulApiConfig.get("resource_uri", "/*").asString(),
regex,
modelClassName);
std::transform(resource.begin(), resource.end(), resource.begin(), tolower);
auto ctrlClassName =
std::regex_replace(restfulApiConfig.get("class_name", "/*").asString(),
regex,
modelClassName);
std::regex regex1("::");
std::string ctlName =
std::regex_replace(ctrlClassName, regex1, std::string("_"));
auto v = utils::splitString(ctrlClassName, "::");
drogon::DrTemplateData data;
data.insert("className", v[v.size() - 1]);
v.pop_back();
data.insert("namespaceVector", v);
data.insert("resource", resource);
data.insert("fileName", ctlName);
data.insert("tableName", tableInfo.get<std::string>("tableName"));
data.insert("tableInfo", tableInfo);
auto filters = restfulApiConfig["filters"];
if (filters.isNull() || filters.empty() || !filters.isArray())
{
data.insert("filters", "");
}
else
{
std::string filtersStr;
for (auto &filterName : filters)
{
filtersStr += ",\"";
filtersStr.append(filterName.asString());
filtersStr += '"';
}
data.insert("filters", filtersStr);
}
auto dbClientConfig = restfulApiConfig["db_client"];
if (dbClientConfig.isNull() || dbClientConfig.empty())
{
data.insertAsString("dbClientName", "default");
data.insert("isFastDbClient", false);
}
else
{
auto clientName = dbClientConfig.get("name", "default").asString();
auto isFast = dbClientConfig.get("is_fast", false).asBool();
data.insertAsString("dbClientName", clientName);
data.insert("isFastDbClient", isFast);
}
auto dir = restfulApiConfig.get("directory", "controllers").asString();
if (dir[dir.length() - 1] != '/')
{
dir += '/';
}
{
std::string headFileName = dir + ctlName + "Base.h";
std::string sourceFilename = dir + ctlName + "Base.cc";
// {
// std::ifstream iHeadFile(headFileName.c_str(), std::ifstream::in);
// std::ifstream iSourceFile(sourceFilename.c_str(),
// std::ifstream::in);
// if (iHeadFile || iSourceFile)
// {
// std::cout << "The " << headFileName << " and " <<
// sourceFilename
// << " you want to create already exist, "
// "overwrite it(y/n)?"
// << std::endl;
// auto in = getchar();
// (void)getchar(); // get the return key
// if (in != 'Y' && in != 'y')
// {
// std::cout << "Abort!" << std::endl;
// exit(0);
// }
// }
// }
std::ofstream oHeadFile(headFileName.c_str(), std::ofstream::out);
std::ofstream oSourceFile(sourceFilename.c_str(), std::ofstream::out);
if (!oHeadFile || !oSourceFile)
{
perror("");
exit(1);
}
try
{
auto templ =
DrTemplateBase::newTemplate("restful_controller_base_h.csp");
oHeadFile << templ->genText(data);
templ =
DrTemplateBase::newTemplate("restful_controller_base_cc.csp");
oSourceFile << templ->genText(data);
}
catch (const std::exception &err)
{
std::cerr << err.what() << std::endl;
exit(1);
}
std::cout << "create a http restful API controller base class:"
<< ctrlClassName << "Base" << std::endl;
std::cout << "file name: " << headFileName << ", " << sourceFilename
<< std::endl
<< std::endl;
}
if (!genBaseOnly)
{
std::string headFileName = dir + ctlName + ".h";
std::string sourceFilename = dir + ctlName + ".cc";
if (!forceOverwrite_)
{
std::ifstream iHeadFile(headFileName.c_str(), std::ifstream::in);
std::ifstream iSourceFile(sourceFilename.c_str(),
std::ifstream::in);
if (iHeadFile || iSourceFile)
{
std::cout << "The " << headFileName << " and " << sourceFilename
<< " you want to create already exist, "
"overwrite them(y/n)?"
<< std::endl;
auto in = getchar();
(void)getchar(); // get the return key
if (in != 'Y' && in != 'y')
{
std::cout << "Abort!" << std::endl;
exit(0);
}
}
}
std::ofstream oHeadFile(headFileName.c_str(), std::ofstream::out);
std::ofstream oSourceFile(sourceFilename.c_str(), std::ofstream::out);
if (!oHeadFile || !oSourceFile)
{
perror("");
exit(1);
}
try
{
auto templ =
DrTemplateBase::newTemplate("restful_controller_custom_h.csp");
oHeadFile << templ->genText(data);
templ =
DrTemplateBase::newTemplate("restful_controller_custom_cc.csp");
oSourceFile << templ->genText(data);
}
catch (const std::exception &err)
{
std::cerr << err.what() << std::endl;
exit(1);
}
std::cout << "create a http restful API controller class: "
<< ctrlClassName << std::endl;
std::cout << "file name: " << headFileName << ", " << sourceFilename
<< std::endl
<< std::endl;
}
}