Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,40 @@
set(TEST_TARGET_NAME ${PROJECT_NAME}_tests)
set(GTEST_LIBRARIES GTest::gmock_main GTest::gtest_main)
add_executable(${TEST_TARGET_NAME})
target_sources(${TEST_TARGET_NAME} PRIVATE
test_connection.cpp
test_database_schema_updater.cpp
test_sqlite_statement.cpp
)
target_include_directories(${TEST_TARGET_NAME} PRIVATE
"${PROJECT_SOURCE_DIR}/include"
)
target_link_libraries(${TEST_TARGET_NAME} PRIVATE
everest::sqlite
${GTEST_LIBRARIES}
)
add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME})
if (EVEREST_SQLITE_BUILD_TESTING AND NOT DISABLE_EDM)
evc_include(CodeCoverage)
append_coverage_compiler_flags_to_target(everest_sqlite)
setup_target_for_coverage_gcovr_html(
NAME ${PROJECT_NAME}_gcovr_coverage
EXECUTABLE ctest
DEPENDENCIES ${TEST_TARGET_NAME}
EXCLUDE "tests/*"
)
setup_target_for_coverage_gcovr_xml(
NAME ${PROJECT_NAME}_gcovr_coverage_xml
EXECUTABLE ctest
DEPENDENCIES ${TEST_TARGET_NAME}
EXCLUDE "tests/*"
)
endif()

View File

@@ -0,0 +1,50 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#pragma once
#include <everest/database/sqlite/connection.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using namespace std::string_literals;
namespace everest::db::sqlite {
class DatabaseTestingUtils : public ::testing::Test {
protected:
std::unique_ptr<ConnectionInterface> database;
public:
DatabaseTestingUtils() : database(std::make_unique<Connection>("file::memory:?cache=shared")) {
EXPECT_TRUE(this->database->open_connection());
}
void ExpectUserVersion(uint32_t expected_version) {
auto statement = this->database->new_statement("PRAGMA user_version");
EXPECT_EQ(statement->step(), SQLITE_ROW);
EXPECT_EQ(statement->column_int(0), expected_version);
}
void SetUserVersion(uint32_t user_version) {
EXPECT_TRUE(this->database->execute_statement("PRAGMA user_version = "s + std::to_string(user_version)));
}
bool DoesTableExist(std::string_view table) {
const std::string statement = "SELECT name FROM sqlite_master WHERE type='table' AND name=@table_name";
std::unique_ptr<StatementInterface> table_exists_statement = this->database->new_statement(statement);
table_exists_statement->bind_text("@table_name", std::string(table), SQLiteString::Transient);
const int status = table_exists_statement->step();
const int number_of_rows = table_exists_statement->get_number_of_rows();
return status != SQLITE_ERROR && number_of_rows == 1;
}
bool DoesColumnExist(std::string_view table, std::string_view column) {
return this->database->execute_statement("SELECT "s + column.data() + " FROM " + table.data() + " LIMIT 1;");
}
};
} // namespace everest::db::sqlite

View File

@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include <everest/database/sqlite/connection.hpp>
#include <everest/database/sqlite/statement.hpp>
#include <gtest/gtest.h>
#include <sqlite3.h>
#include <filesystem>
#include <string>
namespace fs = std::filesystem;
namespace everest::db::sqlite {
class ConnectionWalTest : public ::testing::Test {
protected:
fs::path db_path;
void SetUp() override {
const auto unique = "everest_sqlite_conn_test_" + std::to_string(reinterpret_cast<std::uintptr_t>(this)) + "_" +
std::to_string(::testing::UnitTest::GetInstance()->random_seed()) + ".db";
db_path = fs::temp_directory_path() / unique;
std::error_code ec;
fs::remove(db_path, ec);
fs::remove(db_path.string() + "-wal", ec);
fs::remove(db_path.string() + "-shm", ec);
}
void TearDown() override {
std::error_code ec;
fs::remove(db_path, ec);
fs::remove(db_path.string() + "-wal", ec);
fs::remove(db_path.string() + "-shm", ec);
}
};
TEST_F(ConnectionWalTest, FileDatabaseUsesWalJournalMode) {
Connection db(db_path);
ASSERT_TRUE(db.open_connection());
{
auto stmt = db.new_statement("PRAGMA journal_mode");
ASSERT_EQ(stmt->step(), SQLITE_ROW);
EXPECT_EQ(stmt->column_text(0), "wal");
}
db.close_connection();
}
TEST_F(ConnectionWalTest, InMemoryDatabaseOpensWithoutWalFailure) {
Connection db(fs::path("file::memory:?cache=shared"));
ASSERT_TRUE(db.open_connection());
db.close_connection();
}
TEST_F(ConnectionWalTest, ExistingRollbackDatabaseMigratesToWal) {
sqlite3* raw = nullptr;
ASSERT_EQ(sqlite3_open_v2(db_path.c_str(), &raw, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr), SQLITE_OK);
ASSERT_EQ(sqlite3_exec(raw, "PRAGMA journal_mode=DELETE; CREATE TABLE t(id INTEGER);", nullptr, nullptr, nullptr),
SQLITE_OK);
sqlite3_close(raw);
Connection db(db_path);
ASSERT_TRUE(db.open_connection());
{
auto stmt = db.new_statement("PRAGMA journal_mode");
ASSERT_EQ(stmt->step(), SQLITE_ROW);
EXPECT_EQ(stmt->column_text(0), "wal");
}
db.close_connection();
}
} // namespace everest::db::sqlite

