Implement relationships in ORM (#288)
This commit is contained in:
parent
4868aa964d
commit
876e21f492
|
@ -31,44 +31,51 @@
|
|||
|
||||
using namespace drogon_ctl;
|
||||
|
||||
std::string nameTransform(const std::string &origName, bool isType)
|
||||
static std::map<std::string, std::vector<Relationship>> getRelationships(
|
||||
const Json::Value &relationships)
|
||||
{
|
||||
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
|
||||
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())
|
||||
{
|
||||
pos = str.find("_", startPos);
|
||||
if (pos == std::string::npos)
|
||||
std::cerr << "items must be an array\n";
|
||||
exit(1);
|
||||
}
|
||||
for (auto &relationship : items)
|
||||
{
|
||||
try
|
||||
{
|
||||
pos = str.find(".", startPos);
|
||||
Relationship r(relationship);
|
||||
ret[r.originalTableName()].push_back(r);
|
||||
if (r.enableReverse() &&
|
||||
r.originalTableName() != r.targetTableName())
|
||||
{
|
||||
auto reverse = r.reverse();
|
||||
ret[reverse.originalTableName()].push_back(reverse);
|
||||
}
|
||||
}
|
||||
if (pos != std::string::npos)
|
||||
ret += str.substr(startPos, pos - startPos);
|
||||
else
|
||||
catch (const std::runtime_error &e)
|
||||
{
|
||||
ret += str.substr(startPos);
|
||||
break;
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
while (str[pos] == '_' || 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
|
||||
void create_model::createModelClassFromPG(const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const std::string &tableName,
|
||||
const std::string &schema,
|
||||
const Json::Value &restfulApiConfig)
|
||||
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)
|
||||
{
|
||||
auto className = nameTransform(tableName, true);
|
||||
HttpViewData data;
|
||||
|
@ -78,6 +85,7 @@ void create_model::createModelClassFromPG(const std::string &path,
|
|||
data["primaryKeyName"] = "";
|
||||
data["dbName"] = _dbname;
|
||||
data["rdbms"] = std::string("postgresql");
|
||||
data["relationships"] = relationships;
|
||||
if (schema != "public")
|
||||
{
|
||||
data["schema"] = schema;
|
||||
|
@ -290,10 +298,12 @@ void create_model::createModelClassFromPG(const std::string &path,
|
|||
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)
|
||||
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)
|
||||
{
|
||||
*client << "SELECT a.oid,"
|
||||
"a.relname AS name,"
|
||||
|
@ -307,13 +317,22 @@ void create_model::createModelFromPG(const std::string &path,
|
|||
<< schema << Mode::Blocking >>
|
||||
[&](bool isNull,
|
||||
size_t oid,
|
||||
const std::string &tableName,
|
||||
std::string &&tableName,
|
||||
const std::string &comment) {
|
||||
if (!isNull)
|
||||
{
|
||||
std::transform(tableName.begin(),
|
||||
tableName.end(),
|
||||
tableName.begin(),
|
||||
tolower);
|
||||
std::cout << "table name:" << tableName << std::endl;
|
||||
createModelClassFromPG(
|
||||
path, client, tableName, schema, restfulApiConfig);
|
||||
|
||||
createModelClassFromPG(path,
|
||||
client,
|
||||
tableName,
|
||||
schema,
|
||||
restfulApiConfig,
|
||||
relationships[tableName]);
|
||||
}
|
||||
} >>
|
||||
[](const DrogonDbException &e) {
|
||||
|
@ -328,7 +347,8 @@ void create_model::createModelClassFromMysql(
|
|||
const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const std::string &tableName,
|
||||
const Json::Value &restfulApiConfig)
|
||||
const Json::Value &restfulApiConfig,
|
||||
const std::vector<Relationship> &relationships)
|
||||
{
|
||||
auto className = nameTransform(tableName, true);
|
||||
HttpViewData data;
|
||||
|
@ -338,6 +358,7 @@ void create_model::createModelClassFromMysql(
|
|||
data["primaryKeyName"] = "";
|
||||
data["dbName"] = _dbname;
|
||||
data["rdbms"] = std::string("mysql");
|
||||
data["relationships"] = relationships;
|
||||
std::vector<ColumnInfo> cols;
|
||||
int i = 0;
|
||||
*client << "desc " + tableName << Mode::Blocking >>
|
||||
|
@ -464,25 +485,31 @@ void create_model::createModelClassFromMysql(
|
|||
sourceFile << templ->genText(data);
|
||||
createRestfulAPIController(data, restfulApiConfig);
|
||||
}
|
||||
void create_model::createModelFromMysql(const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const Json::Value &restfulApiConfig)
|
||||
void create_model::createModelFromMysql(
|
||||
const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const Json::Value &restfulApiConfig,
|
||||
std::map<std::string, std::vector<Relationship>> &relationships)
|
||||
{
|
||||
*client << "show tables" << Mode::Blocking >>
|
||||
[&](bool isNull, const std::string &tableName) {
|
||||
if (!isNull)
|
||||
{
|
||||
std::cout << "table name:" << tableName << std::endl;
|
||||
createModelClassFromMysql(path,
|
||||
client,
|
||||
tableName,
|
||||
restfulApiConfig);
|
||||
}
|
||||
} >>
|
||||
[](const DrogonDbException &e) {
|
||||
std::cerr << e.base().what() << std::endl;
|
||||
exit(1);
|
||||
};
|
||||
*client << "show tables" << Mode::Blocking >> [&](bool isNull,
|
||||
std::string &&tableName) {
|
||||
if (!isNull)
|
||||
{
|
||||
std::transform(tableName.begin(),
|
||||
tableName.end(),
|
||||
tableName.begin(),
|
||||
tolower);
|
||||
std::cout << "table name:" << tableName << std::endl;
|
||||
createModelClassFromMysql(path,
|
||||
client,
|
||||
tableName,
|
||||
restfulApiConfig,
|
||||
relationships[tableName]);
|
||||
}
|
||||
} >> [](const DrogonDbException &e) {
|
||||
std::cerr << e.base().what() << std::endl;
|
||||
exit(1);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
#if USE_SQLITE3
|
||||
|
@ -490,7 +517,8 @@ void create_model::createModelClassFromSqlite3(
|
|||
const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const std::string &tableName,
|
||||
const Json::Value &restfulApiConfig)
|
||||
const Json::Value &restfulApiConfig,
|
||||
const std::vector<Relationship> &relationships)
|
||||
{
|
||||
*client << "SELECT sql FROM sqlite_master WHERE name=? and (type='table' "
|
||||
"or type='view');"
|
||||
|
@ -514,6 +542,8 @@ void create_model::createModelClassFromSqlite3(
|
|||
data["primaryKeyName"] = "";
|
||||
data["dbName"] = std::string("sqlite3");
|
||||
data["rdbms"] = std::string("sqlite3");
|
||||
data["relationships"] = relationships;
|
||||
|
||||
// std::cout << sql << std::endl;
|
||||
auto columns = utils::splitString(sql, ",");
|
||||
int i = 0;
|
||||
|
@ -624,21 +654,28 @@ void create_model::createModelClassFromSqlite3(
|
|||
exit(1);
|
||||
};
|
||||
}
|
||||
void create_model::createModelFromSqlite3(const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const Json::Value &restfulApiConfig)
|
||||
void create_model::createModelFromSqlite3(
|
||||
const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const Json::Value &restfulApiConfig,
|
||||
std::map<std::string, std::vector<Relationship>> &relationships)
|
||||
{
|
||||
*client << "SELECT name FROM sqlite_master WHERE name!='sqlite_sequence' "
|
||||
"and (type='table' or type='view') ORDER BY name;"
|
||||
<< Mode::Blocking >>
|
||||
[=](bool isNull, const std::string &tableName) {
|
||||
[&](bool isNull, std::string &&tableName) mutable {
|
||||
if (!isNull)
|
||||
{
|
||||
std::transform(tableName.begin(),
|
||||
tableName.end(),
|
||||
tableName.begin(),
|
||||
tolower);
|
||||
std::cout << "table name:" << tableName << std::endl;
|
||||
createModelClassFromSqlite3(path,
|
||||
client,
|
||||
tableName,
|
||||
restfulApiConfig);
|
||||
restfulApiConfig,
|
||||
relationships[tableName]);
|
||||
}
|
||||
} >>
|
||||
[](const DrogonDbException &e) {
|
||||
|
@ -655,6 +692,7 @@ void create_model::createModel(const std::string &path,
|
|||
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"]);
|
||||
if (dbType == "postgresql")
|
||||
{
|
||||
#if USE_POSTGRESQL
|
||||
|
@ -714,22 +752,35 @@ void create_model::createModel(const std::string &path,
|
|||
{
|
||||
auto tables = config["tables"];
|
||||
if (!tables || tables.size() == 0)
|
||||
createModelFromPG(path, client, schema, restfulApiConfig);
|
||||
createModelFromPG(
|
||||
path, client, schema, restfulApiConfig, relationships);
|
||||
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);
|
||||
createModelClassFromPG(path,
|
||||
client,
|
||||
tableName,
|
||||
schema,
|
||||
restfulApiConfig,
|
||||
relationships[tableName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
createModelClassFromPG(
|
||||
path, client, singleModelName, schema, restfulApiConfig);
|
||||
createModelClassFromPG(path,
|
||||
client,
|
||||
singleModelName,
|
||||
schema,
|
||||
restfulApiConfig,
|
||||
relationships[singleModelName]);
|
||||
}
|
||||
#else
|
||||
std::cerr
|
||||
|
@ -795,17 +846,25 @@ void create_model::createModel(const std::string &path,
|
|||
{
|
||||
auto tables = config["tables"];
|
||||
if (!tables || tables.size() == 0)
|
||||
createModelFromMysql(path, client, restfulApiConfig);
|
||||
createModelFromMysql(path,
|
||||
client,
|
||||
restfulApiConfig,
|
||||
relationships);
|
||||
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);
|
||||
restfulApiConfig,
|
||||
relationships[tableName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -814,7 +873,8 @@ void create_model::createModel(const std::string &path,
|
|||
createModelClassFromMysql(path,
|
||||
client,
|
||||
singleModelName,
|
||||
restfulApiConfig);
|
||||
restfulApiConfig,
|
||||
relationships[singleModelName]);
|
||||
}
|
||||
|
||||
#else
|
||||
|
@ -858,17 +918,25 @@ void create_model::createModel(const std::string &path,
|
|||
{
|
||||
auto tables = config["tables"];
|
||||
if (!tables || tables.size() == 0)
|
||||
createModelFromSqlite3(path, client, restfulApiConfig);
|
||||
createModelFromSqlite3(path,
|
||||
client,
|
||||
restfulApiConfig,
|
||||
relationships);
|
||||
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);
|
||||
restfulApiConfig,
|
||||
relationships[tableName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -877,7 +945,8 @@ void create_model::createModel(const std::string &path,
|
|||
createModelClassFromSqlite3(path,
|
||||
client,
|
||||
singleModelName,
|
||||
restfulApiConfig);
|
||||
restfulApiConfig,
|
||||
relationships[singleModelName]);
|
||||
}
|
||||
|
||||
#else
|
||||
|
|
|
@ -22,6 +22,7 @@ using namespace drogon::orm;
|
|||
#include <drogon/DrObject.h>
|
||||
#include "CommandHandler.h"
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
|
@ -43,6 +44,222 @@ struct ColumnInfo
|
|||
bool _hasDefaultVal = false;
|
||||
};
|
||||
|
||||
inline 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)
|
||||
{
|
||||
pos = str.find(".", startPos);
|
||||
}
|
||||
if (pos != std::string::npos)
|
||||
ret += str.substr(startPos, pos - startPos);
|
||||
else
|
||||
{
|
||||
ret += str.substr(startPos);
|
||||
break;
|
||||
}
|
||||
while (str[pos] == '_' || 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;
|
||||
}
|
||||
class PivotTable
|
||||
{
|
||||
public:
|
||||
PivotTable() = default;
|
||||
PivotTable(const Json::Value &json)
|
||||
{
|
||||
_tableName = json.get("table_name", "").asString();
|
||||
if (_tableName.empty())
|
||||
{
|
||||
throw std::runtime_error("table_name can't be empty");
|
||||
}
|
||||
_originalKey = json.get("original_key", "").asString();
|
||||
if (_originalKey.empty())
|
||||
{
|
||||
throw std::runtime_error("original_key can't be empty");
|
||||
}
|
||||
_targetKey = json.get("target_key", "").asString();
|
||||
if (_targetKey.empty())
|
||||
{
|
||||
throw std::runtime_error("target_key can't be empty");
|
||||
}
|
||||
}
|
||||
PivotTable reverse() const
|
||||
{
|
||||
PivotTable pivot;
|
||||
pivot._tableName = _tableName;
|
||||
pivot._originalKey = _targetKey;
|
||||
pivot._targetKey = _originalKey;
|
||||
return pivot;
|
||||
}
|
||||
const std::string &tableName() const
|
||||
{
|
||||
return _tableName;
|
||||
}
|
||||
const std::string &originalKey() const
|
||||
{
|
||||
return _originalKey;
|
||||
}
|
||||
const std::string &targetKey() const
|
||||
{
|
||||
return _targetKey;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _tableName;
|
||||
std::string _originalKey;
|
||||
std::string _targetKey;
|
||||
};
|
||||
class Relationship
|
||||
{
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
HasOne,
|
||||
HasMany,
|
||||
ManyToMany
|
||||
};
|
||||
Relationship(const Json::Value &relationship)
|
||||
{
|
||||
auto type = relationship.get("type", "has one").asString();
|
||||
if (type == "has one")
|
||||
{
|
||||
_type = Relationship::Type::HasOne;
|
||||
}
|
||||
else if (type == "has many")
|
||||
{
|
||||
_type = Relationship::Type::HasMany;
|
||||
}
|
||||
else if (type == "many to many")
|
||||
{
|
||||
_type = Relationship::Type::ManyToMany;
|
||||
}
|
||||
else
|
||||
{
|
||||
char message[128];
|
||||
sprintf(message, "Invalid relationship type: %s", type.data());
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
_originalTableName =
|
||||
relationship.get("original_table_name", "").asString();
|
||||
if (_originalTableName.empty())
|
||||
{
|
||||
throw std::runtime_error("original_table_name can't be empty");
|
||||
}
|
||||
_originalKey = relationship.get("original_key", "").asString();
|
||||
if (_originalKey.empty())
|
||||
{
|
||||
throw std::runtime_error("original_key can't be empty");
|
||||
}
|
||||
_originalTableAlias =
|
||||
relationship.get("original_table_alias", "").asString();
|
||||
_targetTableName = relationship.get("target_table_name", "").asString();
|
||||
if (_targetTableName.empty())
|
||||
{
|
||||
throw std::runtime_error("target_table_name can't be empty");
|
||||
}
|
||||
_targetKey = relationship.get("target_key", "").asString();
|
||||
if (_targetKey.empty())
|
||||
{
|
||||
throw std::runtime_error("target_key can't be empty");
|
||||
}
|
||||
_targetTableAlias =
|
||||
relationship.get("target_table_alias", "").asString();
|
||||
_enableReverse = relationship.get("enable_reverse", false).asBool();
|
||||
if (_type == Type::ManyToMany)
|
||||
{
|
||||
auto &pivot = relationship["pivot_table"];
|
||||
if (pivot.isNull())
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"ManyToMany relationship needs a pivot table");
|
||||
}
|
||||
_pivotTable = PivotTable(pivot);
|
||||
}
|
||||
}
|
||||
Relationship() = default;
|
||||
Relationship reverse() const
|
||||
{
|
||||
Relationship r;
|
||||
if (_type == Type::HasMany)
|
||||
{
|
||||
r._type = Type::HasOne;
|
||||
}
|
||||
else
|
||||
{
|
||||
r._type = _type;
|
||||
}
|
||||
r._originalTableName = _targetTableName;
|
||||
r._originalTableAlias = _targetTableAlias;
|
||||
r._originalKey = _targetKey;
|
||||
r._targetTableName = _originalTableName;
|
||||
r._targetTableAlias = _originalTableAlias;
|
||||
r._targetKey = _originalKey;
|
||||
r._enableReverse = _enableReverse;
|
||||
r._pivotTable = _pivotTable.reverse();
|
||||
return r;
|
||||
}
|
||||
Type type() const
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
bool enableReverse() const
|
||||
{
|
||||
return _enableReverse;
|
||||
}
|
||||
const std::string &originalTableName() const
|
||||
{
|
||||
return _originalTableName;
|
||||
}
|
||||
const std::string &originalTableAlias() const
|
||||
{
|
||||
return _originalTableAlias;
|
||||
}
|
||||
const std::string &originalKey() const
|
||||
{
|
||||
return _originalKey;
|
||||
}
|
||||
const std::string &targetTableName() const
|
||||
{
|
||||
return _targetTableName;
|
||||
}
|
||||
const std::string &targetTableAlias() const
|
||||
{
|
||||
return _targetTableAlias;
|
||||
}
|
||||
const std::string &targetKey() const
|
||||
{
|
||||
return _targetKey;
|
||||
}
|
||||
const PivotTable &pivotTable() const
|
||||
{
|
||||
return _pivotTable;
|
||||
}
|
||||
|
||||
private:
|
||||
Type _type = Type::HasOne;
|
||||
std::string _originalTableName;
|
||||
std::string _originalTableAlias;
|
||||
std::string _targetTableName;
|
||||
std::string _targetTableAlias;
|
||||
std::string _originalKey;
|
||||
std::string _targetKey;
|
||||
bool _enableReverse = false;
|
||||
PivotTable _pivotTable;
|
||||
};
|
||||
class create_model : public DrObject<create_model>, public CommandHandler
|
||||
{
|
||||
public:
|
||||
|
@ -63,29 +280,40 @@ class create_model : public DrObject<create_model>, public CommandHandler
|
|||
const DbClientPtr &client,
|
||||
const std::string &tableName,
|
||||
const std::string &schema,
|
||||
const Json::Value &restfulApiConfig);
|
||||
void createModelFromPG(const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const std::string &schema,
|
||||
const Json::Value &restfulApiConfig);
|
||||
const Json::Value &restfulApiConfig,
|
||||
const std::vector<Relationship> &relationships);
|
||||
void createModelFromPG(
|
||||
const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const std::string &schema,
|
||||
const Json::Value &restfulApiConfig,
|
||||
std::map<std::string, std::vector<Relationship>> &relationships);
|
||||
#endif
|
||||
#if USE_MYSQL
|
||||
void createModelClassFromMysql(const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const std::string &tableName,
|
||||
const Json::Value &restfulApiConfig);
|
||||
void createModelFromMysql(const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const Json::Value &restfulApiConfig);
|
||||
void createModelClassFromMysql(
|
||||
const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const std::string &tableName,
|
||||
const Json::Value &restfulApiConfig,
|
||||
const std::vector<Relationship> &relationships);
|
||||
void createModelFromMysql(
|
||||
const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const Json::Value &restfulApiConfig,
|
||||
std::map<std::string, std::vector<Relationship>> &relationships);
|
||||
#endif
|
||||
#if USE_SQLITE3
|
||||
void createModelClassFromSqlite3(const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const std::string &tableName,
|
||||
const Json::Value &restfulApiConfig);
|
||||
void createModelFromSqlite3(const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const Json::Value &restfulApiConfig);
|
||||
void createModelClassFromSqlite3(
|
||||
const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const std::string &tableName,
|
||||
const Json::Value &restfulApiConfig,
|
||||
const std::vector<Relationship> &relationships);
|
||||
void createModelFromSqlite3(
|
||||
const std::string &path,
|
||||
const DbClientPtr &client,
|
||||
const Json::Value &restfulApiConfig,
|
||||
std::map<std::string, std::vector<Relationship>> &relationships);
|
||||
#endif
|
||||
void createRestfulAPIController(const DrTemplateData &tableInfo,
|
||||
const Json::Value &restfulApiConfig);
|
||||
|
|
|
@ -9,6 +9,35 @@ using namespace drogon_ctl;
|
|||
*/
|
||||
|
||||
#include "[[className]].h"
|
||||
<%c++
|
||||
auto &relationships=@@.get<std::vector<Relationship>>("relationships");
|
||||
auto rdbms=@@.get<std::string>("rdbms");
|
||||
for(auto &relationship : relationships)
|
||||
{
|
||||
if(relationship.type() == Relationship::Type::HasOne ||
|
||||
relationship.type() == Relationship::Type::HasMany ||
|
||||
relationship.type() == Relationship::Type::ManyToMany)
|
||||
{
|
||||
auto &name=relationship.targetTableName();
|
||||
auto relationshipClassName=nameTransform(name, true);
|
||||
auto originalClassName=nameTransform(relationship.originalTableName(),true);
|
||||
if(relationshipClassName!=originalClassName)
|
||||
{
|
||||
%>
|
||||
#include "{%relationshipClassName%}.h"
|
||||
<%c++
|
||||
}
|
||||
}
|
||||
if(relationship.type() == Relationship::Type::ManyToMany)
|
||||
{
|
||||
auto &pivotTableName=relationship.pivotTable().tableName();
|
||||
auto pivotTableClassName=nameTransform(pivotTableName, true);
|
||||
%>
|
||||
#include "{%pivotTableClassName%}.h"
|
||||
<%c++
|
||||
}
|
||||
}
|
||||
%>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <string>
|
||||
<%c++
|
||||
|
@ -1299,3 +1328,177 @@ if(!col._notNull){%>
|
|||
}
|
||||
return true;
|
||||
}
|
||||
<%c++
|
||||
for(auto &relationship : relationships)
|
||||
{
|
||||
if(relationship.targetKey().empty() || relationship.originalKey().empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto &name=relationship.targetTableName();
|
||||
auto relationshipClassName=nameTransform(name, true);
|
||||
auto relationshipValName=nameTransform(name, false);
|
||||
auto alias=relationship.targetTableAlias();
|
||||
auto aliasValName=nameTransform(alias, false);
|
||||
if(relationship.type() == Relationship::Type::HasOne)
|
||||
{
|
||||
if(!alias.empty())
|
||||
{
|
||||
if(alias[0] <= 'z' && alias[0] >= 'a')
|
||||
{
|
||||
alias[0] += ('A' - 'a');
|
||||
}
|
||||
std::string alind(alias.length(), ' ');
|
||||
%>
|
||||
void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
|
||||
{%indentStr%} {%alind%} const std::function<void({%relationshipClassName%})> &rcb,
|
||||
{%indentStr%} {%alind%} const ExceptionCallback &ecb) const
|
||||
<%c++
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string relationshipClassInde(relationshipClassName.length(), ' ');
|
||||
%>
|
||||
void [[className]]::get{%relationshipClassName%}(const DbClientPtr &clientPtr,
|
||||
{%indentStr%} {%relationshipClassInde%} const std::function<void({%relationshipClassName%})> &rcb,
|
||||
{%indentStr%} {%relationshipClassInde%} const ExceptionCallback &ecb) const
|
||||
<%c++
|
||||
}
|
||||
%>
|
||||
{
|
||||
const static std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
|
||||
if(rdbms=="postgresql")
|
||||
{
|
||||
$$<<"$1";
|
||||
}
|
||||
else
|
||||
{
|
||||
$$<<"?";
|
||||
}%>";
|
||||
*clientPtr << sql
|
||||
<< *_{%nameTransform(relationship.originalKey(), false)%}
|
||||
>> [rcb = std::move(rcb), ecb](const Result &r){
|
||||
if (r.size() == 0)
|
||||
{
|
||||
ecb(UnexpectedRows("0 rows found"));
|
||||
}
|
||||
else if (r.size() > 1)
|
||||
{
|
||||
ecb(UnexpectedRows("Found more than one row"));
|
||||
}
|
||||
else
|
||||
{
|
||||
rcb({%relationshipClassName%}(r[0]));
|
||||
}
|
||||
}
|
||||
>> ecb;
|
||||
}
|
||||
<%c++
|
||||
}
|
||||
else if(relationship.type() == Relationship::Type::HasMany)
|
||||
{
|
||||
if(!alias.empty())
|
||||
{
|
||||
if(alias[0] <= 'z' && alias[0] >= 'a')
|
||||
{
|
||||
alias[0] += ('A' - 'a');
|
||||
}
|
||||
std::string alind(alias.length(), ' ');
|
||||
%>
|
||||
void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
|
||||
{%indentStr%} {%alind%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
|
||||
{%indentStr%} {%alind%} const ExceptionCallback &ecb) const
|
||||
<%c++
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string relationshipClassInde(relationshipClassName.length(), ' ');
|
||||
%>
|
||||
void [[className]]::get{%relationshipClassName%}(const DbClientPtr &clientPtr,
|
||||
{%indentStr%} {%relationshipClassInde%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
|
||||
{%indentStr%} {%relationshipClassInde%} const ExceptionCallback &ecb) const
|
||||
<%c++
|
||||
}
|
||||
%>
|
||||
{
|
||||
const static std::string sql = "select * from {%name%} where {%relationship.targetKey()%} = <%c++
|
||||
if(rdbms=="postgresql")
|
||||
{
|
||||
$$<<"$1";
|
||||
}
|
||||
else
|
||||
{
|
||||
$$<<"?";
|
||||
}%>";
|
||||
*clientPtr << sql
|
||||
<< *_{%nameTransform(relationship.originalKey(), false)%}
|
||||
>> [rcb = std::move(rcb)](const Result &r){
|
||||
std::vector<{%relationshipClassName%}> ret;
|
||||
ret.reserve(ret.size());
|
||||
for (auto const &row : r)
|
||||
{
|
||||
ret.emplace_back({%relationshipClassName%}(row));
|
||||
}
|
||||
rcb(ret);
|
||||
}
|
||||
>> ecb;
|
||||
}
|
||||
<%c++
|
||||
}
|
||||
else if(relationship.type() == Relationship::Type::ManyToMany)
|
||||
{
|
||||
auto &pivotTableName=relationship.pivotTable().tableName();
|
||||
auto pivotTableClassName=nameTransform(pivotTableName, true);
|
||||
auto &pivotOriginalKey=relationship.pivotTable().originalKey();
|
||||
auto &pivotTargetKey=relationship.pivotTable().targetKey();
|
||||
if(!alias.empty())
|
||||
{
|
||||
if(alias[0] <= 'z' && alias[0] >= 'a')
|
||||
{
|
||||
alias[0] += ('A' - 'a');
|
||||
}
|
||||
std::string alind(alias.length(), ' ');
|
||||
%>
|
||||
void [[className]]::get{%alias%}(const DbClientPtr &clientPtr,
|
||||
{%indentStr%} {%alind%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>) &rcb,
|
||||
{%indentStr%} {%alind%} const ExceptionCallback &ecb) const
|
||||
<%c++
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string relationshipClassInde(relationshipClassName.length(), ' ');
|
||||
%>
|
||||
void [[className]]::get{%relationshipClassName%}(const DbClientPtr &clientPtr,
|
||||
{%indentStr%} {%relationshipClassInde%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
|
||||
{%indentStr%} {%relationshipClassInde%} const ExceptionCallback &ecb) const
|
||||
<%c++
|
||||
}
|
||||
%>
|
||||
{
|
||||
const static std::string sql = "select * from {%name%},{%pivotTableName%} where {%pivotTableName%}.{%pivotOriginalKey%} = <%c++
|
||||
if(rdbms=="postgresql")
|
||||
{
|
||||
$$<<"$1";
|
||||
}
|
||||
else
|
||||
{
|
||||
$$<<"?";
|
||||
}%> and {%pivotTableName%}.{%pivotTargetKey%} = {%name%}.{%relationship.targetKey()%}";
|
||||
*clientPtr << sql
|
||||
<< *_{%nameTransform(relationship.originalKey(), false)%}
|
||||
>> [rcb = std::move(rcb)](const Result &r){
|
||||
std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>> ret;
|
||||
ret.reserve(ret.size());
|
||||
for (auto const &row : r)
|
||||
{
|
||||
ret.emplace_back(std::pair<{%relationshipClassName%},{%pivotTableClassName%}>(
|
||||
{%relationshipClassName%}(row),{%pivotTableClassName%}(row,{%relationshipClassName%}::getColumnNumber())));
|
||||
}
|
||||
rcb(ret);
|
||||
}
|
||||
>> ecb;
|
||||
}
|
||||
<%c++
|
||||
}
|
||||
}
|
||||
%>
|
|
@ -25,7 +25,14 @@ using namespace drogon_ctl;
|
|||
#include <iostream>
|
||||
|
||||
using namespace drogon::orm;
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
namespace orm
|
||||
{
|
||||
class DbClient;
|
||||
typedef std::shared_ptr<DbClient> DbClientPtr;
|
||||
}
|
||||
}
|
||||
namespace drogon_model
|
||||
{
|
||||
namespace [[dbName]]
|
||||
|
@ -37,7 +44,25 @@ if(!schema.empty())
|
|||
$$<<"namespace "<<schema<<"\n";
|
||||
$$<<"{\n";
|
||||
}
|
||||
auto &relationships=@@.get<std::vector<Relationship>>("relationships");
|
||||
for(auto &relationship : relationships)
|
||||
{
|
||||
auto &name=relationship.targetTableName();
|
||||
auto relationshipClassName=nameTransform(name, true);
|
||||
%>
|
||||
class {%relationshipClassName%};
|
||||
<%c++
|
||||
if(relationship.type() == Relationship::Type::ManyToMany)
|
||||
{
|
||||
auto &pivotTableName=relationship.pivotTable().tableName();
|
||||
auto pivotTableClassName=nameTransform(pivotTableName, true);
|
||||
%>
|
||||
class {%pivotTableClassName%};
|
||||
<%c++
|
||||
}
|
||||
}
|
||||
%>
|
||||
|
||||
class [[className]]
|
||||
{
|
||||
public:
|
||||
|
@ -172,7 +197,98 @@ auto cols=@@.get<std::vector<ColumnInfo>>("columns");
|
|||
|
||||
Json::Value toJson() const;
|
||||
Json::Value toMasqueradedJson(const std::vector<std::string> &pMasqueradingVector) const;
|
||||
|
||||
/// Relationship interfaces
|
||||
<%c++
|
||||
for(auto &relationship : relationships)
|
||||
{
|
||||
if(relationship.targetKey().empty() || relationship.originalKey().empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto &name=relationship.targetTableName();
|
||||
auto relationshipClassName=nameTransform(name, true);
|
||||
auto relationshipValName=nameTransform(name, false);
|
||||
auto alias=relationship.targetTableAlias();
|
||||
auto aliasValName=nameTransform(alias, false);
|
||||
if(relationship.type() == Relationship::Type::HasOne)
|
||||
{
|
||||
if(!alias.empty())
|
||||
{
|
||||
if(alias[0] <= 'z' && alias[0] >= 'a')
|
||||
{
|
||||
alias[0] += ('A' - 'a');
|
||||
}
|
||||
std::string alind(alias.length(), ' ');
|
||||
%>
|
||||
void get{%alias%}(const DbClientPtr &clientPtr,
|
||||
{%alind%} const std::function<void({%relationshipClassName%})> &rcb,
|
||||
{%alind%} const ExceptionCallback &ecb) const;
|
||||
<%c++
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string relationshipClassInde(relationshipClassName.length(), ' ');
|
||||
%>
|
||||
void get{%relationshipClassName%}(const DbClientPtr &clientPtr,
|
||||
{%relationshipClassInde%} const std::function<void({%relationshipClassName%})> &rcb,
|
||||
{%relationshipClassInde%} const ExceptionCallback &ecb) const;
|
||||
<%c++
|
||||
}
|
||||
}
|
||||
else if(relationship.type() == Relationship::Type::HasMany)
|
||||
{
|
||||
if(!alias.empty())
|
||||
{
|
||||
if(alias[0] <= 'z' && alias[0] >= 'a')
|
||||
{
|
||||
alias[0] += ('A' - 'a');
|
||||
}
|
||||
std::string alind(alias.length(), ' ');
|
||||
%>
|
||||
void get{%alias%}(const DbClientPtr &clientPtr,
|
||||
{%alind%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
|
||||
{%alind%} const ExceptionCallback &ecb) const;
|
||||
<%c++
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string relationshipClassInde(relationshipClassName.length(), ' ');
|
||||
%>
|
||||
void get{%relationshipClassName%}(const DbClientPtr &clientPtr,
|
||||
{%relationshipClassInde%} const std::function<void(std::vector<{%relationshipClassName%}>)> &rcb,
|
||||
{%relationshipClassInde%} const ExceptionCallback &ecb) const;
|
||||
<%c++
|
||||
}
|
||||
}
|
||||
else if(relationship.type() == Relationship::Type::ManyToMany)
|
||||
{
|
||||
auto &pivotTableName=relationship.pivotTable().tableName();
|
||||
auto pivotTableClassName=nameTransform(pivotTableName, true);
|
||||
if(!alias.empty())
|
||||
{
|
||||
if(alias[0] <= 'z' && alias[0] >= 'a')
|
||||
{
|
||||
alias[0] += ('A' - 'a');
|
||||
}
|
||||
std::string alind(alias.length(), ' ');
|
||||
%>
|
||||
void get{%alias%}(const DbClientPtr &clientPtr,
|
||||
{%alind%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
|
||||
{%alind%} const ExceptionCallback &ecb) const;
|
||||
<%c++
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string relationshipClassInde(relationshipClassName.length(), ' ');
|
||||
%>
|
||||
void get{%relationshipClassName%}(const DbClientPtr &clientPtr,
|
||||
{%relationshipClassInde%} const std::function<void(std::vector<std::pair<{%relationshipClassName%},{%pivotTableClassName%}>>)> &rcb,
|
||||
{%relationshipClassInde%} const ExceptionCallback &ecb) const;
|
||||
<%c++
|
||||
}
|
||||
}
|
||||
}
|
||||
%>
|
||||
private:
|
||||
friend Mapper<[[className]]>;
|
||||
static const std::vector<std::string> &insertColumns() noexcept;
|
||||
|
@ -200,7 +316,6 @@ auto cols=@@.get<std::vector<ColumnInfo>>("columns");
|
|||
};
|
||||
static const std::vector<MetaData> _metaData;
|
||||
bool _dirtyFlag[{%cols.size()%}]={ false };
|
||||
|
||||
public:
|
||||
static const std::string &sqlForFindingByPrimaryKey()
|
||||
{
|
||||
|
|
|
@ -10,13 +10,52 @@
|
|||
//dbname: Database name;
|
||||
"dbname": "",
|
||||
//schema: valid for postgreSQL, "public" by default;
|
||||
"schema":"public",
|
||||
"schema": "public",
|
||||
//user: User name
|
||||
"user": "",
|
||||
//password: Password
|
||||
"passwd": "",
|
||||
//table: An array of tables to be modelized. if the array is empty, all revealed tables are modelized.
|
||||
"tables": [],
|
||||
"relationships": {
|
||||
"enabled": false,
|
||||
"items": [{
|
||||
"type": "has one",
|
||||
"original_table_name": "products",
|
||||
"original_table_alias": "product",
|
||||
"original_key": "id",
|
||||
"target_table_name": "skus",
|
||||
"target_table_alias": "SKU",
|
||||
"target_key": "product_id",
|
||||
"enable_reverse": true
|
||||
},
|
||||
{
|
||||
"type": "has many",
|
||||
"original_table_name": "products",
|
||||
"original_table_alias": "product",
|
||||
"original_key": "id",
|
||||
"target_table_name": "reviews",
|
||||
"target_table_alias": "",
|
||||
"target_key": "product_id",
|
||||
"enable_reverse": true
|
||||
},
|
||||
{
|
||||
"type": "many to many",
|
||||
"original_table_name": "products",
|
||||
"original_table_alias": "",
|
||||
"original_key": "id",
|
||||
"pivot_table": {
|
||||
"table_name": "carts_products",
|
||||
"original_key": "product_id",
|
||||
"target_key": "cart_id"
|
||||
},
|
||||
"target_table_name": "carts",
|
||||
"target_table_alias": "",
|
||||
"target_key": "id",
|
||||
"enable_reverse": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"restful_api_controllers": {
|
||||
"enabled": false,
|
||||
// resource_uri: The URI to access the resource, the default value
|
||||
|
|
Loading…
Reference in New Issue