Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ struct person {
REGISTER_AUTO_KEY(person, id)
```

## 跳过插入字段

使用REGISTER_SKIP_INSERT_FIELD注册插入时需要跳过的字段,适用于让数据库默认值生效的列,例如 MySQL 的 `timestamp NOT NULL DEFAULT current_timestamp()`。

```C++
struct material {
int id;
std::string datetime;
std::string company;
};
REGISTER_AUTO_KEY(material, id)
REGISTER_SKIP_INSERT_FIELD(material, datetime)
```

上例在执行 `insert` 时会省略 `datetime` 字段,数据库会使用表定义中的默认值。`replace` 和 `update` 不受影响。

## 冲突主键

使用REGISTER_CONFLICT_KEY注册冲突主键来进行update,如果未注册冲突主键则会采用自增主键
Expand Down
20 changes: 13 additions & 7 deletions ormpp/mysql.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1022,13 +1022,13 @@ class mysql {
if (item.fields.size() > 1) {
std::string str;
for (auto &name : item.fields) {
str.append(name).append(",");
str.append(quote_mysql_identifier(name)).append(",");
}
str.pop_back();
unique.insert(str);
}
else {
unique.insert(*item.fields.begin());
unique.insert(quote_mysql_identifier(*item.fields.begin()));
}
}
});
Expand All @@ -1042,7 +1042,9 @@ class mysql {
T t;
ylt::reflection::for_each(t, [&](auto &field, auto name, size_t index) {
using item_type = std::decay_t<decltype(field)>;
sql.append(name).append(" ").append(type_name_arr[index]);
sql.append(quote_mysql_identifier(name))
.append(" ")
.append(type_name_arr[index]);

std::string str_name(name);

Expand All @@ -1061,12 +1063,14 @@ class mysql {
});

if (!auto_key.empty()) {
sql.append("PRIMARY KEY (").append(auto_key).append("),");
sql.append("PRIMARY KEY (")
.append(quote_mysql_identifier(auto_key))
.append("),");
}
else if (!primary_keys.empty()) {
sql.append("PRIMARY KEY (");
for (auto key : primary_keys) {
sql.append(key).append(",");
sql.append(quote_mysql_identifier(key)).append(",");
}
sql.pop_back();
sql.append("),");
Expand Down Expand Up @@ -1094,7 +1098,8 @@ class mysql {
else {
ylt::reflection::for_each(t, [arr, &param_binds, type, this](
auto &field, auto name, auto index) {
if (type == OptType::insert && is_auto_key<T>(name)) {
if (type == OptType::insert &&
(is_auto_key<T>(name) || is_skip_insert_field<T>(name))) {
return;
}
if constexpr (sizeof...(members) > 0) {
Expand Down Expand Up @@ -1124,7 +1129,8 @@ class mysql {
}
}

if (mysql_stmt_bind_param(stmt_, &param_binds[0])) {
if (!param_binds.empty() &&
mysql_stmt_bind_param(stmt_, param_binds.data())) {
set_last_error(mysql_stmt_error(stmt_));
return INT_MIN;
}
Expand Down
17 changes: 11 additions & 6 deletions ormpp/mysql_async.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1008,13 +1008,13 @@ std::string generate_create_table_sql(DBType db_type, bool append_mysql_charset,
if (item.fields.size() > 1) {
std::string names;
for (auto& name : item.fields) {
names.append(name).append(",");
names.append(quote_mysql_identifier(name)).append(",");
}
names.pop_back();
unique.insert(std::move(names));
}
else if (!item.fields.empty()) {
unique.insert(*item.fields.begin());
unique.insert(quote_mysql_identifier(*item.fields.begin()));
}
}
},
Expand All @@ -1030,7 +1030,9 @@ std::string generate_create_table_sql(DBType db_type, bool append_mysql_charset,
T sample{};
ylt::reflection::for_each(sample, [&](auto& /*field*/, auto name,
size_t index) {
sql.append(name).append(" ").append(type_name_arr[index]);
sql.append(quote_mysql_identifier(name))
.append(" ")
.append(type_name_arr[index]);
std::string str_name(name);

if (!auto_primary_key.empty() &&
Expand All @@ -1047,12 +1049,14 @@ std::string generate_create_table_sql(DBType db_type, bool append_mysql_charset,
});

if (!auto_key.empty()) {
sql.append("PRIMARY KEY (").append(auto_key).append("),");
sql.append("PRIMARY KEY (")
.append(quote_mysql_identifier(auto_key))
.append("),");
}
else if (!primary_keys.empty()) {
sql.append("PRIMARY KEY (");
for (const auto& key : primary_keys) {
sql.append(key).append(",");
sql.append(quote_mysql_identifier(key)).append(",");
}
sql.back() = ')';
sql.push_back(',');
Expand Down Expand Up @@ -1849,7 +1853,8 @@ class mysql_async {

ylt::reflection::for_each(
value, [&](auto& field, auto name, auto /*index*/) {
if (type == OptType::insert && is_auto_key<T>(name)) {
if (type == OptType::insert &&
(is_auto_key<T>(name) || is_skip_insert_field<T>(name))) {
return;
}
values.push_back(detail::mysql_async::to_query_arg_impl(
Expand Down
3 changes: 2 additions & 1 deletion ormpp/postgresql.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,8 @@ class postgresql {
else {
ylt::reflection::for_each(t, [arr, &param_values, type, this](
auto &field, auto name, auto index) {
if (type == OptType::insert && is_auto_key<T>(name)) {
if (type == OptType::insert &&
(is_auto_key<T>(name) || is_skip_insert_field<T>(name))) {
return;
}
if constexpr (sizeof...(members) > 0) {
Expand Down
4 changes: 3 additions & 1 deletion ormpp/sqlite.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,9 @@ class sqlite {
else {
ylt::reflection::for_each(t, [&bind_ok, &index, type, this](
auto &field, auto name, auto /*index*/) {
if ((type == OptType::insert && is_auto_key<T>(name)) || !bind_ok) {
if ((type == OptType::insert &&
(is_auto_key<T>(name) || is_skip_insert_field<T>(name))) ||
!bind_ok) {
return;
}
bind_ok = set_param_bind(field, ++index);
Expand Down
52 changes: 48 additions & 4 deletions ormpp/utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
#include <iostream>
#include <mutex>
#include <optional>
#include <set>
#include <sstream>
#include <unordered_map>

#include "entity.hpp"
#include "iguana/util.hpp"
Expand Down Expand Up @@ -72,6 +74,42 @@ inline auto is_auto_key(std::string_view field_name) {
return it == get_auto_key_map().end() ? false : it->second == field_name;
}

inline auto &get_skip_insert_field_map() {
static std::unordered_map<std::string_view, std::set<std::string_view>> map;
return map;
}

inline int add_skip_insert_field(std::string_view key, std::string_view value) {
get_skip_insert_field_map()[key].insert(value);
return 0;
}

template <typename T>
inline auto is_skip_insert_field(std::string_view field_name) {
auto it = get_skip_insert_field_map().find(get_short_struct_name<T>());
return it == get_skip_insert_field_map().end()
? false
: it->second.find(field_name) != it->second.end();
}

inline std::string quote_mysql_identifier(std::string_view name) {
if (name.size() >= 2 && name.front() == '`' && name.back() == '`') {
return std::string(name);
}

std::string result;
result.reserve(name.size() + 2);
result.push_back('`');
for (char ch : name) {
if (ch == '`') {
result.push_back('`');
}
result.push_back(ch);
}
result.push_back('`');
return result;
}

