diff --git a/include/sqlgen.hpp b/include/sqlgen.hpp index ff8247f..e28c190 100644 --- a/include/sqlgen.hpp +++ b/include/sqlgen.hpp @@ -7,5 +7,6 @@ #include "sqlgen/PrimaryKey.hpp" #include "sqlgen/Ref.hpp" #include "sqlgen/Result.hpp" +#include "sqlgen/write.hpp" #endif diff --git a/include/sqlgen/internal/to_str.hpp b/include/sqlgen/internal/to_str.hpp new file mode 100644 index 0000000..f45f496 --- /dev/null +++ b/include/sqlgen/internal/to_str.hpp @@ -0,0 +1,36 @@ +#ifndef SQLGEN_INTERNAL_TO_STR_HPP_ +#define SQLGEN_INTERNAL_TO_STR_HPP_ + +#include +#include +#include + +#include "../parsing/has_reflection_method.hpp" +#include "../parsing/is_nullable.hpp" + +namespace sqlgen::internal { + +template +std::optional to_str(const T& _val) { + using Type = std::remove_cvref_t; + if constexpr (parsing::is_nullable_v) { + if (!_val) { + return std::nullopt; + } else { + return to_str(*_val); + } + + } else if constexpr (parsing::has_reflection_method) { + return to_str(_val.reflection()); + + } else if constexpr (std::is_same_v) { + return _val; + + } else { + return std::to_string(_val); + } +} + +} // namespace sqlgen::internal + +#endif diff --git a/include/sqlgen/internal/to_str_vec.hpp b/include/sqlgen/internal/to_str_vec.hpp new file mode 100644 index 0000000..c8cd0dd --- /dev/null +++ b/include/sqlgen/internal/to_str_vec.hpp @@ -0,0 +1,25 @@ +#ifndef SQLGEN_INTERNAL_TO_STR_VEC_HPP_ +#define SQLGEN_INTERNAL_TO_STR_VEC_HPP_ + +#include +#include +#include +#include +#include + +#include "to_str.hpp" + +namespace sqlgen::internal { + +template +std::vector> to_str_vec(const T& _t) { + return rfl::apply( + [](auto... _ptrs) { + return std::vector>({to_str(*_ptrs)...}); + }, + rfl::to_view(_t).values()); +} + +} // namespace sqlgen::internal + +#endif diff --git a/include/sqlgen/write.hpp b/include/sqlgen/write.hpp new file mode 100644 index 0000000..f0ef9e0 --- /dev/null +++ b/include/sqlgen/write.hpp @@ -0,0 +1,65 @@ +#ifndef SQLGEN_WRITE_HPP_ +#define SQLGEN_WRITE_HPP_ + +#include +#include +#include +#include +#include +#include + +#include "Connection.hpp" +#include "Ref.hpp" +#include "Result.hpp" +#include "internal/to_str_vec.hpp" +#include "parsing/to_create_table.hpp" +#include "parsing/to_insert.hpp" + +namespace sqlgen { + +template +Result write(const Ref& _conn, ItBegin _begin, + ItEnd _end) { + using T = + std::remove_cvref_t::value_type>; + + const auto start_write = [&](const auto&) -> Result { + const auto insert_stmt = parsing::to_insert(); + return _conn->start_write(insert_stmt); + }; + + const auto write = [&](const auto&) -> Result { + try { + std::vector>> data; + for (auto it = _begin; it != _end; ++it) { + data.emplace_back(internal::to_str_vec(*it)); + if (data.size() == 50000) { + _conn->write(data).value(); + data.clear(); + } + } + if (data.size() != 0) { + _conn->write(data).value(); + } + return Nothing{}; + } catch (std::exception& e) { + _conn->end_write(); + return error(e.what()); + } + }; + + const auto end_write = [&](const auto&) -> Result { + return _conn->end_write(); + }; + + const auto create_table_stmt = parsing::to_create_table(); + + return _conn->execute(_conn->to_sql(create_table_stmt)) + .and_then(start_write) + .and_then(write) + .and_then(end_write); +} + +} // namespace sqlgen + +#endif diff --git a/src/sqlgen/sqlite/Connection.cpp b/src/sqlgen/sqlite/Connection.cpp index f7c56c4..94686f5 100644 --- a/src/sqlgen/sqlite/Connection.cpp +++ b/src/sqlgen/sqlite/Connection.cpp @@ -26,13 +26,15 @@ std::string Connection::create_table_to_sql( std::stringstream stream; stream << "CREATE TABLE "; + + if (_stmt.if_not_exists) { + stream << "IF NOT EXISTS "; + } + if (_stmt.table.schema) { stream << "\"" << *_stmt.table.schema << "\"."; } stream << "\"" << _stmt.table.name << "\" "; - if (_stmt.if_not_exists) { - stream << "IF NOT EXISTS "; - } stream << "("; stream << internal::strings::join( @@ -78,9 +80,9 @@ std::string Connection::insert_to_sql(const dynamic::Insert& _stmt) noexcept { if (_stmt.table.schema) { stream << "\"" << *_stmt.table.schema << "\"."; } - stream << "\"" << _stmt.table.name << "\" "; + stream << "\"" << _stmt.table.name << "\""; - stream << "("; + stream << " ("; stream << internal::strings::join( ", ", internal::collect::vector(_stmt.columns | transform(in_quotes))); stream << ")"; @@ -159,16 +161,18 @@ Result Connection::write( } for (const auto& row : _data) { - for (size_t i = 0; i < row.size(); ++i) { + const auto num_cols = static_cast(row.size()); + + for (int i = 0; i < num_cols; ++i) { if (row[i]) { const auto res = - sqlite3_bind_text(stmt_.get(), static_cast(i), row[i]->c_str(), + sqlite3_bind_text(stmt_.get(), i + 1, row[i]->c_str(), static_cast(row[i]->size()), SQLITE_STATIC); if (res != SQLITE_OK) { return error(sqlite3_errmsg(conn_.get())); } } else { - const auto res = sqlite3_bind_null(stmt_.get(), static_cast(i)); + const auto res = sqlite3_bind_null(stmt_.get(), i + 1); if (res != SQLITE_OK) { return error(sqlite3_errmsg(conn_.get())); } @@ -176,7 +180,7 @@ Result Connection::write( } auto res = sqlite3_step(stmt_.get()); - if (res != SQLITE_OK) { + if (res != SQLITE_OK && res != SQLITE_ROW && res != SQLITE_DONE) { return error(sqlite3_errmsg(conn_.get())); } @@ -186,6 +190,12 @@ Result Connection::write( } } + // We need to reset the statement to avoid segfaults. + const auto res = sqlite3_clear_bindings(stmt_.get()); + if (res != SQLITE_OK) { + return error(sqlite3_errmsg(conn_.get())); + } + return Nothing{}; } diff --git a/tests/sqlite/test_to_create_table.cpp b/tests/sqlite/test_to_create_table.cpp index 2a185a7..1346e7e 100644 --- a/tests/sqlite/test_to_create_table.cpp +++ b/tests/sqlite/test_to_create_table.cpp @@ -17,7 +17,7 @@ TEST(sqlite, test_to_create_table) { const auto create_table_stmt = sqlgen::parsing::to_create_table(); const auto conn = sqlgen::sqlite::connect().value(); const auto expected = - R"(CREATE TABLE "TestTable" IF NOT EXISTS ("field1" TEXT NOT NULL, "field2" INTEGER NOT NULL, "id" INTEGER PRIMARY KEY NOT NULL, "nullable" TEXT);)"; + R"(CREATE TABLE IF NOT EXISTS "TestTable" ("field1" TEXT NOT NULL, "field2" INTEGER NOT NULL, "id" INTEGER PRIMARY KEY NOT NULL, "nullable" TEXT);)"; EXPECT_EQ(conn->to_sql(create_table_stmt), expected); } diff --git a/tests/sqlite/test_write.cpp b/tests/sqlite/test_write.cpp new file mode 100644 index 0000000..fee23b1 --- /dev/null +++ b/tests/sqlite/test_write.cpp @@ -0,0 +1,30 @@ +#include + +#include +#include +#include + +namespace test_write { + +struct Person { + sqlgen::PrimaryKey id; + std::string first_name; + std::string last_name; + int age; +}; + +TEST(sqlite, test_write) { + const auto conn = sqlgen::sqlite::connect().value(); + + const auto people = std::vector( + {Person{ + .id = 0, .first_name = "Homer", .last_name = "Simpson", .age = 45}, + Person{.id = 1, .first_name = "Bart", .last_name = "Simpson", .age = 10}, + Person{.id = 2, .first_name = "Lisa", .last_name = "Simpson", .age = 8}, + Person{ + .id = 3, .first_name = "Maggie", .last_name = "Simpson", .age = 0}}); + + sqlgen::write(conn, people.begin(), people.end()).value(); +} + +} // namespace test_write