Add sub package 'tizen-database' 42/285042/9
authorjh9216.park <jh9216.park@samsung.com>
Mon, 5 Dec 2022 05:44:06 +0000 (00:44 -0500)
committerjh9216.park <jh9216.park@samsung.com>
Mon, 5 Dec 2022 07:02:53 +0000 (02:02 -0500)
- This package provides C++ wrapper API for sqlite3

Change-Id: I05fce9e6deb4922a9dd7aa45f6e3771f80f6cb1b
Signed-off-by: jh9216.park <jh9216.park@samsung.com>
CMakeLists.txt
packaging/bundle.spec
tests/CMakeLists.txt
tests/tizen-database_unittests/CMakeLists.txt [new file with mode: 0644]
tests/tizen-database_unittests/src/test_database.cc [new file with mode: 0644]
tests/tizen-database_unittests/src/test_main.cc [new file with mode: 0644]
tizen-database/CMakeLists.txt [new file with mode: 0644]
tizen-database/README.md [new file with mode: 0644]
tizen-database/database.hpp [new file with mode: 0644]
tizen-database/tizen-database.pc.in [new file with mode: 0644]

index b355e28..f58604b 100644 (file)
@@ -49,6 +49,8 @@ INSTALL(FILES ${CMAKE_BINARY_DIR}/bundle.pc DESTINATION ${LIB_INSTALL_DIR}/pkgco
 ADD_SUBDIRECTORY(parcel)
 ADD_DEPENDENCIES(parcel bundle)
 
+ADD_SUBDIRECTORY(tizen-database)
+
 IF(NOT DEFINED MINIMUM_BUILD)
 ENABLE_TESTING()
 SET(BUNDLE_UNITTESTS bundle_unittests)
@@ -63,4 +65,8 @@ ADD_TEST(NAME ${PARCEL_UNITTESTS} COMMAND ${PARCEL_UNITTESTS}
         WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests/parcel_unittests)
 
 ADD_DEPENDENCIES(parcel_unittests parcel)
+
+SET(TIZEN_DATABASE_UNITTESTS tizen-database_unittests)
+ADD_TEST(NAME ${TIZEN_DATABASE_UNITTESTS} COMMAND ${TIZEN_DATABASE_UNITTESTS}
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests/tizen-database_unittests)
 ENDIF(NOT DEFINED MINIMUM_BUILD)
index 002de6c..b796cdf 100644 (file)
@@ -12,6 +12,7 @@ BuildRequires:  pkgconfig(glib-2.0)
 BuildRequires:  pkgconfig(dlog)
 BuildRequires:  pkgconfig(capi-base-common)
 BuildRequires:  pkgconfig(json-glib-1.0)
+BuildRequires:  pkgconfig(sqlite3)
 BuildRequires:  pkgconfig(gmock)
 
 %if 0%{?gcov:1}
@@ -80,6 +81,26 @@ Group:      Development/Libraries
 %description -n parcel-unittests
 GTest for parcel
 
+#################################################
+## tizen-database-devel
+#################################################
+%package -n tizen-database-devel
+Summary:    Tizen-database Library (devel)
+Group:      Development/Libraries
+
+%description -n tizen-database-devel
+Tizen-database Library (devel)
+
+#################################################
+# tizen-database-unittests
+#################################################
+%package -n tizen-database-unittests
+Summary:    GTest for tizen-database
+Group:      Development/Libraries
+
+%description -n tizen-database-unittests
+GTest for tizen-database
+
 %prep
 %setup -q -n %{name}-%{version}
 cp %{SOURCE1001} .
@@ -195,6 +216,9 @@ EOF
 mkdir -p %{buildroot}%{_bindir}/tizen-unittests/parcel
 install -m 0755 run-parcel-unittest.sh %{buildroot}%{_bindir}/tizen-unittests/parcel/run-unittest.sh
 
+mkdir -p %{buildroot}%{_bindir}/tizen-unittests/tizen-database
+install -m 0755 run-parcel-unittest.sh %{buildroot}%{_bindir}/tizen-unittests/tizen-database/run-unittest.sh
+
 %post -p /sbin/ldconfig
 
 %postun -p /sbin/ldconfig
@@ -255,3 +279,14 @@ install -m 0755 run-parcel-unittest.sh %{buildroot}%{_bindir}/tizen-unittests/pa
 %files -n parcel-unittests
 %{_bindir}/parcel_unittests
 %{_bindir}/tizen-unittests/parcel/run-unittest.sh
+
+#################################################
+# tizen-database-devel
+#################################################
+%files -n tizen-database-devel
+%{_includedir}/tizen-database/*
+%{_libdir}/pkgconfig/tizen-database.pc
+
+%files -n tizen-database-unittests
+%{_bindir}/tizen-database_unittests
+%{_bindir}/tizen-unittests/tizen-database/run-unittest.sh
index 30c8c6f..2091545 100644 (file)
@@ -1,2 +1,3 @@
 ADD_SUBDIRECTORY(bundle_unittests)
 ADD_SUBDIRECTORY(parcel_unittests)
+ADD_SUBDIRECTORY(tizen-database_unittests)
diff --git a/tests/tizen-database_unittests/CMakeLists.txt b/tests/tizen-database_unittests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a7a36fd
--- /dev/null
@@ -0,0 +1,29 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
+PROJECT(tizen-database_unittests CXX)
+
+INCLUDE(FindPkgConfig)
+PKG_CHECK_MODULES(tizen-database_unittests REQUIRED
+    gmock
+    sqlite3
+)
+
+SET(EXTRA_CFLAGS "")
+FOREACH(flag ${database_unittests_CFLAGS})
+    SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -fvisibility=hidden -Wall -Werror")
+
+SET(CMAKE_CXX_FLAGS "${EXTRA_CFLAGS} -std=c++17")
+SET(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
+SET(CMAKE_CXX_FLAGS_RELEASE "-O2")
+
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../../)
+AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/src UNITTESTS_SOURCES)
+
+ADD_EXECUTABLE(${PROJECT_NAME}
+    ${UNITTESTS_SOURCES}
+)
+
+SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "${EXTRA_CFLAGS}")
+TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${tizen-database_unittests_LDFLAGS})
+INSTALL(TARGETS ${PROJECT_NAME} DESTINATION /usr/bin/)
\ No newline at end of file
diff --git a/tests/tizen-database_unittests/src/test_database.cc b/tests/tizen-database_unittests/src/test_database.cc
new file mode 100644 (file)
index 0000000..52bff43
--- /dev/null
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include <iostream>
+
+#include "tizen-database/database.hpp"
+
+namespace {
+
+constexpr const char TEST_DB[] = "test.db";
+constexpr const char Q_CREATE_TABLE[] =
+    "CREATE TABLE TestTable(name TEXT, num INTERGER, val REAL, data BLOB);";
+constexpr const char Q_DROP_TABLE[] = "DROP TABLE TestTable;";
+constexpr const char Q_INSERT[] =
+    "INSERT INTO TestTable(name, num, val, data) VALUES (?, ?, ?, ?);";
+constexpr const char Q_INSERT_POS[] =
+    "INSERT INTO TestTable(name, num, val, data) "
+    "VALUES (?101, ?102, ?103, ?104);";
+constexpr const char Q_INSERT_NAME[] =
+    "INSERT INTO TestTable(name, num, val, data) "
+    "VALUES (:name, :num, :val, :data);";
+constexpr char Q_SELECT[] = "SELECT * FROM TestTable;";
+constexpr char Q_UPDATE[] = "UPDATE TestTable SET name=?;";
+constexpr char Q_DELETE[] = "DELETE FROM TestTable WHERE name=?;";
+
+}  // namespace
+
+TEST(DBBasicTest, create_table) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
+  auto r = db.Exec({ Q_CREATE_TABLE });
+
+  EXPECT_TRUE(static_cast<bool>(r));
+}
+
+TEST(DBBasicTest, drop_table) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
+  db.Exec({ Q_CREATE_TABLE });
+  auto r1 = db.Exec({ Q_DROP_TABLE });
+
+  EXPECT_TRUE(static_cast<bool>(r1));
+}
+
+TEST(DBBasicTest, db_move_ctor) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
+  tizen_base::Database db2(std::move(db));
+
+  EXPECT_FALSE(static_cast<bool>(db));
+  EXPECT_TRUE(static_cast<bool>(db2));
+}
+
+TEST(DBBasicTest, db_move_assignment) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
+  tizen_base::Database db2;
+  db2 = std::move(db);
+
+  EXPECT_FALSE(static_cast<bool>(db));
+  EXPECT_TRUE(static_cast<bool>(db2));
+}
+
+TEST(DBBasicTest, busy_handler) {
+  constexpr useconds_t BUSY_WAITING_USEC = (1000000 / 10 / 2); /* 0.05 sec */
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE |
+      SQLITE_OPEN_CREATE, [&](int count) {
+    usleep(BUSY_WAITING_USEC);
+    if (count < 10)
+      return true;
+
+    return false;
+  });
+
+  EXPECT_TRUE(static_cast<bool>(db));
+}
+
+class ResultTest : public ::testing::Test {
+ public:
+  virtual void SetUp() {
+    tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+    db.Exec({ Q_DROP_TABLE });
+  }
+
+  virtual void TearDown() {
+    tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+    db.Exec({ Q_DROP_TABLE });
+  }
+};
+
+TEST_F(ResultTest, result_move_ctor) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  tizen_base::Database::Result r = db.Exec({ Q_CREATE_TABLE });
+  ASSERT_TRUE(static_cast<bool>(r));
+  tizen_base::Database::Result r2(std::move(r));
+
+  EXPECT_FALSE(static_cast<bool>(r));
+  EXPECT_TRUE(static_cast<bool>(r2));
+}
+
+TEST_F(ResultTest, result_move_assignment) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  tizen_base::Database::Result r = db.Exec({ Q_CREATE_TABLE });
+  ASSERT_TRUE(static_cast<bool>(r));
+  tizen_base::Database::Result r2;
+  ASSERT_FALSE(static_cast<bool>(r2));
+  r2 = std::move(r);
+
+  EXPECT_FALSE(static_cast<bool>(r));
+  EXPECT_TRUE(static_cast<bool>(r2));
+}
+
+class DatabaseTest : public ::testing::Test {
+ public:
+  virtual void SetUp() {
+    tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE |
+        SQLITE_OPEN_CREATE);
+    db.Exec({ Q_CREATE_TABLE });
+  }
+
+  virtual void TearDown() {
+    tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+    db.Exec({ Q_DROP_TABLE });
+  }
+
+  void SetDefault() {
+    tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+    auto q = tizen_base::Database::Sql(Q_INSERT)
+        .Bind("gogo")
+        .Bind(1234)
+        .Bind(9.216)
+        .Bind(std::vector<unsigned char> {'9', '2', '1', '6' });
+
+    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) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto q = tizen_base::Database::Sql(Q_INSERT)
+      .Bind("gogo")
+      .Bind(1234)
+      .Bind(9.216)
+      .Bind(std::vector<unsigned char> {'9', '2', '1', '6' });
+  auto r = db.Exec(q);
+
+  EXPECT_TRUE(static_cast<bool>(r));
+}
+
+TEST_F(DatabaseTest, test_insert_pos) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto q = tizen_base::Database::Sql(Q_INSERT_POS)
+      .Bind(101, "gogo")
+      .Bind(102, 1234)
+      .Bind(103, 9.216)
+      .Bind(104, std::vector<unsigned char> {'9', '2', '1', '6' });
+  auto r = db.Exec(q);
+
+  EXPECT_TRUE(static_cast<bool>(r));
+}
+
+TEST_F(DatabaseTest, test_insert_name) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto q = tizen_base::Database::Sql(Q_INSERT_NAME)
+      .Bind(":name", "gogo")
+      .Bind(":num", 1234)
+      .Bind(":val", 9.216)
+      .Bind(":data", std::vector<unsigned char> {'9', '2', '1', '6' });
+  auto r = db.Exec(q);
+
+  EXPECT_TRUE(static_cast<bool>(r));
+}
+
+TEST_F(DatabaseTest, test_select) {
+  SetDefault();
+  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) {
+    auto [name, num, val, data] = i.Get<_, _, _, _>();
+    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;
+    EXPECT_EQ(v.size(), 4);
+    EXPECT_EQ(v[0], '9');
+    EXPECT_EQ(v[1], '2');
+    EXPECT_EQ(v[2], '1');
+    EXPECT_EQ(v[3], '6');
+  }
+}
+
+TEST_F(DatabaseTest, test_select2) {
+  SetDefault();
+  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::string name = i.Get(0);
+    double val = i.Get(2);
+    EXPECT_EQ(name, "gogo");
+    EXPECT_EQ(val, 9.216);
+  }
+}
+
+TEST_F(DatabaseTest, test_ToVector) {
+  SetDefault();
+
+  struct Rec {
+    std::string Name;
+    int Num;
+    double Val;
+    std::vector<unsigned char> Data;
+
+    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);
+    }
+  };
+
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto table = db.Exec({ Q_SELECT }).ToVector<Rec>();
+
+  EXPECT_GT(table.size(), 0);
+  for (const auto& i : table) {
+    EXPECT_EQ(i.Name, "gogo");
+    EXPECT_EQ(i.Val, 9.216);
+  }
+}
+
+TEST_F(DatabaseTest, test_ToList) {
+  SetDefault();
+
+  struct Rec {
+    std::string Name;
+    int Num;
+    double Val;
+    std::vector<unsigned char> Data;
+
+    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);
+    }
+  };
+
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto table = db.Exec({ Q_SELECT }).ToList<Rec>();
+
+  EXPECT_GT(table.size(), 0);
+  for (const auto& i : table) {
+    EXPECT_EQ(i.Name, "gogo");
+    EXPECT_EQ(i.Val, 9.216);
+  }
+}
+
+TEST_F(DatabaseTest, test_GetFirst) {
+  SetDefault();
+
+  struct Rec {
+    std::string Name;
+    int Num;
+    double Val;
+    std::vector<unsigned char> Data;
+
+    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);
+    }
+  };
+
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto rec = db.Exec({ Q_SELECT }).GetFirst<Rec>();
+
+  EXPECT_TRUE(rec);
+  EXPECT_EQ(rec->Name, "gogo");
+  EXPECT_EQ(rec->Val, 9.216);
+}
+
+TEST_F(DatabaseTest, test_GetFirstRecord) {
+  SetDefault();
+  using tizen_base::_;
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto r = db.Exec({ Q_SELECT });
+
+  EXPECT_TRUE(static_cast<bool>(r));
+  auto rec = r.GetFirstRecord();
+  EXPECT_TRUE(rec);
+
+  std::string name = rec->Get(0);
+  double val = rec->Get(2);
+  EXPECT_EQ(name, "gogo");
+  EXPECT_EQ(val, 9.216);
+}
+
+TEST_F(DatabaseTest, test_update) {
+  SetDefault();
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto q = tizen_base::Database::Sql(Q_UPDATE)
+        .Bind("gugu");
+  auto r = db.Exec(q);
+
+  EXPECT_TRUE(static_cast<bool>(r));
+}
+
+TEST_F(DatabaseTest, test_delete) {
+  SetDefault();
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto q = tizen_base::Database::Sql(Q_DELETE)
+        .Bind("gogo");
+  auto r = db.Exec(q);
+
+  EXPECT_TRUE(static_cast<bool>(r));
+}
+
+TEST_F(DatabaseTest, test_delete_n) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READONLY);
+  auto q = tizen_base::Database::Sql(Q_DELETE)
+        .Bind("gogo");
+  auto r = db.Exec(q);
+
+  EXPECT_FALSE(static_cast<bool>(r));
+  EXPECT_EQ(static_cast<int>(r), SQLITE_READONLY);
+  EXPECT_STREQ(r, "attempt to write a readonly database");
+}
+
+TEST_F(DatabaseTest, test_transaction_commit) {
+  tizen_base::Database db(TEST_DB, SQLITE_OPEN_READWRITE);
+  auto guard = db.CreateTransactionGuard();
+  auto q = tizen_base::Database::Sql(Q_INSERT)
+      .Bind("gogo")
+      .Bind(1234)
+      .Bind(9.216)
+      .Bind(std::vector<unsigned char> {'9', '2', '1', '6' });
+  auto r = db.Exec(q);
+  ASSERT_TRUE(static_cast<bool>(r));
+  int ret = guard.Commit();
+
+  EXPECT_EQ(ret, SQLITE_OK);
+}
diff --git a/tests/tizen-database_unittests/src/test_main.cc b/tests/tizen-database_unittests/src/test_main.cc
new file mode 100644 (file)
index 0000000..da13aab
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include <exception>
+
+int main(int argc, char** argv) {
+  try {
+    testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+  } catch (std::exception const &e) {
+    std::cout << "test_main caught exception: " << e.what() << std::endl;
+    return -1;
+  }
+}
diff --git a/tizen-database/CMakeLists.txt b/tizen-database/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9062594
--- /dev/null
@@ -0,0 +1,9 @@
+### Make pkgconfig file
+SET(PREFIX ${CMAKE_INSTALL_PREFIX})
+CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/tizen-database.pc.in
+  ${CMAKE_BINARY_DIR}/tizen-database.pc @ONLY)
+
+### Install
+INSTALL(FILES ${CMAKE_BINARY_DIR}/tizen-database.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig/)
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/database.hpp
+  DESTINATION include/tizen-database)
diff --git a/tizen-database/README.md b/tizen-database/README.md
new file mode 100644 (file)
index 0000000..10c610a
--- /dev/null
@@ -0,0 +1,195 @@
+# TizenDatabase
+Wrapper class for sqlite3
+
+## Source
+See database.hpp
+
+## Require
+sqlite3, C++17
+
+## How to use?
+Add 'BuildRequires: pkgconfig(tizen-database)' in your .spec file.
+
+## Cookbook
+
+### Auto close
+- It was implemented by RAII idiom. Database is automatically closed when it exits the scope.
+```cpp
+void test() {
+  tizen_base::Database db("test.db", SQLITE_OPEN_READWRITE);
+  ...
+}
+```
+
+### Bind
+- Bind() method supports various data type such as std::string, int, double and std::vector<unsigned char>.
+
+```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("gogo")
+      .Bind(1234)
+      .Bind(9.216)
+      .Bind( std::vector<unsigned char> {'9', '2', '1', '6' } );
+```
+
+- Bind() method also supports named tag using ':'.
+```cpp
+  tizen_base::Database db("test.db", SQLITE_OPEN_READWRITE);
+  auto q = tizen_base::Database::Sql("INSERT INTO TestTable(name, num, val, data) VALUES (:name, :num, :val, :data);")
+      .Bind(":name", "gogo")
+      .Bind(":num", 1234)
+      .Bind(":val", 9.216)
+      .Bind(":data", std::vector<unsigned char> {'9', '2', '1', '6' });
+```
+
+### Structured binding declaration
+```cpp
+  auto [name, num, val, data] = i.Get<_, _, _, _>();
+```
+- Structured binding declaration can be used to unpack the record.
+
+```cpp
+  for (const auto& i : db.Exec({ "SELECT name, num, val, data FROM TestTable;" })) {
+    auto [name, num, val, data] = i.Get<_, _, _, _>();
+    std::cout << static_cast<std::string>(name) << std::endl;
+  }
+```
+
+### Range-based for loop
+- class 'Result' is iterable.
+
+```cpp
+  tizen_base::Database::Result r =
+      db.Exec({ "SELECT name, num, val, data FROM TestTable;" });
+  for (const auto& i : r) {
+    ...
+  }
+```
+
+### Explicit 'bool' type and 'int' type conversion operator
+- 'bool' type operator is supported to check errors.
+- 'int' type operator is supported to get error number.
+```cpp
+  auto r = db.Exec(q);
+  if (!static_cast<bool>(r)) {
+    std::cout << "error code:" << static_cast<int>(r) << std::endl;
+  }
+```
+
+### Implicit 'const char*' type conversion operator
+- 'const char*' type operator is supported to get error string
+```cpp
+  auto r = db.Exec(q);
+  if (!static_cast<bool>(r)) {
+    std::cout << "error message:" << r << std::endl;
+  }
+```
+
+### Extract values from query results
+- 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);
+  }
+```
+
+### ToList(), ToVector() methods
+- Once you provide function operator in your classes, you can call the methods.
+- The class or struct should be movable.
+```cpp
+  struct MyRec {
+    std::string Id;
+    int Val;
+
+    // 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);
+    }
+  };
+
+  auto /*std::vector<MyRec>*/ table =
+      db.Exec({ "SELECT id, val FROM TestTable;" }).ToVector<MyRec>();
+```
+
+### Transaction guard
+```cpp
+void test() {
+  tizen_base::Database db("test.db", SQLITE_OPEN_READWRITE);
+  auto guard = db.CreateTransactionGuard();
+
+  auto r = db.Exec({ "INSERT INTO MyTable(name, id) VALUES('gogo', 1);" });
+  if (!static_cast<bool>(r))
+    return; // Rollback
+
+  auto r2 = db.Exec({ "INSERT INTO MyTable(name, id) VALUES('gugu', 2);" });
+  if (!static_cast<bool>(r2))
+    return; // Rollback
+
+  guard.Commit();
+}
+```
+
+### Busy handler
+```cpp
+void test() {
+  tizen_base::Database db("test.db", SQLITE_OPEN_READWRITE, [&](int count) {
+    // Busy handler
+    usleep(BUSY_WAITING_USEC);
+    if (count < 10)
+      return true;
+
+    return false;
+  });
+  ...
+}
+```
+
+### Samples
+```cpp
+void test() {
+  tizen_base::Database db("test.db", SQLITE_OPEN_READWRITE);
+  for (const auto& i : db.Exec({ "SELECT name, num, val, data FROM TestTable;" })) {
+    auto [name, num, val, data] = i.Get<_, _, _, _>();
+    std::cout << static_cast<std::string>(name) << std::endl;
+  }
+}
+```
+
+```cpp
+void test() {
+  tizen_base::Database db("test.db", SQLITE_OPEN_READWRITE);
+  auto q = tizen_base::Database::Sql("INSERT INTO TestTable(name, num, val, data) VALUES (?, ?, ?, ?);")
+      .Bind("gogo")
+      .Bind(1234)
+      .Bind(9.216)
+      .Bind( std::vector<unsigned char> {'9', '2', '1', '6' } );
+  auto r = db.Exec(q);
+  if (!static_cast<bool>(r)) {
+    std::cout << "insert failed:" << r << std::endl;
+    std::cout << "error code:" << static_cast<int>(r) << std::endl;
+  }
+}
+```
+
+```cpp
+void test() {
+  tizen_base::Database db("test.db", SQLITE_OPEN_READWRITE);
+  auto q = tizen_base::Database::Sql(
+      "INSERT INTO TestTable(name, num, val, data) VALUES (:name, :num, :val, :data);")
+      .Bind(":name", "gogo")
+      .Bind(":num", 1234)
+      .Bind(":val", 9.216)
+      .Bind(":data", std::vector<unsigned char> {'9', '2', '1', '6' });
+  auto r = db.Exec(q);
+  if (!static_cast<bool>(r)) {
+    std::cout << "insert failed:" << r << std::endl;
+    std::cout << "error code:" << static_cast<int>(r) << std::endl;
+  }
+}
+```
diff --git a/tizen-database/database.hpp b/tizen-database/database.hpp
new file mode 100644 (file)
index 0000000..9ace83c
--- /dev/null
@@ -0,0 +1,526 @@
+/*
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TIZEN_DATABASE_DATABASE_HPP_
+#define TIZEN_DATABASE_DATABASE_HPP_
+
+#include <sqlite3.h>
+
+#include <functional>
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace tizen_base {
+
+template<std::size_t N>
+struct num {
+  static const constexpr auto value = N;
+};
+
+template <class F, std::size_t... Is>
+void for_(F func, std::index_sequence<Is...>) {
+  (func(num<Is>{}), ...);
+}
+
+template <std::size_t N, typename F>
+void for_(F func) {
+  for_(func, std::make_index_sequence<N>());
+}
+
+using DbType = 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_);
+  }
+
+  operator std::string () {
+    return std::get<std::string>(db_type_);
+  }
+
+  operator double () {
+    return std::get<double>(db_type_);
+  }
+
+  operator std::vector<unsigned char> () {
+    return std::get<std::vector<unsigned char>>(db_type_);
+  }
+
+ private:
+  DbType db_type_;
+};
+
+using _ = AutoDbType;
+
+class Database {
+ public:
+  class TransactionGuard {
+   public:
+    TransactionGuard(const TransactionGuard&) = delete;
+    TransactionGuard& operator = (const TransactionGuard&) = delete;
+
+    TransactionGuard(TransactionGuard&& t) noexcept {
+      db_ = t.db_;
+      t.db_ = nullptr;
+    }
+
+    TransactionGuard& operator = (TransactionGuard&& t) noexcept {
+      if (this != &t) {
+        if (db_)
+          sqlite3_exec(db_, "ROLLBACK", nullptr, nullptr, nullptr);
+        db_ = t.db_;
+        t.db_ = nullptr;
+      }
+
+      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");
+      }
+    }
+
+    ~TransactionGuard() {
+      if (db_)
+        sqlite3_exec(db_, "ROLLBACK", nullptr, nullptr, nullptr);
+    }
+
+    int Commit() {
+      int ret = sqlite3_exec(db_, "COMMIT", nullptr, nullptr, nullptr);
+      if (ret != SQLITE_OK) {
+        sqlite3_exec(db_, "ROLLBACK", nullptr, nullptr, nullptr);
+      }
+
+      db_ = nullptr;
+      return ret;
+    }
+
+   private:
+    sqlite3* db_ = nullptr;
+  };
+
+  class Sql {
+   public:
+    Sql(std::string query) : query_(std::move(query)) {}
+
+    Sql& Bind(std::string val) {
+      bindings_.push_back(DbType(std::move(val)));
+      return *this;
+    }
+
+    Sql& Bind(int val) {
+      bindings_.push_back(DbType(val));
+      return *this;
+    }
+
+    Sql& Bind(double val) {
+      bindings_.push_back(DbType(val));
+      return *this;
+    }
+
+    Sql& Bind(std::vector<unsigned char> val) {
+      bindings_.push_back(DbType(std::move(val)));
+      return *this;
+    }
+
+    Sql& Bind(int pos, std::string val) {
+      binding_map_[pos] = DbType(std::move(val));
+      return *this;
+    }
+
+    Sql& Bind(int pos, int val) {
+      binding_map_[pos] = DbType(val);
+      return *this;
+    }
+
+    Sql& Bind(int pos, double val) {
+      binding_map_[pos] = DbType(val);
+      return *this;
+    }
+
+    Sql& Bind(int pos, std::vector<unsigned char> val) {
+      binding_map_[pos] = DbType(std::move(val));
+      return *this;
+    }
+
+    Sql& Bind(std::string name, std::string val) {
+      binding_name_map_[std::move(name)] = DbType(std::move(val));
+      return *this;
+    }
+
+    Sql& Bind(std::string name, int val) {
+      binding_name_map_[std::move(name)] = DbType(val);
+      return *this;
+    }
+
+    Sql& Bind(std::string name, double val) {
+      binding_name_map_[std::move(name)] = DbType(val);
+      return *this;
+    }
+
+    Sql& Bind(std::string name, std::vector<unsigned char> val) {
+      binding_name_map_[std::move(name)] = DbType(std::move(val));
+      return *this;
+    }
+
+    const std::vector<DbType>& GetBindings() const {
+      return bindings_;
+    }
+
+    const std::map<int, DbType>& GetBindingMap() const {
+      return binding_map_;
+    }
+
+    const std::map<std::string, DbType>& GetBindingNameMap() const {
+      return binding_name_map_;
+    }
+
+    const std::string& GetQuery() const {
+      return query_;
+    }
+
+   private:
+    std::string query_;
+    std::vector<DbType> bindings_;
+    std::map<int, DbType> binding_map_;
+    std::map<std::string, DbType> binding_name_map_;
+  };
+
+  class Result {
+   public:
+    Result() = default;
+    ~Result() {
+      if (stmt_)
+        sqlite3_finalize(stmt_);
+    }
+
+    Result(const Result&) = delete;
+    Result& operator = (const Result&) = delete;
+
+    Result(Result&& r) noexcept {
+      stmt_ = r.stmt_;
+      r.stmt_ = nullptr;
+    }
+
+    Result& operator = (Result&& r) noexcept {
+      if (this != &r) {
+        if (stmt_)
+          sqlite3_finalize(stmt_);
+        stmt_ = r.stmt_;
+        r.stmt_ = nullptr;
+      }
+
+      return *this;
+    }
+
+    class Record {
+     public:
+      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) {
+          dbt = DbType(reinterpret_cast<const char*>(
+              sqlite3_column_text(stmt, pos)));
+        } else if (type == SQLITE_INTEGER) {
+          dbt = DbType(sqlite3_column_int(stmt, pos));
+        } else if (type == SQLITE_FLOAT) {
+          dbt = DbType(sqlite3_column_double(stmt, pos));
+        } else if (type == SQLITE_BLOB) {
+          const unsigned char* val = reinterpret_cast<const unsigned char*>(
+              sqlite3_column_blob(stmt, pos));
+          int len = sqlite3_column_bytes(stmt, pos);
+
+          if (!val || len < 0) {
+            throw std::runtime_error("invalid blob");;
+          } else {
+            dbt = DbType(std::vector<unsigned char>(val, val + len));
+          }
+        } else {
+          throw std::runtime_error("invalid column type");
+        }
+
+        return AutoDbType(dbt);
+      }
+
+      template <typename ...Types>
+      auto Get() const {
+        std::tuple<Types...> t;
+        int pos = 0;
+        for_<std::tuple_size_v<std::tuple<Types...>>>([&] (auto i) {
+          std::get<i.value>(t) = Get(pos++);
+        });
+
+        return t;
+      }
+
+     private:
+      const sqlite3_stmt* stmt_;
+    };
+
+    class Iterator {
+     public:
+      Iterator(sqlite3_stmt* stmt) : stmt_(stmt) {}
+
+      Record operator*() { return Record(stmt_); }
+
+      bool operator != (const Iterator& rhs) const {
+        return stmt_ != rhs.stmt_;
+      }
+
+      void operator ++() {
+        int r = sqlite3_step(stmt_);
+        if (r != SQLITE_ROW)
+          stmt_ = nullptr;
+      }
+
+     private:
+      sqlite3_stmt* stmt_ = nullptr;
+    };
+
+    Iterator begin() const {
+      return Iterator(stmt_);
+    }
+
+    Iterator end() const {
+      return Iterator(nullptr);
+    }
+
+    explicit operator bool() {
+      if (stmt_ == nullptr)
+        return false;
+      return true;
+    }
+
+    explicit operator int() {
+      if (db_ == nullptr)
+        return SQLITE_ERROR;
+      return sqlite3_errcode(db_);
+    }
+
+    operator const char*() {
+      if (db_ == nullptr)
+        return "";
+      return sqlite3_errmsg(db_);
+    }
+
+    template <class T>
+    std::vector<T> ToVector() {
+      if (stmt_ == nullptr)
+        return {};
+
+      std::vector<T> vec;
+      for (const auto& rec : *this) {
+        T t;
+        t(rec);
+        vec.push_back(std::move(t));
+      }
+
+      return vec;
+    }
+
+    template <class T>
+    std::list<T> ToList() {
+      if (stmt_ == nullptr)
+        return {};
+
+      std::list<T> l;
+      for (const auto& rec : *this) {
+        T t;
+        t(rec);
+        l.push_back(std::move(t));
+      }
+
+      return l;
+    }
+
+    template <class T>
+    std::optional<T> GetFirst() {
+      if (stmt_ == nullptr)
+        return std::nullopt;
+      for (const auto& rec : *this) {
+        T t;
+        t(rec);
+        return t;
+      }
+
+      return std::nullopt;
+    }
+
+    std::optional<Record> GetFirstRecord() {
+      if (stmt_ == nullptr)
+        return std::nullopt;
+      for (const auto& rec : *this) {
+        return rec;
+      }
+
+      return std::nullopt;
+    }
+
+   private:
+    friend class Database;
+    Result(sqlite3_stmt* stmt, sqlite3* db) : stmt_(stmt), db_(db) {}
+
+    sqlite3_stmt* stmt_ = nullptr;
+    sqlite3* db_ = nullptr;
+  };
+
+  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");
+  }
+
+  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");
+
+    busy_handler_ = std::move(busy_handler);
+    r = sqlite3_busy_handler(db_, [](void* data, int count) {
+      Database* pDb = static_cast<Database*>(data);
+      if (pDb->busy_handler_(count))
+        return 1;
+      return 0;
+    }, this);
+
+    if (r != SQLITE_OK) {
+      sqlite3_close_v2(db_);
+      throw std::runtime_error("sqlite3_busy_handler() failed");
+    }
+  }
+
+  ~Database() {
+    if (db_)
+      sqlite3_close_v2(db_);
+  }
+
+  Database() = default;
+  Database(const Database&) = delete;
+  Database& operator = (const Database&) = delete;
+
+  Database(Database&& db) noexcept {
+    db_ = db.db_;
+    db.db_ = nullptr;
+  }
+
+  explicit operator bool() {
+    if (db_ == nullptr)
+      return false;
+    return true;
+  }
+
+  Database& operator = (Database&& db) noexcept {
+    if (this != &db) {
+        if (db_)
+          sqlite3_close_v2(db_);
+        db_ = db.db_;
+        db.db_ = nullptr;
+    }
+
+    return *this;
+  }
+
+  TransactionGuard CreateTransactionGuard() {
+    return TransactionGuard(db_);
+  }
+
+  Result Exec(const Sql& sql) const {
+    if (!db_)
+      throw std::runtime_error("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 };
+    }
+
+    std::unique_ptr<sqlite3_stmt, decltype(sqlite3_finalize)*> stmt_auto(stmt,
+        sqlite3_finalize);
+    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 std::runtime_error("Invalid binding");
+      Bind(pos, i.second, stmt);
+    }
+
+    r = sqlite3_step(stmt);
+    if (r != SQLITE_ROW && r != SQLITE_DONE) {
+      return { nullptr, db_ };
+    }
+
+    return { stmt_auto.release(), db_ };
+  }
+
+ 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)) {
+      r = sqlite3_bind_text(stmt, pos, (*pstr).c_str(), -1,
+          SQLITE_TRANSIENT);
+    } 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)) {
+      r = sqlite3_bind_double(stmt, pos, (*pdouble));
+    } else if (const std::vector<unsigned char>* pvector =
+        std::get_if<std::vector<unsigned char>>(&type)) {
+      r = sqlite3_bind_blob(stmt, pos, (*pvector).data(),
+          (*pvector).size(), nullptr);
+    } else {
+      r = -1;
+    }
+
+    if (r != SQLITE_OK) {
+      throw std::runtime_error("Invalid binding");
+    }
+  }
+
+ private:
+  sqlite3* db_ = nullptr;
+  std::function<bool(int)> busy_handler_;
+};
+
+}  // namespace tizen_base
+
+#endif  // TIZEN_DATABASE_DATABASE_HPP_
diff --git a/tizen-database/tizen-database.pc.in b/tizen-database/tizen-database.pc.in
new file mode 100644 (file)
index 0000000..82ee192
--- /dev/null
@@ -0,0 +1,11 @@
+# Wrapper class for sqlite3
+
+prefix=@PREFIX@
+includedir=${prefix}/include
+
+Name: tizen-database
+Description: Wrapper class for sqlite3
+Version: @VERSION@
+Requires: sqlite3
+Cflags: -I${includedir} -I${includedir}/tizen-database
+cppflags: -I${includedir} -I${includedir}/tizen-database