mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> Co-authored-by: Bart <bthomee@users.noreply.github.com>
360 lines
12 KiB
C++
360 lines
12 KiB
C++
#include <test/jtx/TestSuite.h>
|
|
|
|
#include <xrpl/basics/BasicConfig.h>
|
|
#include <xrpl/basics/contract.h>
|
|
#include <xrpl/beast/unit_test/suite.h>
|
|
#include <xrpl/rdb/SociDB.h>
|
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <boost/filesystem/path.hpp>
|
|
#include <boost/optional/optional.hpp>
|
|
|
|
#include <soci/into.h>
|
|
#include <soci/session.h>
|
|
#include <soci/use.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace xrpl {
|
|
class SociDB_test final : public TestSuite
|
|
{
|
|
private:
|
|
static void
|
|
setupSQLiteConfig(BasicConfig& config, boost::filesystem::path const& dbPath)
|
|
{
|
|
config.overwrite("sqdb", "backend", "sqlite");
|
|
auto value = dbPath.string();
|
|
if (!value.empty())
|
|
config.legacy("database_path", value);
|
|
}
|
|
|
|
static void
|
|
cleanupDatabaseDir(boost::filesystem::path const& dbPath)
|
|
{
|
|
using namespace boost::filesystem;
|
|
if (!exists(dbPath) || !is_directory(dbPath) || !is_empty(dbPath))
|
|
return;
|
|
remove(dbPath);
|
|
}
|
|
|
|
static void
|
|
setupDatabaseDir(boost::filesystem::path const& dbPath)
|
|
{
|
|
using namespace boost::filesystem;
|
|
if (!exists(dbPath))
|
|
{
|
|
create_directory(dbPath);
|
|
return;
|
|
}
|
|
|
|
if (!is_directory(dbPath))
|
|
{
|
|
// someone created a file where we want to put out directory
|
|
Throw<std::runtime_error>("Cannot create directory: " + dbPath.string());
|
|
}
|
|
}
|
|
static boost::filesystem::path
|
|
getDatabasePath()
|
|
{
|
|
return boost::filesystem::current_path() / "socidb_test_databases";
|
|
}
|
|
|
|
public:
|
|
SociDB_test()
|
|
{
|
|
try
|
|
{
|
|
setupDatabaseDir(getDatabasePath());
|
|
}
|
|
catch (std::exception const&) // NOLINT(bugprone-empty-catch)
|
|
{
|
|
}
|
|
}
|
|
~SociDB_test() override
|
|
{
|
|
try
|
|
{
|
|
cleanupDatabaseDir(getDatabasePath());
|
|
}
|
|
catch (std::exception const&) // NOLINT(bugprone-empty-catch)
|
|
{
|
|
}
|
|
}
|
|
void
|
|
testSQLiteFileNames()
|
|
{
|
|
// confirm that files are given the correct extensions
|
|
testcase("sqliteFileNames");
|
|
BasicConfig c;
|
|
setupSQLiteConfig(c, getDatabasePath());
|
|
std::vector<std::pair<std::string, std::string>> const d(
|
|
{{"peerfinder", ".sqlite"},
|
|
{"state", ".db"},
|
|
{"random", ".db"},
|
|
{"validators", ".sqlite"}});
|
|
|
|
for (auto const& i : d)
|
|
{
|
|
DBConfig const sc(c, i.first);
|
|
BEAST_EXPECT(boost::ends_with(sc.connectionString(), i.first + i.second));
|
|
}
|
|
}
|
|
void
|
|
testSQLiteSession()
|
|
{
|
|
testcase("open");
|
|
BasicConfig c;
|
|
setupSQLiteConfig(c, getDatabasePath());
|
|
DBConfig const sc(c, "SociTestDB");
|
|
std::vector<std::string> const stringData({"String1", "String2", "String3"});
|
|
std::vector<int> const intData({1, 2, 3});
|
|
auto checkValues = [this, &stringData, &intData](soci::session& s) {
|
|
// Check values in db
|
|
std::vector<std::string> stringResult(20 * stringData.size());
|
|
std::vector<int> intResult(20 * intData.size());
|
|
s << "SELECT StringData, IntData FROM SociTestTable;", soci::into(stringResult),
|
|
soci::into(intResult);
|
|
BEAST_EXPECT(
|
|
stringResult.size() == stringData.size() && intResult.size() == intData.size());
|
|
for (int i = 0; i < stringResult.size(); ++i)
|
|
{
|
|
auto si = std::distance(
|
|
stringData.begin(), std::ranges::find(stringData, stringResult[i]));
|
|
auto ii = std::distance(intData.begin(), std::ranges::find(intData, intResult[i]));
|
|
BEAST_EXPECT(si == ii && si < stringResult.size());
|
|
}
|
|
};
|
|
|
|
{
|
|
soci::session s;
|
|
sc.open(s);
|
|
s << "CREATE TABLE IF NOT EXISTS SociTestTable ("
|
|
" Key INTEGER PRIMARY KEY,"
|
|
" StringData TEXT,"
|
|
" IntData INTEGER"
|
|
");";
|
|
|
|
s << "INSERT INTO SociTestTable (StringData, IntData) VALUES "
|
|
"(:stringData, :intData);",
|
|
soci::use(stringData), soci::use(intData);
|
|
checkValues(s);
|
|
}
|
|
{
|
|
// Check values in db after session was closed
|
|
soci::session s;
|
|
sc.open(s);
|
|
checkValues(s);
|
|
}
|
|
{
|
|
namespace bfs = boost::filesystem;
|
|
// Remove the database
|
|
bfs::path const dbPath(sc.connectionString());
|
|
if (bfs::is_regular_file(dbPath))
|
|
bfs::remove(dbPath);
|
|
}
|
|
}
|
|
|
|
void
|
|
testSQLiteSelect()
|
|
{
|
|
testcase("select");
|
|
BasicConfig c;
|
|
setupSQLiteConfig(c, getDatabasePath());
|
|
DBConfig const sc(c, "SociTestDB");
|
|
std::vector<std::uint64_t> const ubid(
|
|
{(std::uint64_t)std::numeric_limits<std::int64_t>::max(), 20, 30});
|
|
std::vector<std::int64_t> const bid({-10, -20, -30});
|
|
std::vector<std::uint32_t> const uid({std::numeric_limits<std::uint32_t>::max(), 2, 3});
|
|
std::vector<std::int32_t> const id({-1, -2, -3});
|
|
|
|
{
|
|
soci::session s;
|
|
sc.open(s);
|
|
|
|
s << "DROP TABLE IF EXISTS STT;";
|
|
|
|
s << "CREATE TABLE STT ("
|
|
" I INTEGER,"
|
|
" UI INTEGER UNSIGNED,"
|
|
" BI BIGINT,"
|
|
" UBI BIGINT UNSIGNED"
|
|
");";
|
|
|
|
s << "INSERT INTO STT (I, UI, BI, UBI) VALUES "
|
|
"(:id, :idu, :bid, :bidu);",
|
|
soci::use(id), soci::use(uid), soci::use(bid), soci::use(ubid);
|
|
|
|
try
|
|
{
|
|
std::int32_t ig = 0;
|
|
std::uint32_t uig = 0;
|
|
std::int64_t big = 0;
|
|
std::uint64_t ubig = 0;
|
|
s << "SELECT I, UI, BI, UBI from STT;", soci::into(ig), soci::into(uig),
|
|
soci::into(big), soci::into(ubig);
|
|
BEAST_EXPECT(ig == id[0] && uig == uid[0] && big == bid[0] && ubig == ubid[0]);
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
fail();
|
|
}
|
|
try
|
|
{
|
|
// SOCI requires boost::optional (not std::optional) as
|
|
// parameters.
|
|
boost::optional<std::int32_t> ig;
|
|
// Known bug: https://github.com/SOCI/soci/issues/926
|
|
// boost::optional<std::uint32_t> uig;
|
|
uint32_t uig = 0;
|
|
boost::optional<std::int64_t> big;
|
|
boost::optional<std::uint64_t> ubig;
|
|
s << "SELECT I, UI, BI, UBI from STT;", soci::into(ig), soci::into(uig),
|
|
soci::into(big), soci::into(ubig);
|
|
BEAST_EXPECT(*ig == id[0] && uig == uid[0] && *big == bid[0] && *ubig == ubid[0]);
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
fail();
|
|
}
|
|
// There are too many issues when working with soci::row and
|
|
// boost::tuple. DO NOT USE soci row! I had a set of workarounds to
|
|
// make soci row less error prone, I'm keeping these tests in case I
|
|
// try to add soci::row and boost::tuple back into soci.
|
|
#if 0
|
|
try
|
|
{
|
|
std::int32_t ig = 0;
|
|
std::uint32_t uig = 0;
|
|
std::int64_t big = 0;
|
|
std::uint64_t ubig = 0;
|
|
soci::row r;
|
|
s << "SELECT I, UI, BI, UBI from STT", soci::into (r);
|
|
ig = r.get<std::int32_t>(0);
|
|
uig = r.get<std::uint32_t>(1);
|
|
big = r.get<std::int64_t>(2);
|
|
ubig = r.get<std::uint64_t>(3);
|
|
BEAST_EXPECT(ig == id[0] && uig == uid[0] && big == bid[0] &&
|
|
ubig == ubid[0]);
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
fail ();
|
|
}
|
|
try
|
|
{
|
|
std::int32_t ig = 0;
|
|
std::uint32_t uig = 0;
|
|
std::int64_t big = 0;
|
|
std::uint64_t ubig = 0;
|
|
soci::row r;
|
|
s << "SELECT I, UI, BI, UBI from STT", soci::into (r);
|
|
ig = r.get<std::int32_t>("I");
|
|
uig = r.get<std::uint32_t>("UI");
|
|
big = r.get<std::int64_t>("BI");
|
|
ubig = r.get<std::uint64_t>("UBI");
|
|
BEAST_EXPECT(ig == id[0] && uig == uid[0] && big == bid[0] &&
|
|
ubig == ubid[0]);
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
fail ();
|
|
}
|
|
try
|
|
{
|
|
boost::tuple<std::int32_t,
|
|
std::uint32_t,
|
|
std::int64_t,
|
|
std::uint64_t> d;
|
|
s << "SELECT I, UI, BI, UBI from STT", soci::into (d);
|
|
BEAST_EXPECT(get<0>(d) == id[0] && get<1>(d) == uid[0] &&
|
|
get<2>(d) == bid[0] && get<3>(d) == ubid[0]);
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
fail ();
|
|
}
|
|
#endif
|
|
}
|
|
{
|
|
namespace bfs = boost::filesystem;
|
|
// Remove the database
|
|
bfs::path const dbPath(sc.connectionString());
|
|
if (bfs::is_regular_file(dbPath))
|
|
bfs::remove(dbPath);
|
|
}
|
|
}
|
|
void
|
|
testSQLiteDeleteWithSubselect()
|
|
{
|
|
testcase("deleteWithSubselect");
|
|
BasicConfig c;
|
|
setupSQLiteConfig(c, getDatabasePath());
|
|
DBConfig const sc(c, "SociTestDB");
|
|
{
|
|
soci::session s;
|
|
sc.open(s);
|
|
|
|
std::string_view const dbInit[] = {
|
|
"BEGIN TRANSACTION;",
|
|
"CREATE TABLE Ledgers ( \
|
|
LedgerHash CHARACTER(64) PRIMARY KEY, \
|
|
LedgerSeq BIGINT UNSIGNED \
|
|
);",
|
|
"CREATE INDEX SeqLedger ON Ledgers(LedgerSeq);"};
|
|
for (auto const c : dbInit)
|
|
s << c;
|
|
char lh[65];
|
|
memset(lh, 'a', 64);
|
|
lh[64] = '\0';
|
|
int toIncIndex = 63;
|
|
int const numRows = 16;
|
|
std::vector<std::string> ledgerHashes;
|
|
std::vector<int> ledgerIndexes;
|
|
ledgerHashes.reserve(numRows);
|
|
ledgerIndexes.reserve(numRows);
|
|
for (int i = 0; i < numRows; ++i)
|
|
{
|
|
++lh[toIncIndex];
|
|
if (lh[toIncIndex] == 'z')
|
|
--toIncIndex;
|
|
ledgerHashes.emplace_back(lh);
|
|
ledgerIndexes.emplace_back(i);
|
|
}
|
|
s << "INSERT INTO Ledgers (LedgerHash, LedgerSeq) VALUES "
|
|
"(:lh, :li);",
|
|
soci::use(ledgerHashes), soci::use(ledgerIndexes);
|
|
|
|
std::vector<int> ledgersLS(numRows * 2);
|
|
s << "SELECT LedgerSeq FROM Ledgers;", soci::into(ledgersLS);
|
|
BEAST_EXPECT(ledgersLS.size() == numRows);
|
|
}
|
|
namespace bfs = boost::filesystem;
|
|
// Remove the database
|
|
bfs::path const dbPath(sc.connectionString());
|
|
if (bfs::is_regular_file(dbPath))
|
|
bfs::remove(dbPath);
|
|
}
|
|
void
|
|
run() override
|
|
{
|
|
testSQLiteFileNames();
|
|
testSQLiteSession();
|
|
testSQLiteSelect();
|
|
testSQLiteDeleteWithSubselect();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(SociDB, core, xrpl);
|
|
|
|
} // namespace xrpl
|