commit 22817b0d0706d3910210d2139c62abd9858d6b8d
parent e7eade369c716f8e51744c091ba4c1f4e8d648cb
Author: cfillion <cfillion@users.noreply.github.com>
Date: Sun, 17 Jan 2016 16:14:48 -0500
enhance database support and usage
Diffstat:
A | src/database.cpp | | | 124 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/database.hpp | | | 72 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/registry.cpp | | | 42 | ++++++++++++++++++++++++------------------ |
M | src/registry.hpp | | | 10 | ++++------ |
D | src/sqlite.cpp | | | 118 | ------------------------------------------------------------------------------- |
D | src/sqlite.hpp | | | 72 | ------------------------------------------------------------------------ |
A | test/database.cpp | | | 96 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | test/sqlite.cpp | | | 27 | --------------------------- |
8 files changed, 320 insertions(+), 241 deletions(-)
diff --git a/src/database.cpp b/src/database.cpp
@@ -0,0 +1,124 @@
+/* ReaPack: Package manager for REAPER
+ * Copyright (C) 2015-2016 Christian Fillion
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "database.hpp"
+
+#include "errors.hpp"
+
+#include <sqlite3.h>
+
+using namespace std;
+
+Database::Database(const string &filename)
+{
+ const char *file = ":memory:";
+
+ if(!filename.empty())
+ file = filename.c_str();
+
+ if(sqlite3_open(file, &m_db)) {
+ const auto &error = lastError();
+ sqlite3_close(m_db);
+
+ throw error;
+ }
+}
+
+Database::~Database()
+{
+ for(Statement *stmt : m_statements)
+ delete stmt;
+
+ sqlite3_close(m_db);
+}
+
+Statement *Database::prepare(const char *sql)
+{
+ Statement *stmt = new Statement(sql, this);
+ m_statements.push_back(stmt);
+
+ return stmt;
+}
+
+void Database::exec(const char *sql)
+{
+ if(sqlite3_exec(m_db, sql, nullptr, nullptr, nullptr) != SQLITE_OK)
+ throw lastError();
+}
+
+reapack_error Database::lastError() const
+{
+ return reapack_error(sqlite3_errmsg(m_db));
+}
+
+Statement::Statement(const char *sql, Database *db)
+ : m_db(db)
+{
+ if(sqlite3_prepare_v2(db->m_db, sql, -1, &m_stmt, nullptr) != SQLITE_OK)
+ throw m_db->lastError();
+}
+
+Statement::~Statement()
+{
+ sqlite3_finalize(m_stmt);
+}
+
+void Statement::bind(const int index, const string &text)
+{
+ if(sqlite3_bind_text(m_stmt, index, text.c_str(), -1, SQLITE_STATIC))
+ throw m_db->lastError();
+}
+
+void Statement::bind(const int index, const uint64_t integer)
+{
+ if(sqlite3_bind_int64(m_stmt, index, (sqlite3_int64)integer))
+ throw m_db->lastError();
+}
+
+void Statement::exec()
+{
+ exec([=] { return false; });
+}
+
+void Statement::exec(const ExecCallback &callback)
+{
+ while(true) {
+ switch(sqlite3_step(m_stmt)) {
+ case SQLITE_ROW:
+ if(callback())
+ break;
+ case SQLITE_DONE:
+ sqlite3_clear_bindings(m_stmt);
+ sqlite3_reset(m_stmt);
+ return;
+ default:
+ sqlite3_clear_bindings(m_stmt);
+ sqlite3_reset(m_stmt);
+ throw m_db->lastError();
+ };
+ }
+}
+
+uint64_t Statement::uint64Column(const int index) const
+{
+ return (uint64_t)sqlite3_column_int64(m_stmt, index);
+}
+
+string Statement::stringColumn(const int index) const
+{
+ return (char *)sqlite3_column_text(m_stmt, index);
+}
diff --git a/src/database.hpp b/src/database.hpp
@@ -0,0 +1,72 @@
+/* ReaPack: Package manager for REAPER
+ * Copyright (C) 2015-2016 Christian Fillion
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef REAPACK_DATABASE_HPP
+#define REAPACK_DATABASE_HPP
+
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <vector>
+
+class reapack_error;
+
+struct sqlite3;
+struct sqlite3_stmt;
+
+class Statement;
+
+class Database {
+public:
+ Database(const std::string &filename = std::string());
+ ~Database();
+
+ Statement *prepare(const char *sql);
+ void exec(const char *sql);
+
+private:
+ friend Statement;
+
+ reapack_error lastError() const;
+
+ sqlite3 *m_db;
+ std::vector<Statement *> m_statements;
+};
+
+class Statement {
+public:
+ typedef std::function<bool (void)> ExecCallback;
+
+ void bind(const int index, const std::string &text);
+ void bind(const int index, const uint64_t integer);
+ void exec();
+ void exec(const ExecCallback &);
+
+ uint64_t uint64Column(const int index) const;
+ std::string stringColumn(const int index) const;
+
+private:
+ friend Database;
+
+ Statement(const char *sql, Database *db);
+ ~Statement();
+
+ Database *m_db;
+ sqlite3_stmt *m_stmt;
+};
+
+#endif
diff --git a/src/registry.cpp b/src/registry.cpp
@@ -18,6 +18,7 @@
#include "registry.hpp"
#include "errors.hpp"
+#include "index.hpp"
#include "package.hpp"
#include "path.hpp"
@@ -28,23 +29,26 @@ using namespace std;
Registry::Registry(const Path &path)
: m_db(path.join())
{
- m_db.query(
+ m_db.exec(
"PRAGMA foreign_keys = ON;"
"CREATE TABLE IF NOT EXISTS entries ("
- " package TEXT NOT NULL UNIQUE,"
- " version INTEGER NOT NULL"
+ " remote TEXT NOT NULL,"
+ " category TEXT NOT NULL,"
+ " package TEXT NOT NULL,"
+ " version INTEGER NOT NULL,"
+ " UNIQUE(remote, category, package)"
");"
);
m_insertEntry = m_db.prepare(
"INSERT OR REPLACE INTO entries "
- "VALUES(?, ?);"
+ "VALUES(?, ?, ?, ?);"
);
m_findEntry = m_db.prepare(
- "SELECT rowid, version FROM entries "
- "WHERE package = ? "
+ "SELECT version FROM entries "
+ "WHERE remote = ? AND category = ? AND package = ? "
"LIMIT 1"
);
}
@@ -52,12 +56,13 @@ Registry::Registry(const Path &path)
void Registry::push(Version *ver)
{
Package *pkg = ver->package();
+ Category *cat = pkg->category();
+ RemoteIndex *ri = cat->index();
- if(!pkg)
- return;
-
- m_insertEntry->bind(1, hashPackage(pkg).c_str());
- m_insertEntry->bind(2, ver->code());
+ m_insertEntry->bind(1, ri->name());
+ m_insertEntry->bind(2, cat->name());
+ m_insertEntry->bind(3, pkg->name());
+ m_insertEntry->bind(4, ver->code());
m_insertEntry->exec();
}
@@ -66,9 +71,15 @@ Registry::QueryResult Registry::query(Package *pkg) const
bool exists = false;
uint64_t version = 0;
- m_findEntry->bind(1, hashPackage(pkg).c_str());
+ Category *cat = pkg->category();
+ RemoteIndex *ri = cat->index();
+
+ m_findEntry->bind(1, ri->name());
+ m_findEntry->bind(2, cat->name());
+ m_findEntry->bind(3, pkg->name());
+
m_findEntry->exec([&] {
- version = m_findEntry->uint64Column(1);
+ version = m_findEntry->uint64Column(0);
exists = true;
return false;
});
@@ -100,8 +111,3 @@ bool Registry::addToREAPER(Version *ver, const Path &root)
return id > 0;
}
-
-string Registry::hashPackage(Package *pkg) const
-{
- return (pkg->targetPath() + pkg->name()).join('\30');
-}
diff --git a/src/registry.hpp b/src/registry.hpp
@@ -22,7 +22,7 @@
#include <string>
#include "path.hpp"
-#include "sqlite.hpp"
+#include "database.hpp"
class Package;
class Path;
@@ -50,11 +50,9 @@ public:
QueryResult query(Package *) const;
private:
- std::string hashPackage(Package *) const;
-
- SQLite::Database m_db;
- SQLite::Statement *m_insertEntry;
- SQLite::Statement *m_findEntry;
+ Database m_db;
+ Statement *m_insertEntry;
+ Statement *m_findEntry;
};
#endif
diff --git a/src/sqlite.cpp b/src/sqlite.cpp
@@ -1,118 +0,0 @@
-/* ReaPack: Package manager for REAPER
- * Copyright (C) 2015-2016 Christian Fillion
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "sqlite.hpp"
-
-#include "errors.hpp"
-
-#include <sqlite3.h>
-
-using namespace std;
-using namespace SQLite;
-
-Database::Database(const string &filename)
-{
- const char *file = ":memory:";
-
- if(!filename.empty())
- file = filename.c_str();
-
- if(sqlite3_open(file, &m_db)) {
- const auto &error = lastError();
- sqlite3_close(m_db);
-
- throw error;
- }
-}
-
-Database::~Database()
-{
- for(Statement *stmt : m_statements)
- delete stmt;
-
- sqlite3_close(m_db);
-}
-
-SQLite::Statement *Database::prepare(const char *sql)
-{
- Statement *stmt = new Statement(sql, this);
- m_statements.push_back(stmt);
-
- return stmt;
-}
-
-void Database::query(const char *sql)
-{
- if(sqlite3_exec(m_db, sql, nullptr, nullptr, nullptr) != SQLITE_OK)
- throw lastError();
-}
-
-reapack_error Database::lastError() const
-{
- return reapack_error(sqlite3_errmsg(m_db));
-}
-
-Statement::Statement(const char *sql, Database *db)
- : m_db(db)
-{
- if(sqlite3_prepare_v2(db->m_db, sql, -1, &m_stmt, nullptr) != SQLITE_OK)
- throw m_db->lastError();
-}
-
-Statement::~Statement()
-{
- sqlite3_finalize(m_stmt);
-}
-
-void Statement::bind(const int index, const char *text)
-{
- if(sqlite3_bind_text(m_stmt, index, text, -1, SQLITE_TRANSIENT))
- throw m_db->lastError();
-}
-
-void Statement::bind(const int index, const uint64_t integer)
-{
- if(sqlite3_bind_int64(m_stmt, index, (sqlite3_int64)integer))
- throw m_db->lastError();
-}
-
-void Statement::exec()
-{
- exec([=] { return false; });
-}
-
-void Statement::exec(const ExecCallback &callback)
-{
- while(true) {
- switch(sqlite3_step(m_stmt)) {
- case SQLITE_ROW:
- if(callback())
- break;
- case SQLITE_DONE:
- sqlite3_reset(m_stmt);
- return;
- default:
- sqlite3_reset(m_stmt);
- throw m_db->lastError();
- };
- }
-}
-
-uint64_t Statement::uint64Column(const int index) const
-{
- return (uint64_t)sqlite3_column_int64(m_stmt, index);
-}
diff --git a/src/sqlite.hpp b/src/sqlite.hpp
@@ -1,72 +0,0 @@
-/* ReaPack: Package manager for REAPER
- * Copyright (C) 2015-2016 Christian Fillion
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef REAPACK_SQLITE_HPP
-#define REAPACK_SQLITE_HPP
-
-#include <functional>
-#include <string>
-#include <vector>
-
-class reapack_error;
-
-struct sqlite3;
-struct sqlite3_stmt;
-
-namespace SQLite {
- class Statement;
-
- class Database {
- public:
- Database(const std::string &filename = std::string());
- ~Database();
-
- Statement *prepare(const char *sql);
- void query(const char *sql);
-
- private:
- friend Statement;
-
- reapack_error lastError() const;
-
- sqlite3 *m_db;
- std::vector<Statement *> m_statements;
- };
-
- class Statement {
- public:
- typedef std::function<bool (void)> ExecCallback;
-
- void bind(const int index, const char *text);
- void bind(const int index, const uint64_t integer);
- void exec();
- void exec(const ExecCallback &);
-
- uint64_t uint64Column(const int index) const;
-
- private:
- friend Database;
-
- Statement(const char *sql, Database *db);
- ~Statement();
-
- Database *m_db;
- sqlite3_stmt *m_stmt;
- };
-};
-
-#endif
diff --git a/test/database.cpp b/test/database.cpp
@@ -0,0 +1,96 @@
+#include <catch.hpp>
+
+#include <database.hpp>
+
+#include <errors.hpp>
+
+using namespace std;
+
+static const char *M = "[database]";
+
+TEST_CASE("open bad sqlite file path", M) {
+ try {
+ Database db("/a\\");
+ FAIL();
+ }
+ catch(const reapack_error &e) {
+ REQUIRE(string(e.what()) == "unable to open database file");
+ }
+}
+
+TEST_CASE("execute invalid sql", M) {
+ Database db;
+
+ try {
+ db.exec("WHERE");
+ FAIL();
+ }
+ catch(const reapack_error &e) {
+ REQUIRE(string(e.what()) == "near \"WHERE\": syntax error");
+ }
+}
+
+TEST_CASE("prepare invalid sql", M) {
+ Database db;
+
+ try {
+ db.prepare("WHERE");
+ FAIL();
+ }
+ catch(const reapack_error &e) {
+ REQUIRE(string(e.what()) == "near \"WHERE\": syntax error");
+ }
+}
+
+TEST_CASE("get rows from prepared statement", M) {
+ Database db;
+ db.exec(
+ "CREATE TABLE test (value TEXT NOT NULL);"
+ "INSERT INTO test VALUES (\"hello\");"
+ "INSERT INTO test VALUES (\"世界\");"
+ );
+
+ vector<string> values;
+
+ Statement *stmt = db.prepare("SELECT value FROM test");
+
+ SECTION("continue") {
+ stmt->exec([&] {
+ values.push_back(stmt->stringColumn(0));
+ return true;
+ });
+
+ REQUIRE(values.size() == 2);
+ REQUIRE(values[0] == "hello");
+ REQUIRE(values[1] == "世界");
+ }
+
+ SECTION("abort") {
+ stmt->exec([&] {
+ values.push_back(stmt->stringColumn(0));
+ return false;
+ });
+
+ REQUIRE(values.size() == 1);
+ REQUIRE(values[0] == "hello");
+ }
+}
+
+TEST_CASE("bing values and clear", M) {
+ Database db;
+ db.exec("CREATE TABLE test (value TEXT NOT NULL)");
+
+ vector<string> values;
+
+ Statement *stmt = db.prepare("INSERT INTO test VALUES (?)");
+ stmt->bind(1, "hello");
+ stmt->exec();
+
+ try {
+ stmt->exec();
+ FAIL("bindings not cleared");
+ }
+ catch(const reapack_error &e) {
+ REQUIRE(string(e.what()) == "NOT NULL constraint failed: test.value");
+ }
+}
diff --git a/test/sqlite.cpp b/test/sqlite.cpp
@@ -1,27 +0,0 @@
-#include <catch.hpp>
-
-#include <sqlite.hpp>
-
-#include <errors.hpp>
-
-using namespace std;
-
-static const char *M = "[sqlite]";
-
-TEST_CASE("open bad sqlite file path", M) {
- try {
- SQLite::Database db("/a\\");
- FAIL();
- }
- catch(const reapack_error &e) {}
-}
-
-TEST_CASE("execute invalid sql", M) {
- SQLite::Database db;
-
- try {
- db.query("WHERE");
- FAIL();
- }
- catch(const reapack_error &e) {}
-}