View File

@@ -0,0 +1,313 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "database_testing_utils.hpp"
#include <everest/database/sqlite/schema_updater.hpp>
#include <fstream>
namespace everest::db::sqlite {
struct MigrationFile {
std::string_view name;
std::string_view content;
};
static constexpr MigrationFile migration_file_up_1_valid{
"1_up-initial.sql",
"PRAGMA foreign_keys = ON; CREATE TABLE TEST_TABLE1(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
static constexpr MigrationFile migration_file_up_1_valid_empty_name{
"1_up.sql",
"PRAGMA foreign_keys = ON; CREATE TABLE TEST_TABLE1(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
static constexpr MigrationFile migration_file_up_1_invalid{
"1_up-initial.sql", "PRAGMA foreign_keys = ON; CREATE TABLE <invalid> TEST_TABLE1(FIELD1 TEXT PRIMARY KEY NOT "
"NULL, FIELD2 INT NOT NULL);"};
static constexpr MigrationFile migration_file_up_2_valid{
"2_up-add_table.sql", "CREATE TABLE TEST_TABLE2(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
static constexpr MigrationFile migration_file_down_2_valid{"2_down-drop_table.sql", "DROP TABLE TEST_TABLE2;"};
static constexpr MigrationFile migration_file_up_3_valid{
"3_up-add_table.sql", "CREATE TABLE TEST_TABLE3(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
static constexpr MigrationFile migration_file_down_3_valid{"3_down-drop_table.sql", "DROP TABLE TEST_TABLE3;"};
static constexpr MigrationFile migration_file_up_4_valid{
"4_up-add_table.sql", "CREATE TABLE TEST_TABLE4(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
static constexpr MigrationFile migration_file_down_4_valid{"4_down-drop_table.sql", "DROP TABLE TEST_TABLE4;"};
static constexpr std::string_view table1{"TEST_TABLE1"};
static constexpr std::string_view table2{"TEST_TABLE2"};
static constexpr std::string_view table3{"TEST_TABLE3"};
static constexpr std::string_view table4{"TEST_TABLE3"};
class DatabaseSchemaUpdaterTest : public DatabaseTestingUtils {
protected:
std::filesystem::path migration_files_path;
public:
DatabaseSchemaUpdaterTest() :
DatabaseTestingUtils(),
migration_files_path(std::filesystem::temp_directory_path() / "database_schema_test" / "core_migrations") {
std::filesystem::create_directories(migration_files_path);
EXPECT_TRUE(this->database->open_connection());
}
~DatabaseSchemaUpdaterTest() {
std::filesystem::remove_all(migration_files_path);
}
void WriteMigrationFile(const MigrationFile& file) {
std::ofstream stream{this->migration_files_path / file.name};
stream << file.content;
}
};
TEST_F(DatabaseSchemaUpdaterTest, FolderDoesNotExist) {
SchemaUpdater updater{this->database.get()};
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path / "invalid", 1));
}
TEST_F(DatabaseSchemaUpdaterTest, TargetVersionInvalid) {
SchemaUpdater updater{this->database.get()};
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 0));
}
TEST_F(DatabaseSchemaUpdaterTest, ApplyInitialMigrationFile) {
this->WriteMigrationFile(migration_file_up_1_valid);
SchemaUpdater updater{this->database.get()};
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(1);
EXPECT_TRUE(this->DoesTableExist(table1));
}
TEST_F(DatabaseSchemaUpdaterTest, ApplyInitialMigrationFileEmptyName) {
this->WriteMigrationFile(migration_file_up_1_valid_empty_name);
SchemaUpdater updater{this->database.get()};
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(1);
EXPECT_TRUE(this->DoesTableExist(table1));
}
TEST_F(DatabaseSchemaUpdaterTest, ApplyInitialMigrationFileAlreadyUpToDate) {
this->WriteMigrationFile(migration_file_up_1_valid);
this->SetUserVersion(1);
SchemaUpdater updater{this->database.get()};
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(1);
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
}
TEST_F(DatabaseSchemaUpdaterTest, ApplyInitialMigrationFileVersionToHigh) {
this->WriteMigrationFile(migration_file_up_1_valid);
this->SetUserVersion(2);
SchemaUpdater updater{this->database.get()};
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(2);
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
}
TEST_F(DatabaseSchemaUpdaterTest, ApplyInvalidInitialMigrationFile) {
this->WriteMigrationFile(migration_file_up_1_invalid);
SchemaUpdater updater{this->database.get()};
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(0);
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
}
TEST_F(DatabaseSchemaUpdaterTest, MissingInitialMigrationFile) {
this->WriteMigrationFile(migration_file_up_2_valid);
SchemaUpdater updater{this->database.get()};
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(0);
EXPECT_FALSE(this->DoesTableExist(table1));
}
TEST_F(DatabaseSchemaUpdaterTest, SequenceNotValidUnevenNrOfFiles) {
this->WriteMigrationFile(migration_file_up_1_valid);
this->WriteMigrationFile(migration_file_up_2_valid);
this->WriteMigrationFile(migration_file_up_3_valid);
this->WriteMigrationFile(migration_file_down_3_valid);
SchemaUpdater updater{this->database.get()};
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 2));
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
this->ExpectUserVersion(0);
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
}
TEST_F(DatabaseSchemaUpdaterTest, SequenceNotValidNotEnoughFiles) {
this->WriteMigrationFile(migration_file_up_1_valid);
this->WriteMigrationFile(migration_file_up_2_valid);
this->WriteMigrationFile(migration_file_down_2_valid);
SchemaUpdater updater{this->database.get()};
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
this->ExpectUserVersion(0);
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
}
TEST_F(DatabaseSchemaUpdaterTest, SequenceNotValidMissingDownFile) {
this->WriteMigrationFile(migration_file_up_1_valid);
this->WriteMigrationFile(migration_file_up_2_valid);
this->WriteMigrationFile(migration_file_up_3_valid);
this->WriteMigrationFile(migration_file_up_4_valid);
this->WriteMigrationFile(migration_file_down_3_valid);
SchemaUpdater updater{this->database.get()};
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 2));
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
this->ExpectUserVersion(0);
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
}
TEST_F(DatabaseSchemaUpdaterTest, SequenceNotValidMissingUpFile) {
this->WriteMigrationFile(migration_file_up_1_valid);
this->WriteMigrationFile(migration_file_up_2_valid);
this->WriteMigrationFile(migration_file_down_2_valid);
this->WriteMigrationFile(migration_file_down_3_valid);
this->WriteMigrationFile(migration_file_down_4_valid);
SchemaUpdater updater{this->database.get()};
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 2));
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
this->ExpectUserVersion(0);
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
}
TEST_F(DatabaseSchemaUpdaterTest, ApplyMultipleMigrationFilesStepByStep) {
this->WriteMigrationFile(migration_file_up_1_valid);
this->WriteMigrationFile(migration_file_up_2_valid);
this->WriteMigrationFile(migration_file_up_3_valid);
this->WriteMigrationFile(migration_file_down_2_valid);
this->WriteMigrationFile(migration_file_down_3_valid);
SchemaUpdater updater{this->database.get()};
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(1);
EXPECT_TRUE(this->DoesTableExist(table1));
EXPECT_FALSE(this->DoesTableExist(table2));
EXPECT_FALSE(this->DoesTableExist(table3));
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 2));
this->ExpectUserVersion(2);
EXPECT_TRUE(this->DoesTableExist(table1));
EXPECT_TRUE(this->DoesTableExist(table2));
EXPECT_FALSE(this->DoesTableExist(table3));
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 3));
this->ExpectUserVersion(3);
EXPECT_TRUE(this->DoesTableExist(table1));
EXPECT_TRUE(this->DoesTableExist(table2));
EXPECT_TRUE(this->DoesTableExist(table3));
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 2));
this->ExpectUserVersion(2);
EXPECT_TRUE(this->DoesTableExist(table1));
EXPECT_TRUE(this->DoesTableExist(table2));
EXPECT_FALSE(this->DoesTableExist(table3));
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(1);
EXPECT_TRUE(this->DoesTableExist(table1));
EXPECT_FALSE(this->DoesTableExist(table2));
EXPECT_FALSE(this->DoesTableExist(table3));
}
TEST_F(DatabaseSchemaUpdaterTest, ApplyMultipleMigrationFilesAtOnce) {
this->WriteMigrationFile(migration_file_up_1_valid);
this->WriteMigrationFile(migration_file_up_2_valid);
this->WriteMigrationFile(migration_file_up_3_valid);
this->WriteMigrationFile(migration_file_down_2_valid);
this->WriteMigrationFile(migration_file_down_3_valid);
SchemaUpdater updater{this->database.get()};
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 3));
this->ExpectUserVersion(3);
EXPECT_TRUE(this->DoesTableExist(table1));
EXPECT_TRUE(this->DoesTableExist(table2));
EXPECT_TRUE(this->DoesTableExist(table3));
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(1);
EXPECT_TRUE(this->DoesTableExist(table1));
EXPECT_FALSE(this->DoesTableExist(table2));
EXPECT_FALSE(this->DoesTableExist(table3));
}
TEST_F(DatabaseSchemaUpdaterTest, ApplyMultipleMigrationFilesAtOnceWithFailure) {
this->WriteMigrationFile(migration_file_up_1_valid);
this->WriteMigrationFile(migration_file_up_2_valid);
this->WriteMigrationFile(migration_file_up_3_valid);
this->WriteMigrationFile(migration_file_down_2_valid);
this->WriteMigrationFile(migration_file_down_3_valid);
SchemaUpdater updater{this->database.get()};
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
this->ExpectUserVersion(1);
EXPECT_TRUE(this->DoesTableExist(table1));
EXPECT_FALSE(this->DoesTableExist(table2));
EXPECT_TRUE(this->database->execute_statement(migration_file_up_2_valid.content.data()));
EXPECT_TRUE(this->DoesTableExist(table2));
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
EXPECT_TRUE(this->DoesTableExist(table1));
EXPECT_TRUE(this->DoesTableExist(table2));
EXPECT_FALSE(this->DoesTableExist(table3));
this->ExpectUserVersion(1);
}
} // namespace everest::db::sqlite

