153 lines
6.1 KiB
C++
153 lines
6.1 KiB
C++
#pragma once
|
|
|
|
namespace sql {
|
|
|
|
template <typename T>
|
|
struct Column {
|
|
constexpr explicit Column(const std::string_view& name) : name{name} { }
|
|
|
|
const std::string name;
|
|
};
|
|
|
|
struct BindingNameGenerator {
|
|
public:
|
|
[[nodiscard]] static std::string generate(size_t index) {
|
|
assert(index > 0);
|
|
std::string result{};
|
|
result.resize(std::max(index >> 4U, 1UL));
|
|
for(auto it = result.begin(); index > 0; index >>= 4U)
|
|
*(it++) = number_map[index & 0xFU];
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
constexpr static std::array<char, 16> number_map{
|
|
'a', 'b', 'c', 'd', 'e', 'f',
|
|
'g', 'h', 'i', 'j', 'k', 'l',
|
|
'm', 'n', 'o', 'p'
|
|
};
|
|
};
|
|
|
|
template <typename... ColTypes>
|
|
struct InsertQueryGenerator {
|
|
struct GenerateOptions {
|
|
bool enable_ignore{false};
|
|
sql::SqlType target{sql::SqlType::TYPE_SQLITE};
|
|
};
|
|
|
|
explicit InsertQueryGenerator(std::string target_table, Column<ColTypes>... columns) : table_name{std::move(target_table)}, columns{std::move(columns.name)...} { }
|
|
|
|
[[nodiscard]] constexpr inline auto column_count() const { return sizeof...(ColTypes); };
|
|
|
|
[[nodiscard]] inline std::string generate_query(size_t entry_count, sql::SqlType target = sql::SqlType::TYPE_SQLITE, bool enable_ignore = false) const {
|
|
if(entry_count == 0) return "-- No entries given";
|
|
|
|
std::string result{"INSERT "};
|
|
if(enable_ignore) {
|
|
if(target == sql::TYPE_MYSQL)
|
|
result += "IGNORE ";
|
|
else
|
|
result += "OR IGNORE ";
|
|
}
|
|
|
|
result += this->table_name + " (";
|
|
//"INSERT INTO " + this->table_name + " ("
|
|
for(auto it = this->columns.begin();;) {
|
|
result += "`" + *it + "`";
|
|
if(++it != this->columns.end()) {
|
|
result += ", ";
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
result += ") VALUES (";
|
|
for(size_t index{1}; index <= this->column_count() * entry_count; index++)
|
|
result += ":" + BindingNameGenerator::generate(index) + ", ";
|
|
result = result.substr(0, result.length() - 2) + ");";
|
|
return result;
|
|
}
|
|
|
|
const std::string table_name{};
|
|
const std::array<std::string, sizeof...(ColTypes)> columns{};
|
|
};
|
|
|
|
template <typename... ColTypes>
|
|
struct InsertQuery : public InsertQueryGenerator<ColTypes...> {
|
|
struct ExecuteResult {
|
|
std::vector<std::tuple<size_t, sql::result>> failed_entries{};
|
|
|
|
[[nodiscard]] inline auto has_succeeded() const { return this->failed_entries.empty(); }
|
|
};
|
|
|
|
explicit InsertQuery(const std::string& target_table, Column<ColTypes>... columns) : InsertQueryGenerator<ColTypes...>{target_table, std::move(columns)...} { }
|
|
|
|
inline void reserve_entries(size_t amount) {
|
|
this->entries.reserve(amount);
|
|
}
|
|
|
|
inline void add_entry(const ColTypes&... values) {
|
|
this->entries.push_back(std::array<variable, sizeof...(ColTypes)>{variable{"", values}...});
|
|
}
|
|
|
|
inline ExecuteResult execute(sql::SqlManager* sql, bool ignore_fails = false) {
|
|
if(this->entries.empty()) return ExecuteResult{};
|
|
|
|
ExecuteResult result{};
|
|
|
|
const auto chunk_size = std::min(2UL, this->entries.size());
|
|
sql::model chunk_base{sql, this->generate_query(chunk_size, sql->getType(), ignore_fails)};
|
|
sql::model entry_model{sql, this->generate_query(1, sql->getType(), ignore_fails)};
|
|
|
|
for(size_t chunk{0}; chunk < this->entries.size() / chunk_size; chunk++) {
|
|
auto command = chunk_base.command();
|
|
size_t parameter_index{1};
|
|
for(size_t index{chunk * chunk_size}; index < (chunk + 1) * chunk_size; index++) {
|
|
for(auto& var : this->entries[index]) {
|
|
var.set_key(":" + BindingNameGenerator::generate(parameter_index++));
|
|
command.value(var);
|
|
}
|
|
}
|
|
|
|
auto exec_result = command.execute();
|
|
if(!exec_result) {
|
|
/* try every entry 'till we've found the error one */
|
|
for(size_t index{chunk * chunk_size}; index < (chunk + 1) * chunk_size; index++) {
|
|
parameter_index = 1;
|
|
|
|
auto entry_command = entry_model.command();
|
|
for(auto& var : this->entries[index]) {
|
|
var.set_key(":" + BindingNameGenerator::generate(parameter_index++));
|
|
entry_command.value(var);
|
|
}
|
|
exec_result = entry_command.execute();
|
|
if(!exec_result) {
|
|
result.failed_entries.emplace_back(index, std::move(exec_result));
|
|
if(!ignore_fails)
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(size_t index{(this->entries.size() / chunk_size) * chunk_size}; index < this->entries.size(); index++) {
|
|
size_t parameter_index{1};
|
|
auto entry_command = entry_model.command();
|
|
for(auto& var : this->entries[index]) {
|
|
var.set_key(":" + BindingNameGenerator::generate(parameter_index++));
|
|
entry_command.value(var);
|
|
}
|
|
auto exec_result = entry_command.execute();
|
|
if(!exec_result) {
|
|
result.failed_entries.emplace_back(index, std::move(exec_result));
|
|
if(!ignore_fails)
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::array<variable, sizeof...(ColTypes)>> entries{};
|
|
};
|
|
|
|
} |