#ifdef _MSC_VER
#define ORMPP_UNIQUE_VARIABLE(str) YLT_CONCAT(str, __COUNTER__)
#else
Expand All @@ -82,6 +120,11 @@ inline auto is_auto_key(std::string_view field_name) {
inline auto ORMPP_UNIQUE_VARIABLE(STRUCT_NAME) = ormpp::add_auto_key_field( \
ylt::reflection::get_struct_name<STRUCT_NAME>(), #KEY);

#define REGISTER_SKIP_INSERT_FIELD(STRUCT_NAME, FIELD) \
inline auto ORMPP_UNIQUE_VARIABLE(STRUCT_NAME) = \
ormpp::add_skip_insert_field( \
ylt::reflection::get_struct_name<STRUCT_NAME>(), #FIELD);

inline auto &get_conflict_map() {
static std::unordered_map<std::string_view, std::string_view> map;
return map;
Expand Down Expand Up @@ -305,7 +348,7 @@ inline std::string get_fields(DBType db_type) {
std::call_once(flag, [&fields, db_type]() {
for (const auto &it : ylt::reflection::get_member_names<T>()) {
if (db_type == DBType::mysql) {
fields += "`" + std::string(it) + "`";
fields += quote_mysql_identifier(it);
}
else {
fields += std::string(it);
Expand Down Expand Up @@ -398,7 +441,7 @@ inline std::vector<std::string> get_conflict_keys(DBType db_type) {
for (auto sv : v) {
std::string str;
if (db_type == DBType::mysql) {
str.append("`").append(sv).append("`");
str = quote_mysql_identifier(sv);
}
else {
str.append(sv);
Expand Down Expand Up @@ -470,7 +513,8 @@ inline std::string generate_insert_sql(DBType db_type, bool insert,
std::string values = "values(";
for (size_t i = 0; i < Count; ++i) {
std::string field_name(ylt::reflection::name_of<T>(i));
if (insert && is_auto_key<T>(field_name)) {
if (insert &&
(is_auto_key<T>(field_name) || is_skip_insert_field<T>(field_name))) {
continue;
}
if (db_type == DBType::postgresql) {
Expand All @@ -480,7 +524,7 @@ inline std::string generate_insert_sql(DBType db_type, bool insert,
values += "?";
}
if (db_type == DBType::mysql) {
fields += "`" + field_name + "`";
fields += quote_mysql_identifier(field_name);
}
else {
fields += field_name;
Expand Down
53 changes: 53 additions & 0 deletions tests/test_ormpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ struct builder_person {
};
REGISTER_AUTO_KEY(builder_person, id)

struct mysql_keyword_material_index {
int id;
std::string company;
std::string group;
int MaterialNumber;
};
REGISTER_AUTO_KEY(mysql_keyword_material_index, id)

struct insert_default_field {
int id;
std::string datetime;
std::string company;
};
REGISTER_AUTO_KEY(insert_default_field, id)
REGISTER_SKIP_INSERT_FIELD(insert_default_field, datetime)

struct fake_postgresql_db {
static constexpr DBType db_type_v = DBType::postgresql;
};
Expand Down Expand Up @@ -1349,6 +1365,43 @@ TEST_CASE("create table") {
#endif
}

TEST_CASE("create mysql table with keyword field") {
#ifdef ORMPP_ENABLE_MYSQL
dbng<mysql> mysql;
if (mysql.connect(ip, username, password, db)) {
mysql.execute("drop table if exists mysql_keyword_material_index");
REQUIRE(mysql.create_datatable<mysql_keyword_material_index>(
ormpp_auto_key{"id"}));
}
#endif
}

TEST_CASE("skip insert field") {
auto mysql_sql =
generate_insert_sql<insert_default_field>(DBType::mysql, true);
CHECK(mysql_sql.find("`id`") == std::string::npos);
CHECK(mysql_sql.find("`datetime`") == std::string::npos);
CHECK(mysql_sql.find("`company`") != std::string::npos);
CHECK(mysql_sql.find("values(?)") != std::string::npos);

dbng<sqlite> sqlite;
#ifdef SQLITE_HAS_CODEC
REQUIRE(sqlite.connect(db, password));
#else
REQUIRE(sqlite.connect(db));
#endif
sqlite.execute("drop table if exists insert_default_field");
REQUIRE(sqlite.execute(
"create table insert_default_field(id integer primary key autoincrement, "
"datetime text default 'db_default', company text)"));
REQUIRE(sqlite.insert<insert_default_field>({0, "ignored", "purecpp"}) == 1);

auto rows = sqlite.query_s<insert_default_field>("order by id");
REQUIRE(rows.size() == 1);
CHECK(rows[0].datetime == "db_default");
CHECK(rows[0].company == "purecpp");
}

TEST_CASE("insert query") {
ormpp_key key{col_name(&student::code)};
ormpp_not_null not_null{{col_name(&student::code), col_name(&student::age)}};
Expand Down
Loading