diff options
-rwxr-xr-x | Makefile | 2 | ||||
-rw-r--r-- | common.mk | 14 | ||||
-rw-r--r-- | compiledsql.cpp | 32 | ||||
-rw-r--r-- | compiledsql.h | 34 | ||||
-rw-r--r-- | storage.cpp | 130 | ||||
-rw-r--r-- | storage.h | 27 | ||||
-rw-r--r-- | tests/Makefile | 45 |
7 files changed, 192 insertions, 92 deletions
@@ -10,7 +10,7 @@ DISTROS=base debian11 ubuntu2204 VERSION=$(shell dpkg-parsechangelog --show-field Version) INCLUDES=-I. -HEADERS=file.h config.h qrcode.h storage.h whiteboard.h +HEADERS=file.h config.h qrcode.h storage.h whiteboard.h compiledsql.h SOURCES=$(HEADERS:.h=.cpp) OBJECTS=$(HEADERS:.h=.o) TARGETS=whiteboard.fcgi @@ -34,17 +34,11 @@ CXXFLAGS+=-std=c++20 endif ifeq ($(CXX),clang++-10) -LIBS+= \ --fuse-ld=lld-10 \ --lstdc++ -#-lc++ \ -#-lc++abi -#-lc++fs -#-lstdc++fs +LIBS+=-fuse-ld=lld-10 -lstdc++ +else ifeq ($(CXX),clang++-14) +LIBS+=-fuse-ld=lld-14 -lc++ -lc++abi else -LIBS+= \ --lstdc++ \ --lstdc++fs +LIBS+=-lstdc++ -lstdc++fs endif CXXFLAGS+=$(shell pkg-config --cflags qrcodegencpp Magick++ fmt sqlite3) diff --git a/compiledsql.cpp b/compiledsql.cpp new file mode 100644 index 0000000..a3503af --- /dev/null +++ b/compiledsql.cpp @@ -0,0 +1,32 @@ +#include "compiledsql.h" + +CompiledSQL::CompiledSQL(SQLite::Database& db): + m_stmt{}, + m_db{db}, + m_isSelect{} +{ +} + +void CompiledSQL::init(const std::string& stmt) +{ + if (m_stmt) { + m_stmt->reset(); + } else { + if (stmt.starts_with("SELECT ")) { + m_isSelect = true; + } else { + m_isSelect = false; + } + m_stmt = std::make_shared<SQLite::Statement>(m_db, stmt); + } +} + +bool CompiledSQL::execute() +{ + if (m_isSelect) { + return m_stmt->executeStep(); + } else { + return m_stmt->exec(); + } +} + diff --git a/compiledsql.h b/compiledsql.h new file mode 100644 index 0000000..923be83 --- /dev/null +++ b/compiledsql.h @@ -0,0 +1,34 @@ +// Helper Class for SQLite backed storage + +#pragma once + +#include <memory> + +#include <SQLiteCpp/SQLiteCpp.h> + +class CompiledSQL +{ +public: + CompiledSQL(SQLite::Database& db); + + void init(const std::string& stmt); + + template<typename T> + void bind(int index, T value) + { + m_stmt->bind(index, value); + } + + bool execute(); + + template<typename T> + T getColumn(const int index) + { + return m_stmt->getColumn(index); + } + +private: + std::shared_ptr<SQLite::Statement> m_stmt; + SQLite::Database& m_db; + bool m_isSelect; // In SQLite, SELECT statements will be handled w/ executeStep(), others w/ exec() +}; diff --git a/storage.cpp b/storage.cpp index 392f06c..ffdb2f4 100644 --- a/storage.cpp +++ b/storage.cpp @@ -11,23 +11,23 @@ using namespace std::string_literals; Storage::Storage(const Config& config): m_db(config.getDataPath() + "/whiteboard.db3", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE), - m_maxage(config.getMaxage()) + m_maxage(config.getMaxage()), + m_stmt_create(m_db), + m_stmt_getNumberOfDocuments(m_db), + m_stmt_cleanup(m_db), + m_stmt_exists(m_db), + m_stmt_getDocument(m_db), + m_stmt_getRevision(m_db), + m_stmt_getCursorPos(m_db), + m_stmt_getRow(m_db), + m_stmt_setDocument(m_db), + m_stmt_setDocument_new(m_db), + m_stmt_setRevision(m_db), + m_stmt_setCursorPos(m_db), + m_stmt_setRow(m_db) { - m_stmt_create = std::make_shared<SQLite::Statement>(m_db, "CREATE TABLE IF NOT EXISTS documents (id VARCHAR(16) PRIMARY KEY, value BLOB, rev INTEGER, cursorpos INTEGER, timestamp BIGINT)"); - m_stmt_create->exec(); - - m_stmt_getNumberOfDocuments = std::make_shared<SQLite::Statement>(m_db, "SELECT COUNT(*) FROM documents"); - m_stmt_cleanup = std::make_shared<SQLite::Statement>(m_db, "DELETE FROM documents WHERE timestamp + " + std::to_string(static_cast<int64_t>(m_maxage)) + " < unixepoch()"); - m_stmt_exists = std::make_shared<SQLite::Statement>(m_db, "SELECT id FROM documents WHERE id = ?"); - m_stmt_getDocument = std::make_shared<SQLite::Statement>(m_db, "SELECT value FROM documents WHERE id = ?"); - m_stmt_getRevision = std::make_shared<SQLite::Statement>(m_db, "SELECT rev FROM documents WHERE id = ?"); - m_stmt_getCursorPos = std::make_shared<SQLite::Statement>(m_db, "SELECT cursorpos FROM documents WHERE id = ?"); - m_stmt_getRow = std::make_shared<SQLite::Statement>(m_db, "SELECT value, rev, cursorpos FROM documents WHERE id = ?"); - m_stmt_setDocument = std::make_shared<SQLite::Statement>(m_db, "UPDATE documents SET value = ? WHERE id = ?"); - m_stmt_setDocument_new = std::make_shared<SQLite::Statement>(m_db, "INSERT INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, unixepoch())"); - m_stmt_setRevision = std::make_shared<SQLite::Statement>(m_db, "UPDATE documents SET rev = ? WHERE id = ?"); - m_stmt_setCursorPos = std::make_shared<SQLite::Statement>(m_db, "UPDATE documents SET cursorpos = ? WHERE id = ?"); - m_stmt_setRow = std::make_shared<SQLite::Statement>(m_db, "INSERT OR REPLACE INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, unixepoch())"); + m_stmt_create.init("CREATE TABLE IF NOT EXISTS documents (id VARCHAR(16) PRIMARY KEY, value BLOB, rev INTEGER, cursorpos INTEGER, timestamp BIGINT)"); + m_stmt_create.execute(); } Storage::~Storage() @@ -36,11 +36,11 @@ Storage::~Storage() uint64_t Storage::getNumberOfDocuments() { - m_stmt_getNumberOfDocuments->reset(); - if (!m_stmt_getNumberOfDocuments->executeStep()) + m_stmt_getNumberOfDocuments.init("SELECT COUNT(*) FROM documents"); + if (!m_stmt_getNumberOfDocuments.execute()) throw std::runtime_error("Count not possible"); - return static_cast<int64_t>(m_stmt_getNumberOfDocuments->getColumn(0)); + return m_stmt_getNumberOfDocuments.getColumn<int64_t>(0); } void Storage::cleanup() @@ -48,106 +48,106 @@ void Storage::cleanup() if (m_maxage == 0) return; - m_stmt_cleanup->reset(); - m_stmt_cleanup->exec(); + m_stmt_cleanup.init("DELETE FROM documents WHERE timestamp + "s + std::to_string(static_cast<int64_t>(m_maxage)) + " < unixepoch()"s); + m_stmt_cleanup.execute(); } bool Storage::exists(const std::string& id) { - m_stmt_exists->reset(); - m_stmt_exists->bind(1, id); + m_stmt_exists.init("SELECT id FROM documents WHERE id = ?"); + m_stmt_exists.bind(1, id); - return m_stmt_exists->executeStep(); + return m_stmt_exists.execute(); } std::string Storage::getDocument(const std::string& id) { - m_stmt_getDocument->reset(); - m_stmt_getDocument->bind(1, id); + m_stmt_getDocument.init("SELECT value FROM documents WHERE id = ?"); + m_stmt_getDocument.bind(1, id); - if (!m_stmt_getDocument->executeStep()) + if (!m_stmt_getDocument.execute()) throw std::runtime_error("id "s + id + " not found"s); - return m_stmt_getDocument->getColumn(0); + return m_stmt_getDocument.getColumn<std::string>(0); } int Storage::getRevision(const std::string& id) { - m_stmt_getRevision->reset(); - m_stmt_getRevision->bind(1, id); + m_stmt_getRevision.init("SELECT rev FROM documents WHERE id = ?"); + m_stmt_getRevision.bind(1, id); - if (!m_stmt_getRevision->executeStep()) + if (!m_stmt_getRevision.execute()) throw std::runtime_error("id "s + id + " not found"s); - return m_stmt_getRevision->getColumn(0); + return m_stmt_getRevision.getColumn<int>(0); } int Storage::getCursorPos(const std::string& id) { - m_stmt_getCursorPos->reset(); - m_stmt_getCursorPos->bind(1, id); + m_stmt_getCursorPos.init("SELECT cursorpos FROM documents WHERE id = ?"); + m_stmt_getCursorPos.bind(1, id); - if (!m_stmt_getCursorPos->executeStep()) + if (!m_stmt_getCursorPos.execute()) throw std::runtime_error("id "s + id + " not found"s); - return m_stmt_getCursorPos->getColumn(0); + return m_stmt_getCursorPos.getColumn<int>(0); } std::tuple<std::string, int, int> Storage::getRow(const std::string& id) { - m_stmt_getRow->reset(); - m_stmt_getRow->bind(1, id); + m_stmt_getRow.init("SELECT value, rev, cursorpos FROM documents WHERE id = ?"); + m_stmt_getRow.bind(1, id); - if (!m_stmt_getRow->executeStep()) + if (!m_stmt_getRow.execute()) throw std::runtime_error("id "s + id + " not found"s); - return {m_stmt_getRow->getColumn(0), m_stmt_getRow->getColumn(1), m_stmt_getRow->getColumn(2)}; + return {m_stmt_getRow.getColumn<std::string>(0), m_stmt_getRow.getColumn<int>(1), m_stmt_getRow.getColumn<int>(2)}; } void Storage::setDocument(const std::string& id, const std::string& document) { - m_stmt_setDocument->reset(); - m_stmt_setDocument->bind(1, document); - m_stmt_setDocument->bind(2, id); - - if (!m_stmt_setDocument->exec()) { - m_stmt_setDocument_new->reset(); - m_stmt_setDocument_new->bind(1, id); - m_stmt_setDocument_new->bind(2, document); - m_stmt_setDocument_new->bind(3, 0); - m_stmt_setDocument_new->bind(4, 0); - m_stmt_setDocument_new->exec(); + m_stmt_setDocument.init("UPDATE documents SET value = ? WHERE id = ?"); + m_stmt_setDocument.bind(1, document); + m_stmt_setDocument.bind(2, id); + + if (!m_stmt_setDocument.execute()) { + m_stmt_setDocument_new.init("INSERT INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, unixepoch())"); + m_stmt_setDocument_new.bind(1, id); + m_stmt_setDocument_new.bind(2, document); + m_stmt_setDocument_new.bind(3, 0); + m_stmt_setDocument_new.bind(4, 0); + m_stmt_setDocument_new.execute(); } } void Storage::setRevision(const std::string& id, int rev) { - m_stmt_setRevision->reset(); - m_stmt_setRevision->bind(1, rev); - m_stmt_setRevision->bind(2, id); + m_stmt_setRevision.init("UPDATE documents SET rev = ? WHERE id = ?"); + m_stmt_setRevision.bind(1, rev); + m_stmt_setRevision.bind(2, id); - if (!m_stmt_setRevision->exec()) + if (!m_stmt_setRevision.execute()) throw std::runtime_error("Unable to insert row with id "s + id); } void Storage::setCursorPos(const std::string& id, int cursorPos) { - m_stmt_setCursorPos->reset(); - m_stmt_setCursorPos->bind(1, cursorPos); - m_stmt_setCursorPos->bind(2, id); + m_stmt_setCursorPos.init("UPDATE documents SET cursorpos = ? WHERE id = ?"); + m_stmt_setCursorPos.bind(1, cursorPos); + m_stmt_setCursorPos.bind(2, id); - if (!m_stmt_setCursorPos->exec()) + if (!m_stmt_setCursorPos.execute()) throw std::runtime_error("Unable to insert row with id "s + id); } void Storage::setRow(const std::string& id, const std::string& document, int rev, int cursorPos) { - m_stmt_setRow->reset(); - m_stmt_setRow->bind(1, id); - m_stmt_setRow->bind(2, document); - m_stmt_setRow->bind(3, rev); - m_stmt_setRow->bind(4, cursorPos); - if (!m_stmt_setRow->exec()) + m_stmt_setRow.init("INSERT OR REPLACE INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, unixepoch())"); + m_stmt_setRow.bind(1, id); + m_stmt_setRow.bind(2, document); + m_stmt_setRow.bind(3, rev); + m_stmt_setRow.bind(4, cursorPos); + if (!m_stmt_setRow.execute()) throw std::runtime_error("Unable to insert row with id "s + id); } @@ -7,6 +7,7 @@ #include <SQLiteCpp/SQLiteCpp.h> #include "config.h" +#include "compiledsql.h" class Storage { @@ -34,18 +35,18 @@ private: uint64_t m_maxage; // shared_ptr to work around initialization in constructor - std::shared_ptr<SQLite::Statement> m_stmt_create; - std::shared_ptr<SQLite::Statement> m_stmt_getNumberOfDocuments; - std::shared_ptr<SQLite::Statement> m_stmt_cleanup; - std::shared_ptr<SQLite::Statement> m_stmt_exists; - std::shared_ptr<SQLite::Statement> m_stmt_getDocument; - std::shared_ptr<SQLite::Statement> m_stmt_getRevision; - std::shared_ptr<SQLite::Statement> m_stmt_getCursorPos; - std::shared_ptr<SQLite::Statement> m_stmt_getRow; - std::shared_ptr<SQLite::Statement> m_stmt_setDocument; - std::shared_ptr<SQLite::Statement> m_stmt_setDocument_new; - std::shared_ptr<SQLite::Statement> m_stmt_setRevision; - std::shared_ptr<SQLite::Statement> m_stmt_setCursorPos; - std::shared_ptr<SQLite::Statement> m_stmt_setRow; + CompiledSQL m_stmt_create; + CompiledSQL m_stmt_getNumberOfDocuments; + CompiledSQL m_stmt_cleanup; + CompiledSQL m_stmt_exists; + CompiledSQL m_stmt_getDocument; + CompiledSQL m_stmt_getRevision; + CompiledSQL m_stmt_getCursorPos; + CompiledSQL m_stmt_getRow; + CompiledSQL m_stmt_setDocument; + CompiledSQL m_stmt_setDocument_new; + CompiledSQL m_stmt_setRevision; + CompiledSQL m_stmt_setCursorPos; + CompiledSQL m_stmt_setRow; }; diff --git a/tests/Makefile b/tests/Makefile index 1f912c3..e36198b 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,5 +1,18 @@ +CXXFLAGS=-g -O0 + +ifeq ($(CXX),clang++-14) +CXXFLAGS+=-fprofile-instr-generate -fcoverage-mapping +else +# GCC +CXXFLAGS+=--coverage +endif + include ../common.mk +LDFLAGS+=-fprofile-instr-generate -fcoverage-mapping + +UNITS=storage.cpp config.cpp file.cpp compiledsql.cpp qrcode.cpp whiteboard.cpp + UNITTESTS=test-config.cpp \ test-storage.cpp @@ -11,14 +24,40 @@ CXXFLAGS+=\ -I.. test: unittests - ./unittests + # https://clang.llvm.org/docs/SourceBasedCodeCoverage.html +ifeq ($(CXX),clang++-14) + LLVM_PROFILE_FILE="unittests.profraw" ./unittests + llvm-profdata-14 merge -sparse unittests.profraw -o unittests.profdata + llvm-cov-14 report --ignore-filename-regex='google' --ignore-filename-regex='test-' --show-region-summary=0 -instr-profile unittests.profdata unittests +endif + +coverage: + llvm-cov-14 show -instr-profile unittests.profdata $(UNITS:.cpp=.o) -unittests: libgmock.a $(UNITTESTS:.cpp=.o) ../config.o ../file.o ../storage.o +unittests: libgmock.a $(UNITTESTS:.cpp=.o) $(UNITS:.cpp=.o) $(CXX) $(LDFLAGS) $^ $(LDLIBS) $(LIBS) -o $@ %.o: %.cpp $(CXX) $(CXXFLAGS) -o $@ -c $< +config.o: ../config.cpp + $(CXX) $(CXXFLAGS) -o $@ -c $< + +file.o: ../file.cpp + $(CXX) $(CXXFLAGS) -o $@ -c $< + +storage.o: ../storage.cpp + $(CXX) $(CXXFLAGS) -o $@ -c $< + +compiledsql.o: ../compiledsql.cpp + $(CXX) $(CXXFLAGS) -o $@ -c $< + +whiteboard.o: ../whiteboard.cpp + $(CXX) $(CXXFLAGS) -o $@ -c $< + +qrcode.o: ../qrcode.cpp + $(CXX) $(CXXFLAGS) -o $@ -c $< + libgmock.a: $(CXX) $(CXXFLAGS) -c /usr/src/googletest/googletest/src/gtest-all.cc $(CXX) $(CXXFLAGS) -c /usr/src/googletest/googlemock/src/gmock-all.cc @@ -26,4 +65,4 @@ libgmock.a: ar -rv libgmock.a gmock-all.o gtest-all.o gmock_main.o clean: - -rm -f *.o *.a unittests + -rm -f *.o *.a unittests *.gcno *.profraw *.profdata |