View File

@@ -0,0 +1,199 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include <everest/database/exceptions.hpp>
#include <everest/database/sqlite/connection.hpp>
#include <everest/database/sqlite/statement.hpp>
#include <gtest/gtest.h>
#include <filesystem>
#include <string>
namespace fs = std::filesystem;
namespace everest::db::sqlite {
class SQLiteStatementTest : public ::testing::Test {
protected:
std::unique_ptr<Connection> db;
void SetUp() override {
fs::path db_path = "file::memory:?cache=shared";
db = std::make_unique<Connection>(db_path);
ASSERT_TRUE(db->open_connection());
ASSERT_TRUE(db->execute_statement(
"CREATE TABLE test_table (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, value INTEGER, score REAL);"));
}
void TearDown() override {
db->close_connection();
}
};
TEST_F(SQLiteStatementTest, InsertAndQueryRow) {
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (:name, :value, :score);");
insert_stmt->bind_text(":name", "test_name", SQLiteString::Transient);
insert_stmt->bind_int(":value", 42);
insert_stmt->bind_double(":score", 98.6);
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
auto select_stmt = db->new_statement("SELECT name, value, score FROM test_table WHERE id = 1;");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_EQ(select_stmt->column_text(0), "test_name");
EXPECT_EQ(select_stmt->column_int(1), 42);
EXPECT_DOUBLE_EQ(select_stmt->column_double(2), 98.6);
}
TEST_F(SQLiteStatementTest, NullBindingAndOptionalResult) {
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (?, ?, ?);");
insert_stmt->bind_null(1);
insert_stmt->bind_int(2, 100);
insert_stmt->bind_null(3);
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
auto select_stmt = db->new_statement("SELECT name, score FROM test_table WHERE id = 1;");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_FALSE(select_stmt->column_text_nullable(0).has_value());
EXPECT_FALSE(select_stmt->column_text_nullable(1).has_value());
}
TEST_F(SQLiteStatementTest, InvalidParameterThrows) {
auto stmt = db->new_statement("SELECT * FROM test_table WHERE name = :name;");
EXPECT_THROW(stmt->bind_int(":invalid", 1), std::out_of_range);
}
TEST_F(SQLiteStatementTest, ResetAndReuseStatement) {
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (?, ?, ?);");
insert_stmt->bind_text(1, "row1");
insert_stmt->bind_int(2, 1);
insert_stmt->bind_double(3, 1.1);
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
ASSERT_EQ(insert_stmt->reset(), SQLITE_OK);
insert_stmt->bind_text(1, "row2");
insert_stmt->bind_int(2, 2);
insert_stmt->bind_double(3, 2.2);
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
auto select_stmt = db->new_statement("SELECT COUNT(*) FROM test_table;");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_EQ(select_stmt->column_int(0), 2);
}
TEST_F(SQLiteStatementTest, BindByNameAndIndexConsistency) {
auto stmt_by_index = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (?, ?, ?);");
auto stmt_by_name =
db->new_statement("INSERT INTO test_table (name, value, score) VALUES (:name, :value, :score);");
stmt_by_index->bind_text(1, "index_row");
stmt_by_index->bind_int(2, 123);
stmt_by_index->bind_double(3, 1.23);
ASSERT_EQ(stmt_by_index->step(), SQLITE_DONE);
stmt_by_name->bind_text(":name", "name_row");
stmt_by_name->bind_int(":value", 321);
stmt_by_name->bind_double(":score", 3.21);
ASSERT_EQ(stmt_by_name->step(), SQLITE_DONE);
auto select_stmt = db->new_statement("SELECT COUNT(*) FROM test_table;");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_EQ(select_stmt->column_int(0), 2);
}
TEST_F(SQLiteStatementTest, BindInt64AndReadBack) {
int64_t large_value = 9223372036854775807LL; // max int64
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (?, ?, ?);");
insert_stmt->bind_text(1, "int64_test");
insert_stmt->bind_int64(2, large_value);
insert_stmt->bind_double(3, 0.0);
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
auto select_stmt = db->new_statement("SELECT value FROM test_table WHERE name = 'int64_test';");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_EQ(select_stmt->column_int64(0), large_value);
}
TEST_F(SQLiteStatementTest, BindInt64NamedParameter) {
int64_t big_value = 1234567890123456789LL;
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (:name, :value, :score);");
insert_stmt->bind_text(":name", "int64_named", SQLiteString::Transient);
insert_stmt->bind_int64(":value", big_value);
insert_stmt->bind_double(":score", 42.42);
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
auto select_stmt = db->new_statement("SELECT value FROM test_table WHERE name = 'int64_named';");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_EQ(select_stmt->column_int64(0), big_value);
}
TEST_F(SQLiteStatementTest, StatementDestructorFinalizesStatement) {
{
auto stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES ('temp', 0, 0.0);");
ASSERT_EQ(stmt->step(), SQLITE_DONE);
// stmt goes out of scope here
}
auto select_stmt = db->new_statement("SELECT COUNT(*) FROM test_table WHERE name = 'temp';");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_EQ(select_stmt->column_int(0), 1);
}
TEST_F(SQLiteStatementTest, GetNumberOfColumnsInRow) {
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES ('check_cols', 1, 1.0);");
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
auto select_stmt = db->new_statement("SELECT id, name, value, score FROM test_table WHERE name = 'check_cols';");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_EQ(select_stmt->get_number_of_rows(), 4);
}
TEST_F(SQLiteStatementTest, ColumnTypeChecks) {
auto insert_stmt =
db->new_statement("INSERT INTO test_table (name, value, score) VALUES ('type_test', 999, 9.99);");
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
auto select_stmt = db->new_statement("SELECT name, value, score FROM test_table WHERE name = 'type_test';");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_EQ(select_stmt->column_type(0), SQLITE_TEXT);
EXPECT_EQ(select_stmt->column_type(1), SQLITE_INTEGER);
EXPECT_EQ(select_stmt->column_type(2), SQLITE_FLOAT);
}
TEST_F(SQLiteStatementTest, BindNullByName) {
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (:name, :value, :score);");
insert_stmt->bind_null(":name");
insert_stmt->bind_int(":value", 5);
insert_stmt->bind_null(":score");
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
auto select_stmt = db->new_statement("SELECT name, score FROM test_table WHERE value = 5;");
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
EXPECT_FALSE(select_stmt->column_text_nullable(0).has_value());
EXPECT_FALSE(select_stmt->column_text_nullable(1).has_value());
}
TEST_F(SQLiteStatementTest, ResetPreservesPreparedStatement) {
auto stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES ('x', 1, 1.0);");
ASSERT_EQ(stmt->step(), SQLITE_DONE);
ASSERT_EQ(stmt->reset(), SQLITE_OK);
stmt->bind_text(1, "x"); // optional rebind test
stmt->bind_int(2, 2);
stmt->bind_double(3, 2.0);
ASSERT_EQ(stmt->step(), SQLITE_DONE);
}
} // namespace everest::db::sqlite