From cbf1ba38794ab6a323441dcc3b0e5e942f7ab386 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sun, 1 Jan 2023 14:53:05 +0100 Subject: Added CompiledSQL class, Test coverage --- Makefile | 2 +- common.mk | 14 ++---- compiledsql.cpp | 32 ++++++++++++++ compiledsql.h | 34 +++++++++++++++ storage.cpp | 130 ++++++++++++++++++++++++++++---------------------------- storage.h | 27 ++++++------ tests/Makefile | 45 ++++++++++++++++++-- 7 files changed, 192 insertions(+), 92 deletions(-) create mode 100644 compiledsql.cpp create mode 100644 compiledsql.h diff --git a/Makefile b/Makefile index 85a5ed4..140b1ba 100755 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/common.mk b/common.mk index 9da602e..ccacd6c 100644 --- a/common.mk +++ b/common.mk @@ -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(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 + +#include + +class CompiledSQL +{ +public: + CompiledSQL(SQLite::Database& db); + + void init(const std::string& stmt); + + template + void bind(int index, T value) + { + m_stmt->bind(index, value); + } + + bool execute(); + + template + T getColumn(const int index) + { + return m_stmt->getColumn(index); + } + +private: + std::shared_ptr 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(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(m_db, "SELECT COUNT(*) FROM documents"); - m_stmt_cleanup = std::make_shared(m_db, "DELETE FROM documents WHERE timestamp + " + std::to_string(static_cast(m_maxage)) + " < unixepoch()"); - m_stmt_exists = std::make_shared(m_db, "SELECT id FROM documents WHERE id = ?"); - m_stmt_getDocument = std::make_shared(m_db, "SELECT value FROM documents WHERE id = ?"); - m_stmt_getRevision = std::make_shared(m_db, "SELECT rev FROM documents WHERE id = ?"); - m_stmt_getCursorPos = std::make_shared(m_db, "SELECT cursorpos FROM documents WHERE id = ?"); - m_stmt_getRow = std::make_shared(m_db, "SELECT value, rev, cursorpos FROM documents WHERE id = ?"); - m_stmt_setDocument = std::make_shared(m_db, "UPDATE documents SET value = ? WHERE id = ?"); - m_stmt_setDocument_new = std::make_shared(m_db, "INSERT INTO documents (id, value, rev, cursorpos, timestamp) values (?, ?, ?, ?, unixepoch())"); - m_stmt_setRevision = std::make_shared(m_db, "UPDATE documents SET rev = ? WHERE id = ?"); - m_stmt_setCursorPos = std::make_shared(m_db, "UPDATE documents SET cursorpos = ? WHERE id = ?"); - m_stmt_setRow = std::make_shared(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(m_stmt_getNumberOfDocuments->getColumn(0)); + return m_stmt_getNumberOfDocuments.getColumn(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(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(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(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(0); } std::tuple 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(0), m_stmt_getRow.getColumn(1), m_stmt_getRow.getColumn(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); } diff --git a/storage.h b/storage.h index 24f2961..09514c3 100644 --- a/storage.h +++ b/storage.h @@ -7,6 +7,7 @@ #include #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 m_stmt_create; - std::shared_ptr m_stmt_getNumberOfDocuments; - std::shared_ptr m_stmt_cleanup; - std::shared_ptr m_stmt_exists; - std::shared_ptr m_stmt_getDocument; - std::shared_ptr m_stmt_getRevision; - std::shared_ptr m_stmt_getCursorPos; - std::shared_ptr m_stmt_getRow; - std::shared_ptr m_stmt_setDocument; - std::shared_ptr m_stmt_setDocument_new; - std::shared_ptr m_stmt_setRevision; - std::shared_ptr m_stmt_setCursorPos; - std::shared_ptr 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 -- cgit v1.2.3