From 375603907b5844eb3836d70e2cf0c439b9d48fef Mon Sep 17 00:00:00 2001 From: "jh9216.park" Date: Mon, 5 Dec 2022 05:53:16 -0500 Subject: [PATCH] Implement null-type binding Change-Id: Idd700a3000ce130b3bd01ab9580507f3b7ca6d2f Signed-off-by: jh9216.park --- .../tizen-database_unittests/src/test_database.cc | 71 ++++++++++++---- tizen-database/README.md | 34 ++++++-- tizen-database/database.hpp | 98 ++++++++++++++++++---- 3 files changed, 167 insertions(+), 36 deletions(-) diff --git a/tests/tizen-database_unittests/src/test_database.cc b/tests/tizen-database_unittests/src/test_database.cc index 52bff43..d67f2e4 100644 --- a/tests/tizen-database_unittests/src/test_database.cc +++ b/tests/tizen-database_unittests/src/test_database.cc @@ -148,6 +148,20 @@ class DatabaseTest : public ::testing::Test { auto r2 = db.Exec(q); ASSERT_TRUE(static_cast(r2)); } + + void SetDefaultWithNull() { + tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE); + auto q = tizen_base::Database::Sql(Q_INSERT) + .Bind(std::nullopt) + .Bind(1234) + .Bind(9.216) + .Bind(std::nullopt); + + auto r1 = db.Exec(q); + ASSERT_TRUE(static_cast(r1)); + auto r2 = db.Exec(q); + ASSERT_TRUE(static_cast(r2)); + } }; TEST_F(DatabaseTest, test_insert) { @@ -186,6 +200,18 @@ TEST_F(DatabaseTest, test_insert_name) { EXPECT_TRUE(static_cast(r)); } +TEST_F(DatabaseTest, test_insert_name_and_data_with_null) { + tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE); + auto q = tizen_base::Database::Sql(Q_INSERT_NAME) + .Bind(":name", std::nullopt) + .Bind(":num", 1234) + .Bind(":val", 9.216) + .Bind(":data", std::nullopt); + auto r = db.Exec(q); + + EXPECT_TRUE(static_cast(r)); +} + TEST_F(DatabaseTest, test_select) { SetDefault(); using tizen_base::_; @@ -198,7 +224,7 @@ TEST_F(DatabaseTest, test_select) { EXPECT_EQ(static_cast(name), "gogo"); EXPECT_EQ(static_cast(num), 1234); EXPECT_EQ(static_cast(val), 9.216); - std::vector v = data; + auto v = static_cast>(data); EXPECT_EQ(v.size(), 4); EXPECT_EQ(v[0], '9'); EXPECT_EQ(v[1], '2'); @@ -215,13 +241,30 @@ TEST_F(DatabaseTest, test_select2) { EXPECT_TRUE(static_cast(r)); for (const auto& i : r) { - std::string name = i.Get(0); - double val = i.Get(2); + std::string name = static_cast(i.Get(0)); + double val = static_cast(i.Get(2)); EXPECT_EQ(name, "gogo"); EXPECT_EQ(val, 9.216); } } +TEST_F(DatabaseTest, test_select3) { + SetDefaultWithNull(); + using tizen_base::_; + tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE); + auto r = db.Exec({ Q_SELECT }); + + EXPECT_TRUE(static_cast(r)); + for (const auto& i : r) { + std::optional name = i.Get(0); + double val = static_cast(i.Get(2)); + std::optional> data = i.Get(3); + EXPECT_FALSE(name); + EXPECT_EQ(val, 9.216); + EXPECT_FALSE(data); + } +} + TEST_F(DatabaseTest, test_ToVector) { SetDefault(); @@ -233,9 +276,9 @@ TEST_F(DatabaseTest, test_ToVector) { void operator () (const tizen_base::Database::Result::Record& rec) { Name = static_cast(rec.Get(0)); - Num = rec.Get(1); - Val = rec.Get(2); - Data = rec.Get(3); + Num = static_cast(rec.Get(1)); + Val = static_cast(rec.Get(2)); + Data = static_cast>(rec.Get(3)); } }; @@ -260,9 +303,9 @@ TEST_F(DatabaseTest, test_ToList) { void operator () (const tizen_base::Database::Result::Record& rec) { Name = static_cast(rec.Get(0)); - Num = rec.Get(1); - Val = rec.Get(2); - Data = rec.Get(3); + Num = static_cast(rec.Get(1)); + Val = static_cast(rec.Get(2)); + Data = static_cast>(rec.Get(3)); } }; @@ -287,9 +330,9 @@ TEST_F(DatabaseTest, test_GetFirst) { void operator () (const tizen_base::Database::Result::Record& rec) { Name = static_cast(rec.Get(0)); - Num = rec.Get(1); - Val = rec.Get(2); - Data = rec.Get(3); + Num = static_cast(rec.Get(1)); + Val = static_cast(rec.Get(2)); + Data = static_cast>(rec.Get(3)); } }; @@ -311,8 +354,8 @@ TEST_F(DatabaseTest, test_GetFirstRecord) { auto rec = r.GetFirstRecord(); EXPECT_TRUE(rec); - std::string name = rec->Get(0); - double val = rec->Get(2); + std::string name = static_cast(rec->Get(0)); + double val = static_cast(rec->Get(2)); EXPECT_EQ(name, "gogo"); EXPECT_EQ(val, 9.216); } diff --git a/tizen-database/README.md b/tizen-database/README.md index 10c610a..d16bc6f 100644 --- a/tizen-database/README.md +++ b/tizen-database/README.md @@ -21,7 +21,7 @@ void test() { } ``` -### Bind +### Binding - Bind() method supports various data type such as std::string, int, double and std::vector. ```cpp @@ -44,6 +44,28 @@ void test() { .Bind(":data", std::vector {'9', '2', '1', '6' }); ``` +### Null-type binding +- Null-type binding is supported using 'std::nullopt'. + +```cpp + tizen_base::Database db("test.db", SQLITE_OPEN_READWRITE); + auto q = tizen_base::Database::Sql( + "INSERT INTO TestTable(name, num, val, data) VALUES (?, ?, ?, ?);") + .Bind(std::nullopt) + .Bind(1234) + .Bind(9.216) + .Bind(std::nullopt); +``` +- To get nullable object, you can use 'std::optional<>'. +```cpp + for (const auto& i : r) { + std::optional name = i.Get(0); + double val = static_cast(i.Get(2)); + std::optional> data = i.Get(3); + ... + } +``` + ### Structured binding declaration ```cpp auto [name, num, val, data] = i.Get<_, _, _, _>(); @@ -91,10 +113,10 @@ void test() { - There are many conversion operators to get the value. ```cpp for (const auto& i : db.Exec({ "SELECT name, num, val, data FROM TestTable;" })) { - std::string name = i.Get(0); - int num = i.Get(1); - double val = i.Get(2); - std::vector data = i.Get(3); + auto name = static_cast(i.Get(0)); + auto num = static_cast(i.Get(1)); + auto val = static_cast(i.Get(2)); + auto data = static_cast>(i.Get(3)); } ``` @@ -109,7 +131,7 @@ void test() { // It should be implemented to map data set void operator () (const tizen_base::Database::Result::Record& rec) { Id = static_cast(rec.Get(0)); - Val = rec.Get(1); + Val = static_cast(rec.Get(1)); } }; diff --git a/tizen-database/database.hpp b/tizen-database/database.hpp index 9ace83c..492af5f 100644 --- a/tizen-database/database.hpp +++ b/tizen-database/database.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -46,28 +47,69 @@ void for_(F func) { for_(func, std::make_index_sequence()); } -using DbType = std::variant>; +using DbType = std::optional>>; class AutoDbType { public: AutoDbType() = default; AutoDbType(DbType db_type) : db_type_(db_type) {} - operator int () { - return std::get(db_type_); + explicit operator int () { + if (!db_type_) + throw std::runtime_error("invalid type conversion from nullopt to int"); + return std::get(*db_type_); } - operator std::string () { - return std::get(db_type_); + explicit operator std::string () { + if (!db_type_) { + throw std::runtime_error( + "invalid type conversion from nullopt to string"); + } + + return std::get(*db_type_); + } + + explicit operator double () { + if (!db_type_) { + throw std::runtime_error( + "invalid type conversion from nullopt to double"); + } + + return std::get(*db_type_); + } + + explicit operator std::vector () { + if (!db_type_) { + throw std::runtime_error( + "invalid type conversion from nullopt to std::vector"); + } + + return std::get>(*db_type_); } - operator double () { - return std::get(db_type_); + operator std::optional () { + if (!db_type_) + return std::nullopt; + return std::get(*db_type_); + } + + operator std::optional () { + if (!db_type_) + return std::nullopt; + return std::get(*db_type_); } - operator std::vector () { - return std::get>(db_type_); + operator std::optional () { + if (!db_type_) + return std::nullopt; + return std::get(*db_type_); + } + + operator std::optional> () { + if (!db_type_) + return std::nullopt; + return std::get>(*db_type_); } private: @@ -149,6 +191,11 @@ class Database { return *this; } + Sql& Bind(const std::nullopt_t val) { + bindings_.push_back(DbType(val)); + return *this; + } + Sql& Bind(int pos, std::string val) { binding_map_[pos] = DbType(std::move(val)); return *this; @@ -169,6 +216,11 @@ class Database { return *this; } + Sql& Bind(int pos, const std::nullopt_t& val) { + binding_map_[pos] = DbType(val); + return *this; + } + Sql& Bind(std::string name, std::string val) { binding_name_map_[std::move(name)] = DbType(std::move(val)); return *this; @@ -189,6 +241,11 @@ class Database { return *this; } + Sql& Bind(std::string name, const std::nullopt_t& val) { + binding_name_map_[std::move(name)] = DbType(val); + return *this; + } + const std::vector& GetBindings() const { return bindings_; } @@ -246,8 +303,6 @@ class Database { AutoDbType Get(int pos) const { sqlite3_stmt* stmt = const_cast(stmt_); int type = sqlite3_column_type(stmt, pos); - if (type == SQLITE_NULL) - throw std::runtime_error("invalid column"); DbType dbt; if (type == SQLITE_TEXT) { @@ -267,6 +322,8 @@ class Database { } else { dbt = DbType(std::vector(val, val + len)); } + } else if (type == SQLITE_NULL) { + dbt = DbType(std::nullopt); } else { throw std::runtime_error("invalid column type"); } @@ -496,15 +553,24 @@ class Database { private: void Bind(int pos, const DbType& type, sqlite3_stmt* stmt) const { int r; - if (const std::string* pstr = std::get_if(&type)) { + if (!type) { + r = sqlite3_bind_null(stmt, pos); + if (r != SQLITE_OK) { + throw std::runtime_error("Invalid binding"); + } + + return; + } + + if (const std::string* pstr = std::get_if(&(*type))) { r = sqlite3_bind_text(stmt, pos, (*pstr).c_str(), -1, SQLITE_TRANSIENT); - } else if (const int* pint = std::get_if(&type)) { + } else if (const int* pint = std::get_if(&(*type))) { r = sqlite3_bind_int(stmt, pos, (*pint)); - } else if (const double* pdouble = std::get_if(&type)) { + } else if (const double* pdouble = std::get_if(&(*type))) { r = sqlite3_bind_double(stmt, pos, (*pdouble)); } else if (const std::vector* pvector = - std::get_if>(&type)) { + std::get_if>(&(*type))) { r = sqlite3_bind_blob(stmt, pos, (*pvector).data(), (*pvector).size(), nullptr); } else { -- 2.7.4