2018-10-31 16:45:27 +08:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* create_model.cc
|
|
|
|
* An Tao
|
|
|
|
*
|
|
|
|
* Copyright 2018, An Tao. All rights reserved.
|
|
|
|
* Use of this source code is governed by a MIT license
|
|
|
|
* that can be found in the License file.
|
|
|
|
*
|
2018-11-16 13:26:14 +08:00
|
|
|
* Drogon
|
2018-10-31 16:45:27 +08:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#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 <json/json.h>
|
|
|
|
#include <iostream>
|
|
|
|
#include <fstream>
|
|
|
|
#include <regex>
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <dlfcn.h>
|
|
|
|
#include <fstream>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
using namespace drogon_ctl;
|
|
|
|
|
|
|
|
std::string nameTransform(const std::string &origName, bool isType)
|
|
|
|
{
|
|
|
|
auto str = origName;
|
|
|
|
std::transform(str.begin(), str.end(), str.begin(), tolower);
|
|
|
|
std::string::size_type startPos = 0;
|
|
|
|
std::string::size_type pos;
|
|
|
|
std::string ret;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
pos = str.find("_", startPos);
|
|
|
|
if (pos != std::string::npos)
|
|
|
|
ret += str.substr(startPos, pos - startPos);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret += str.substr(startPos);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
while (str[pos] == '_')
|
|
|
|
pos++;
|
|
|
|
if (str[pos] >= 'a' && str[pos] <= 'z')
|
|
|
|
str[pos] += ('A' - 'a');
|
|
|
|
startPos = pos;
|
|
|
|
} while (1);
|
|
|
|
if (isType && ret[0] >= 'a' && ret[0] <= 'z')
|
|
|
|
ret[0] += ('A' - 'a');
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if USE_POSTGRESQL
|
2018-11-11 12:02:48 +08:00
|
|
|
void create_model::createModelClassFromPG(const std::string &path, const DbClientPtr &client, const std::string &tableName)
|
2018-10-31 16:45:27 +08:00
|
|
|
{
|
|
|
|
auto className = nameTransform(tableName, true);
|
|
|
|
HttpViewData data;
|
|
|
|
data["className"] = className;
|
|
|
|
data["tableName"] = tableName;
|
2018-11-07 18:46:16 +08:00
|
|
|
data["hasPrimaryKey"] = (int)0;
|
2018-10-31 16:45:27 +08:00
|
|
|
data["primaryKeyName"] = "";
|
|
|
|
data["dbName"] = _dbname;
|
|
|
|
std::vector<ColumnInfo> cols;
|
2018-11-11 12:02:48 +08:00
|
|
|
*client << "SELECT * \
|
2018-10-31 16:45:27 +08:00
|
|
|
FROM information_schema.columns \
|
|
|
|
WHERE table_schema = 'public' \
|
|
|
|
AND table_name = $1"
|
2018-11-11 12:02:48 +08:00
|
|
|
<< tableName << Mode::Blocking >>
|
2018-10-31 16:45:27 +08:00
|
|
|
[&](const Result &r) {
|
2018-11-19 18:15:41 +08:00
|
|
|
for (size_t i = 0; i < r.size(); i++)
|
2018-10-31 16:45:27 +08:00
|
|
|
{
|
2018-11-17 11:40:17 +08:00
|
|
|
auto row = r[i];
|
2018-10-31 16:45:27 +08:00
|
|
|
ColumnInfo info;
|
2018-11-17 11:40:17 +08:00
|
|
|
info._index = i;
|
2018-11-05 16:00:49 +08:00
|
|
|
info._dbType = "pg";
|
2018-10-31 16:45:27 +08:00
|
|
|
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>();
|
2018-11-05 16:00:49 +08:00
|
|
|
info._colDatabaseType = type;
|
2018-10-31 16:45:27 +08:00
|
|
|
if (type == "smallint")
|
|
|
|
{
|
|
|
|
info._colType = "short";
|
|
|
|
info._colLength = 2;
|
|
|
|
}
|
|
|
|
else if (type == "integer")
|
|
|
|
{
|
|
|
|
info._colType = "int32_t";
|
|
|
|
info._colLength = 4;
|
|
|
|
}
|
|
|
|
else if (type == "bigint" || type == "numeric") //FIXME:Use int64 to represent numeric type?
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2018-11-07 18:46:16 +08:00
|
|
|
else if (type == "date")
|
2018-11-05 16:00:49 +08:00
|
|
|
{
|
|
|
|
info._colType = "::trantor::Date";
|
|
|
|
info._colLength = 4;
|
|
|
|
}
|
2018-11-08 16:31:39 +08:00
|
|
|
else if (type.find("timestamp") != std::string::npos)
|
|
|
|
{
|
|
|
|
info._colType = "::trantor::Date";
|
|
|
|
}
|
2018-11-19 18:15:41 +08:00
|
|
|
else if (type == "bytea")
|
|
|
|
{
|
|
|
|
info._colType = "std::string";
|
|
|
|
}
|
2018-11-07 18:46:16 +08:00
|
|
|
else
|
2018-10-31 16:45:27 +08:00
|
|
|
{
|
2018-11-15 09:17:08 +08:00
|
|
|
info._colType = "std::string";
|
|
|
|
//FIXME add more type such as hstore,blob...
|
2018-10-31 16:45:27 +08:00
|
|
|
}
|
|
|
|
auto defaultVal = row["column_default"].as<std::string>();
|
|
|
|
|
|
|
|
if (!defaultVal.empty())
|
|
|
|
{
|
|
|
|
info._hasDefaultVal = true;
|
|
|
|
if (defaultVal.find("nextval(") == 0)
|
|
|
|
{
|
|
|
|
info._isAutoVal = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cols.push_back(std::move(info));
|
|
|
|
}
|
|
|
|
} >>
|
|
|
|
[](const DrogonDbException &e) {
|
|
|
|
std::cerr << e.base().what() << std::endl;
|
|
|
|
exit(1);
|
|
|
|
};
|
2018-11-11 12:16:26 +08:00
|
|
|
|
2018-11-07 18:46:16 +08:00
|
|
|
size_t pkNumber = 0;
|
2018-11-11 12:02:48 +08:00
|
|
|
*client << "SELECT \
|
2018-11-07 18:46:16 +08:00
|
|
|
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'"
|
2018-11-11 12:02:48 +08:00
|
|
|
<< tableName
|
|
|
|
<< Mode::Blocking >>
|
2018-11-07 18:46:16 +08:00
|
|
|
[&](bool isNull, const std::string &pkName, const std::vector<std::shared_ptr<short>> &pk) {
|
|
|
|
if (!isNull)
|
|
|
|
{
|
2018-11-08 11:05:50 +08:00
|
|
|
//std::cout << tableName << " Primary key = " << pk.size() << std::endl;
|
2018-11-07 18:46:16 +08:00
|
|
|
pkNumber = pk.size();
|
|
|
|
}
|
|
|
|
} >>
|
|
|
|
[](const DrogonDbException &e) {
|
|
|
|
std::cerr << e.base().what() << std::endl;
|
|
|
|
exit(1);
|
|
|
|
};
|
|
|
|
data["hasPrimaryKey"] = (int)pkNumber;
|
|
|
|
if (pkNumber == 1)
|
|
|
|
{
|
2018-11-11 12:02:48 +08:00
|
|
|
*client << "SELECT \
|
2018-10-31 16:45:27 +08:00
|
|
|
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'"
|
2018-11-11 12:02:48 +08:00
|
|
|
<< tableName << Mode::Blocking >>
|
2018-11-07 18:46:16 +08:00
|
|
|
[&](bool isNull, std::string colName, const std::string &type) {
|
|
|
|
if (isNull)
|
|
|
|
return;
|
2018-11-08 16:31:39 +08:00
|
|
|
|
2018-11-07 18:46:16 +08:00
|
|
|
data["primaryKeyName"] = colName;
|
|
|
|
for (auto &col : cols)
|
2018-10-31 16:45:27 +08:00
|
|
|
{
|
2018-11-07 18:46:16 +08:00
|
|
|
if (col._colName == colName)
|
|
|
|
{
|
|
|
|
col._isPrimaryKey = true;
|
|
|
|
data["primaryKeyType"] = col._colType;
|
|
|
|
}
|
2018-10-31 16:45:27 +08:00
|
|
|
}
|
2018-11-07 18:46:16 +08:00
|
|
|
} >>
|
|
|
|
[](const DrogonDbException &e) {
|
|
|
|
std::cerr << e.base().what() << std::endl;
|
|
|
|
exit(1);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else if (pkNumber > 1)
|
|
|
|
{
|
|
|
|
std::vector<std::string> pkNames, pkTypes;
|
2018-11-09 15:55:20 +08:00
|
|
|
for (size_t i = 1; i <= pkNumber; i++)
|
2018-11-07 18:46:16 +08:00
|
|
|
{
|
2018-11-11 12:02:48 +08:00
|
|
|
*client << "SELECT \
|
2018-11-07 18:46:16 +08:00
|
|
|
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'"
|
2018-11-11 12:27:17 +08:00
|
|
|
<< (int)i
|
2018-11-11 12:02:48 +08:00
|
|
|
<< tableName
|
|
|
|
<< Mode::Blocking >>
|
2018-11-07 18:46:16 +08:00
|
|
|
[&](bool isNull, std::string colName, const std::string &type) {
|
|
|
|
if (isNull)
|
|
|
|
return;
|
2018-11-08 11:05:50 +08:00
|
|
|
//std::cout << "primary key name=" << colName << std::endl;
|
2018-11-07 18:46:16 +08:00
|
|
|
pkNames.push_back(colName);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-10-31 16:45:27 +08:00
|
|
|
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);
|
|
|
|
}
|
2018-11-11 12:02:48 +08:00
|
|
|
void create_model::createModelFromPG(const std::string &path, const DbClientPtr &client)
|
2018-10-31 16:45:27 +08:00
|
|
|
{
|
2018-11-11 12:02:48 +08:00
|
|
|
*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 = 'public') "
|
|
|
|
"AND a.relkind = 'r' ORDER BY a.relname"
|
|
|
|
<< Mode::Blocking >>
|
2018-10-31 16:45:27 +08:00
|
|
|
[&](bool isNull, size_t oid, const std::string &tableName, const std::string &comment) {
|
|
|
|
if (!isNull)
|
|
|
|
{
|
|
|
|
std::cout << "table name:" << tableName << std::endl;
|
|
|
|
createModelClassFromPG(path, client, 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)
|
|
|
|
{
|
|
|
|
auto dbType = config.get("rdbms", "No dbms").asString();
|
|
|
|
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();
|
|
|
|
|
|
|
|
auto connStr = formattedString("host=%s port=%u dbname=%s user=%s", host.c_str(), port, dbname.c_str(), user.c_str());
|
|
|
|
if (!password.empty())
|
|
|
|
{
|
|
|
|
connStr += " password=";
|
|
|
|
connStr += password;
|
|
|
|
}
|
2018-11-11 12:02:48 +08:00
|
|
|
DbClientPtr client = drogon::orm::DbClient::newPgClient(connStr, 1);
|
2018-10-31 16:45:27 +08:00
|
|
|
std::cout << "Connect to server..." << std::endl;
|
2018-11-01 17:58:32 +08:00
|
|
|
std::cout << "Source files in the " << path << " folder will be overwritten, continue(y/n)?\n";
|
|
|
|
auto in = getchar();
|
|
|
|
if (in != 'Y' && in != 'y')
|
|
|
|
{
|
|
|
|
std::cout << "Abort!" << std::endl;
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
auto tables = config["tables"];
|
|
|
|
if (!tables || tables.size() == 0)
|
|
|
|
createModelFromPG(path, client);
|
|
|
|
else
|
|
|
|
{
|
2018-11-01 22:14:53 +08:00
|
|
|
for (int i = 0; i < (int)tables.size(); i++)
|
2018-11-01 17:58:32 +08:00
|
|
|
{
|
|
|
|
auto tableName = tables[i].asString();
|
|
|
|
std::cout << "table name:" << tableName << std::endl;
|
|
|
|
createModelClassFromPG(path, client, tableName);
|
|
|
|
}
|
|
|
|
}
|
2018-10-31 16:45:27 +08:00
|
|
|
#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)
|
|
|
|
{
|
|
|
|
DIR *dp;
|
|
|
|
if ((dp = opendir(path.c_str())) == NULL)
|
|
|
|
{
|
|
|
|
std::cerr << "No such file or directory : " << path << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
closedir(dp);
|
|
|
|
auto configFile = path + "/model.json";
|
|
|
|
if (access(configFile.c_str(), 0) != 0)
|
|
|
|
{
|
|
|
|
std::cerr << "Config file " << configFile << " not found!" << std::endl;
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
if (access(configFile.c_str(), R_OK) != 0)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
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> ¶meters)
|
|
|
|
{
|
2018-11-02 10:03:50 +08:00
|
|
|
#if USE_POSTGRESQL
|
2018-10-31 16:45:27 +08:00
|
|
|
std::cout << "Create model" << std::endl;
|
|
|
|
if (parameters.size() == 0)
|
|
|
|
{
|
|
|
|
std::cerr << "Missing Model path name!" << std::endl;
|
|
|
|
}
|
|
|
|
for (auto path : parameters)
|
|
|
|
{
|
|
|
|
createModel(path);
|
|
|
|
}
|
2018-11-02 10:03:50 +08:00
|
|
|
#else
|
|
|
|
std::cout << "No database can be found in your system, please install one first!" << std::endl;
|
|
|
|
exit(1);
|
|
|
|
#endif
|
2018-10-31 16:45:27 +08:00
|
|
|
}
|