Add OneStepExec() method to tizen_base::Database
[platform/core/base/bundle.git] / tizen-database / database.hpp
index 9ace83c..32140b0 100644 (file)
@@ -23,6 +23,7 @@
 #include <list>
 #include <map>
 #include <memory>
+#include <optional>
 #include <string>
 #include <tuple>
 #include <utility>
@@ -46,28 +47,95 @@ 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 DbException : public std::runtime_error {
+ public:
+  explicit DbException(const std::string& msg, int code = SQLITE_ERROR)
+      : std::runtime_error(msg), code_(code) {
+  }
+
+  DbException(const std::string& msg, int code, const std::string& file,
+      int line) : std::runtime_error(msg), code_(code) {
+    msg_ = msg + " at " + file + ":" + std::to_string(line);
+  }
+
+  int code() const {
+    return code_;
+  }
+
+  const char* msg() const {
+    if (msg_.empty())
+      return what();
+    return msg_.c_str();
+  }
+
+ private:
+  int code_;
+  std::string msg_;
+};
 
 class AutoDbType {
  public:
   AutoDbType() = default;
-  AutoDbType(DbType db_type) : db_type_(db_type) {}
+  explicit AutoDbType(DbType db_type) : db_type_(db_type) {}
+
+  explicit operator int () {
+    if (!db_type_)
+      throw DbException("invalid type conversion from nullopt to int");
+    return std::get<int>(*db_type_);
+  }
+
+  explicit operator std::string () {
+    if (!db_type_) {
+      throw DbException(
+          "invalid type conversion from nullopt to string");
+    }
+
+    return std::get<std::string>(*db_type_);
+  }
+
+  explicit operator double () {
+    if (!db_type_) {
+      throw DbException(
+          "invalid type conversion from nullopt to double");
+    }
+
+    return std::get<double>(*db_type_);
+  }
+
+  explicit operator std::vector<unsigned char> () {
+    if (!db_type_) {
+      throw DbException(
+          "invalid type conversion from nullopt to std::vector<unsigned char>");
+    }
+
+    return std::get<std::vector<unsigned char>>(*db_type_);
+  }
 
-  operator int () {
-    return std::get<int>(db_type_);
+  operator std::optional<int> () {
+    if (!db_type_)
+      return std::nullopt;
+    return std::get<int>(*db_type_);
   }
 
-  operator std::string () {
-    return std::get<std::string>(db_type_);
+  operator std::optional<std::string> () {
+    if (!db_type_)
+      return std::nullopt;
+    return std::get<std::string>(*db_type_);
   }
 
-  operator double () {
-    return std::get<double>(db_type_);
+  operator std::optional<double> () {
+    if (!db_type_)
+      return std::nullopt;
+    return std::get<double>(*db_type_);
   }
 
-  operator std::vector<unsigned char> () {
-    return std::get<std::vector<unsigned char>>(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:
@@ -99,10 +167,10 @@ class Database {
       return *this;
     }
 
-    TransactionGuard(sqlite3* db) : db_(db) {
-      if (sqlite3_exec(db, "BEGIN DEFERRED", nullptr, nullptr, nullptr)
-          != SQLITE_OK) {
-        throw std::runtime_error("begin transaction failed");
+    explicit TransactionGuard(sqlite3* db) : db_(db) {
+      int r = sqlite3_exec(db, "BEGIN DEFERRED", nullptr, nullptr, nullptr);
+      if (r != SQLITE_OK) {
+        throw DbException("begin transaction failed", r);
       }
     }
 
@@ -130,7 +198,10 @@ class Database {
     Sql(std::string query) : query_(std::move(query)) {}
 
     Sql& Bind(std::string val) {
-      bindings_.push_back(DbType(std::move(val)));
+      if (empty_string_as_null_ && val.empty())
+        bindings_.push_back(DbType(std::nullopt));
+      else
+        bindings_.push_back(DbType(std::move(val)));
       return *this;
     }
 
@@ -145,12 +216,23 @@ class Database {
     }
 
     Sql& Bind(std::vector<unsigned char> val) {
-      bindings_.push_back(DbType(std::move(val)));
+      if (empty_vector_as_null_ && val.empty())
+        bindings_.push_back(DbType(std::nullopt));
+      else
+        bindings_.push_back(DbType(std::move(val)));
+      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));
+      if (empty_string_as_null_ && val.empty())
+        binding_map_[pos] = DbType(std::nullopt);
+      else
+        binding_map_[pos] = DbType(std::move(val));
       return *this;
     }
 
@@ -165,12 +247,23 @@ class Database {
     }
 
     Sql& Bind(int pos, std::vector<unsigned char> val) {
-      binding_map_[pos] = DbType(std::move(val));
+      if (empty_vector_as_null_ && val.empty())
+        binding_map_[pos] = DbType(std::nullopt);
+      else
+        binding_map_[pos] = DbType(std::move(val));
+      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));
+      if (empty_string_as_null_ && val.empty())
+        binding_name_map_[std::move(name)] = DbType(std::nullopt);
+      else
+        binding_name_map_[std::move(name)] = DbType(std::move(val));
       return *this;
     }
 
@@ -185,7 +278,15 @@ class Database {
     }
 
     Sql& Bind(std::string name, std::vector<unsigned char> val) {
-      binding_name_map_[std::move(name)] = DbType(std::move(val));
+      if (empty_vector_as_null_ && val.empty())
+        binding_name_map_[std::move(name)] = DbType(std::nullopt);
+      else
+        binding_name_map_[std::move(name)] = DbType(std::move(val));
+      return *this;
+    }
+
+    Sql& Bind(std::string name, const std::nullopt_t& val) {
+      binding_name_map_[std::move(name)] = DbType(val);
       return *this;
     }
 
@@ -205,11 +306,32 @@ class Database {
       return query_;
     }
 
+    Sql& SetEmptyStringAsNull(bool as_null) {
+      empty_string_as_null_ = as_null;
+      return *this;
+    }
+
+    Sql& SetEmptyVectorAsNull(bool as_null) {
+      empty_vector_as_null_ = as_null;
+      return *this;
+    }
+
+    Sql& Reset() {
+      bindings_.clear();
+      binding_map_.clear();
+      binding_name_map_.clear();
+      empty_string_as_null_ = false;
+      empty_vector_as_null_ = false;
+      return *this;
+    }
+
    private:
     std::string query_;
     std::vector<DbType> bindings_;
     std::map<int, DbType> binding_map_;
     std::map<std::string, DbType> binding_name_map_;
+    bool empty_string_as_null_ = false;
+    bool empty_vector_as_null_ = false;
   };
 
   class Result {
@@ -241,13 +363,11 @@ class Database {
 
     class Record {
      public:
-      Record(const sqlite3_stmt* stmt) : stmt_(stmt) {}
+      explicit Record(const sqlite3_stmt* stmt) : stmt_(stmt) {}
 
       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) {
@@ -263,12 +383,14 @@ class Database {
           int len = sqlite3_column_bytes(stmt, pos);
 
           if (!val || len < 0) {
-            throw std::runtime_error("invalid blob");;
+            throw DbException("invalid blob");;
           } 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");
+          throw DbException("invalid column type", type);
         }
 
         return AutoDbType(dbt);
@@ -291,7 +413,7 @@ class Database {
 
     class Iterator {
      public:
-      Iterator(sqlite3_stmt* stmt) : stmt_(stmt) {}
+      explicit Iterator(sqlite3_stmt* stmt) : stmt_(stmt) {}
 
       Record operator*() { return Record(stmt_); }
 
@@ -388,24 +510,34 @@ class Database {
       return std::nullopt;
     }
 
+    sqlite3_stmt* GetRaw() const {
+      return stmt_;
+    }
+
+    const std::string& GetQuery() const {
+      return query_;
+    }
+
    private:
     friend class Database;
-    Result(sqlite3_stmt* stmt, sqlite3* db) : stmt_(stmt), db_(db) {}
+    Result(sqlite3_stmt* stmt, sqlite3* db, std::string query)
+        : stmt_(stmt), db_(db), query_(std::move(query)) {}
 
     sqlite3_stmt* stmt_ = nullptr;
     sqlite3* db_ = nullptr;
+    std::string query_;
   };
 
   Database(std::string db, int flags) {
     int r = sqlite3_open_v2(db.c_str(), &db_, flags, nullptr);
     if (r != SQLITE_OK)
-      throw std::runtime_error("open failed");
+      throw DbException("open failed", r);
   }
 
   Database(std::string db, int flags, std::function<bool(int)> busy_handler) {
     int r = sqlite3_open_v2(db.c_str(), &db_, flags, nullptr);
     if (r != SQLITE_OK)
-      throw std::runtime_error("sqlite3_open_v2() failed");
+      throw DbException("sqlite3_open_v2() failed", r);
 
     busy_handler_ = std::move(busy_handler);
     r = sqlite3_busy_handler(db_, [](void* data, int count) {
@@ -417,7 +549,7 @@ class Database {
 
     if (r != SQLITE_OK) {
       sqlite3_close_v2(db_);
-      throw std::runtime_error("sqlite3_busy_handler() failed");
+      throw DbException("sqlite3_busy_handler() failed", r);
     }
   }
 
@@ -452,19 +584,19 @@ class Database {
     return *this;
   }
 
-  TransactionGuard CreateTransactionGuard() {
+  TransactionGuard CreateTransactionGuard() const {
     return TransactionGuard(db_);
   }
 
   Result Exec(const Sql& sql) const {
     if (!db_)
-      throw std::runtime_error("Not opened");
+      throw DbException("Not opened");
 
     sqlite3_stmt* stmt = nullptr;
     int r = sqlite3_prepare_v2(db_, sql.GetQuery().c_str(),
         -1, &stmt, nullptr);
     if (r != SQLITE_OK) {
-      return { nullptr, nullptr };
+      return { nullptr, nullptr, "" };
     }
 
     std::unique_ptr<sqlite3_stmt, decltype(sqlite3_finalize)*> stmt_auto(stmt,
@@ -481,30 +613,84 @@ class Database {
     for (const auto& i : sql.GetBindingNameMap()) {
       int pos = sqlite3_bind_parameter_index(stmt, i.first.c_str());
       if (pos == 0)
-        throw std::runtime_error("Invalid binding");
+        throw DbException("Invalid binding");
       Bind(pos, i.second, stmt);
     }
 
     r = sqlite3_step(stmt);
     if (r != SQLITE_ROW && r != SQLITE_DONE) {
-      return { nullptr, db_ };
+      return { nullptr, db_, "" };
     }
 
-    return { stmt_auto.release(), db_ };
+    return { stmt_auto.release(), db_, sql.GetQuery() };
+  }
+
+  bool Exec(const Sql& sql, const Result& previous_stmt) {
+    if (sql.GetQuery() != previous_stmt.GetQuery())
+      throw DbException("Query is different");
+
+    sqlite3_stmt* stmt = previous_stmt.GetRaw();
+    if (!stmt)
+      return false;
+
+    int r = sqlite3_reset(stmt);
+    if (r != SQLITE_ROW && r != SQLITE_DONE && r != SQLITE_OK)
+      return false;
+
+    int pos = 1;
+    for (const auto& i : sql.GetBindings()) {
+      Bind(pos++, i, stmt);
+    }
+
+    for (const auto& i : sql.GetBindingMap()) {
+      Bind(i.first, i.second, stmt);
+    }
+
+    for (const auto& i : sql.GetBindingNameMap()) {
+      int pos = sqlite3_bind_parameter_index(stmt, i.first.c_str());
+      if (pos == 0)
+        throw DbException("Invalid binding");
+      Bind(pos, i.second, stmt);
+    }
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_ROW && r != SQLITE_DONE)
+      return false;
+
+    return true;
+  }
+
+  void OneStepExec(const Sql& sql) const {
+    char* errmsg = nullptr;
+    int ret = sqlite3_exec(db_, sql.GetQuery().c_str(), nullptr, nullptr,
+        &errmsg);
+    if (ret != SQLITE_OK) {
+       std::unique_ptr<char, decltype(free)*> errmsg_auto(errmsg, free);
+       throw DbException(errmsg);
+    }
   }
 
  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 DbException("Invalid binding", r);
+      }
+
+      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 {
@@ -512,7 +698,7 @@ class Database {
     }
 
     if (r != SQLITE_OK) {
-      throw std::runtime_error("Invalid binding");
+      throw DbException("Invalid binding");
     }
   }