Implement null-type binding 57/285057/3
authorjh9216.park <jh9216.park@samsung.com>
Mon, 5 Dec 2022 10:53:16 +0000 (05:53 -0500)
committerjh9216.park <jh9216.park@samsung.com>
Mon, 5 Dec 2022 23:11:52 +0000 (18:11 -0500)
Change-Id: Idd700a3000ce130b3bd01ab9580507f3b7ca6d2f
Signed-off-by: jh9216.park <jh9216.park@samsung.com>
tests/tizen-database_unittests/src/test_database.cc
tizen-database/README.md
tizen-database/database.hpp

index 52bff43..d67f2e4 100644 (file)
@@ -148,6 +148,20 @@ class DatabaseTest : public ::testing::Test {
     auto r2 = db.Exec(q);
     ASSERT_TRUE(static_cast<bool>(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<bool>(r1));
+    auto r2 = db.Exec(q);
+    ASSERT_TRUE(static_cast<bool>(r2));
+  }
 };
 
 TEST_F(DatabaseTest, test_insert) {
@@ -186,6 +200,18 @@ TEST_F(DatabaseTest, test_insert_name) {
   EXPECT_TRUE(static_cast<bool>(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<bool>(r));
+}
+
 TEST_F(DatabaseTest, test_select) {
   SetDefault();
   using tizen_base::_;
@@ -198,7 +224,7 @@ TEST_F(DatabaseTest, test_select) {
     EXPECT_EQ(static_cast<std::string>(name), "gogo");
     EXPECT_EQ(static_cast<int>(num), 1234);
     EXPECT_EQ(static_cast<double>(val), 9.216);
-    std::vector<unsigned char> v = data;
+    auto v = static_cast<std::vector<unsigned char>>(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<bool>(r));
   for (const auto& i : r) {
-    std::string name = i.Get(0);
-    double val = i.Get(2);
+    std::string name = static_cast<std::string>(i.Get(0));
+    double val = static_cast<double>(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<bool>(r));
+  for (const auto& i : r) {
+    std::optional<std::string> name = i.Get(0);
+    double val = static_cast<double>(i.Get(2));
+    std::optional<std::vector<unsigned char>> 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<std::string>(rec.Get(0));
-      Num = rec.Get(1);
-      Val = rec.Get(2);
-      Data = rec.Get(3);
+      Num = static_cast<int>(rec.Get(1));
+      Val = static_cast<double>(rec.Get(2));
+      Data = static_cast<std::vector<unsigned char>>(rec.Get(3));
     }
   };
 
@@ -260,9 +303,9 @@ TEST_F(DatabaseTest, test_ToList) {
 
     void operator () (const tizen_base::Database::Result::Record& rec) {
       Name = static_cast<std::string>(rec.Get(0));
-      Num = rec.Get(1);
-      Val = rec.Get(2);
-      Data = rec.Get(3);
+      Num = static_cast<int>(rec.Get(1));
+      Val = static_cast<double>(rec.Get(2));
+      Data = static_cast<std::vector<unsigned char>>(rec.Get(3));
     }
   };
 
@@ -287,9 +330,9 @@ TEST_F(DatabaseTest, test_GetFirst) {
 
     void operator () (const tizen_base::Database::Result::Record& rec) {
       Name = static_cast<std::string>(rec.Get(0));
-      Num = rec.Get(1);
-      Val = rec.Get(2);
-      Data = rec.Get(3);
+      Num = static_cast<int>(rec.Get(1));
+      Val = static_cast<double>(rec.Get(2));
+      Data = static_cast<std::vector<unsigned char>>(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<std::string>(rec->Get(0));
+  double val = static_cast<double>(rec->Get(2));
   EXPECT_EQ(name, "gogo");
   EXPECT_EQ(val, 9.216);
 }
index 10c610a..d16bc6f 100644 (file)
@@ -21,7 +21,7 @@ void test() {
 }
 ```
 
-### Bind
+### Binding
 - Bind() method supports various data type such as std::string, int, double and std::vector<unsigned char>.
 
 ```cpp
@@ -44,6 +44,28 @@ void test() {
       .Bind(":data", std::vector<unsigned char> {'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<std::string> name = i.Get(0);
+    double val = static_cast<double>(i.Get(2));
+    std::optional<std::vector<unsigned char>> 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<unsigned char> data = i.Get(3);
+    auto name = static_cast<std::string>(i.Get(0));
+    auto num = static_cast<int>(i.Get(1));
+    auto val = static_cast<double>(i.Get(2));
+    auto data = static_cast<std::vector<unsigned char>>(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<std::string>(rec.Get(0));
-      Val = rec.Get(1);
+      Val = static_cast<int>(rec.Get(1));
     }
   };
 
index 9ace83c..492af5f 100644 (file)
@@ -23,6 +23,7 @@
 #include <list>
 #include <map>
 #include <memory>
+#include <optional>
 #include <string>
 #include <tuple>
 #include <utility>
@@ -46,28 +47,69 @@ void for_(F func) {
   for_(func, std::make_index_sequence<N>());
 }
 
-using DbType = std::variant<int, double, std::string,
-  std::vector<unsigned char>>;
+using DbType = std::optional<std::variant<int, double, std::string,
+    std::vector<unsigned char>>>;
 
 class AutoDbType {
  public:
   AutoDbType() = default;
   AutoDbType(DbType db_type) : db_type_(db_type) {}
 
-  operator int () {
-    return std::get<int>(db_type_);
+  explicit operator int () {
+    if (!db_type_)
+      throw std::runtime_error("invalid type conversion from nullopt to int");
+    return std::get<int>(*db_type_);
   }
 
-  operator std::string () {
-    return std::get<std::string>(db_type_);
+  explicit operator std::string () {
+    if (!db_type_) {
+      throw std::runtime_error(
+          "invalid type conversion from nullopt to string");
+    }
+
+    return std::get<std::string>(*db_type_);
+  }
+
+  explicit operator double () {
+    if (!db_type_) {
+      throw std::runtime_error(
+          "invalid type conversion from nullopt to double");
+    }
+
+    return std::get<double>(*db_type_);
+  }
+
+  explicit operator std::vector<unsigned char> () {
+    if (!db_type_) {
+      throw std::runtime_error(
+          "invalid type conversion from nullopt to std::vector<unsigned char>");
+    }
+
+    return std::get<std::vector<unsigned char>>(*db_type_);
   }
 
-  operator double () {
-    return std::get<double>(db_type_);
+  operator std::optional<int> () {
+    if (!db_type_)
+      return std::nullopt;
+    return std::get<int>(*db_type_);
+  }
+
+  operator std::optional<std::string> () {
+    if (!db_type_)
+      return std::nullopt;
+    return std::get<std::string>(*db_type_);
   }
 
-  operator std::vector<unsigned char> () {
-    return std::get<std::vector<unsigned char>>(db_type_);
+  operator std::optional<double> () {
+    if (!db_type_)
+      return std::nullopt;
+    return std::get<double>(*db_type_);
+  }
+
+  operator std::optional<std::vector<unsigned char>> () {
+    if (!db_type_)
+      return std::nullopt;
+    return std::get<std::vector<unsigned char>>(*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<DbType>& GetBindings() const {
       return bindings_;
     }
@@ -246,8 +303,6 @@ class Database {
       AutoDbType Get(int pos) const {
         sqlite3_stmt* stmt = const_cast<sqlite3_stmt*>(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<unsigned char>(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<std::string>(&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<std::string>(&(*type))) {
       r = sqlite3_bind_text(stmt, pos, (*pstr).c_str(), -1,
           SQLITE_TRANSIENT);
-    } else if (const int* pint = std::get_if<int>(&type)) {
+    } else if (const int* pint = std::get_if<int>(&(*type))) {
       r = sqlite3_bind_int(stmt, pos, (*pint));
-    } else if (const double* pdouble = std::get_if<double>(&type)) {
+    } else if (const double* pdouble = std::get_if<double>(&(*type))) {
       r = sqlite3_bind_double(stmt, pos, (*pdouble));
     } else if (const std::vector<unsigned char>* pvector =
-        std::get_if<std::vector<unsigned char>>(&type)) {
+        std::get_if<std::vector<unsigned char>>(&(*type))) {
       r = sqlite3_bind_blob(stmt, pos, (*pvector).data(),
           (*pvector).size(), nullptr);
     } else {