ADD_SUBDIRECTORY(parcel)
ADD_DEPENDENCIES(parcel bundle)
+ADD_SUBDIRECTORY(tizen-database)
+
IF(NOT DEFINED MINIMUM_BUILD)
ENABLE_TESTING()
SET(BUNDLE_UNITTESTS bundle_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)
BuildRequires: pkgconfig(dlog)
BuildRequires: pkgconfig(capi-base-common)
BuildRequires: pkgconfig(json-glib-1.0)
+BuildRequires: pkgconfig(sqlite3)
BuildRequires: pkgconfig(gmock)
%if 0%{?gcov:1}
%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} .
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
%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
ADD_SUBDIRECTORY(bundle_unittests)
ADD_SUBDIRECTORY(parcel_unittests)
+ADD_SUBDIRECTORY(tizen-database_unittests)
--- /dev/null
+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
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+### 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)
--- /dev/null
+# 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;
+ }
+}
+```
--- /dev/null
+/*
+ * 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_
--- /dev/null
+# 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