xrpld
Loading...
Searching...
No Matches
SociDB.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/config/BasicConfig.h>
3#include <xrpl/config/Constants.h>
4#include <xrpl/core/Job.h>
5#include <xrpl/core/JobQueue.h>
6#include <xrpl/core/ServiceRegistry.h>
7
8#include <soci/blob.h>
9
10#include <cstddef>
11#include <cstdint>
12#include <filesystem>
13#include <mutex>
14#include <stdexcept>
15#include <string>
16#include <utility>
17#include <vector>
18#ifdef __clang__
19#pragma clang diagnostic push
20#pragma clang diagnostic ignored "-Wdeprecated"
21#endif
22
23#include <xrpl/basics/ByteUtilities.h>
24#include <xrpl/basics/contract.h>
25#include <xrpl/rdb/DatabaseCon.h>
26#include <xrpl/rdb/SociDB.h>
27
28#include <soci/sqlite3/soci-sqlite3.h> // IWYU pragma: keep
29
30#include <format>
31#include <memory>
32
33namespace xrpl {
34
35static auto gCheckpointPageCount = 1000;
36
37namespace detail {
38
40getSociSqliteInit(std::string const& name, std::string const& dir, std::string const& ext)
41{
42 if (name.empty())
43 {
45 "Sqlite databases must specify a dir and a name. Name: {} Dir: {}", name, dir));
46 }
47 std::filesystem::path file(dir);
49 file /= name + ext;
50 return file.string();
51}
52
54getSociInit(BasicConfig const& config, std::string const& dbName)
55{
56 auto const& section = config.section(Sections::kSqdb);
57 auto const backendName = get(section, Keys::kBackend, "sqlite");
58
59 if (backendName != "sqlite")
60 Throw<std::runtime_error>(std::format("Unsupported soci backend: {}", backendName));
61
62 auto const path = config.legacy(Sections::kDatabasePath);
63 auto const ext = dbName == "validators" || dbName == "peerfinder" ? ".sqlite" : ".db";
64 return detail::getSociSqliteInit(dbName, path, ext);
65}
66
67} // namespace detail
68
70{
71}
72
73DBConfig::DBConfig(BasicConfig const& config, std::string const& dbName)
74 : DBConfig(detail::getSociInit(config, dbName))
75{
76}
77
83
84void
85DBConfig::open(soci::session& s) const
86{
87 s.open(soci::sqlite3, connectionString());
88}
89
90void
91open(soci::session& s, BasicConfig const& config, std::string const& dbName)
92{
93 DBConfig(config, dbName).open(s);
94}
95
96void
97open(soci::session& s, std::string const& beName, std::string const& connectionString)
98{
99 if (beName == "sqlite")
100 {
101 s.open(soci::sqlite3, connectionString);
102 }
103 else
104 {
105 Throw<std::runtime_error>(std::format("Unsupported soci backend: {}", beName));
106 }
107}
108
109static sqlite_api::sqlite3*
110getConnection(soci::session& s)
111{
112 sqlite_api::sqlite3* result = nullptr; // NOLINT(misc-const-correctness)
113 auto be = s.get_backend();
114 if (auto b = dynamic_cast<soci::sqlite3_session_backend*>(be))
115 result = b->conn_;
116
117 if (result == nullptr)
118 Throw<std::logic_error>("Didn't get a database connection.");
119
120 return result;
121}
122
124getKBUsedAll(soci::session& s)
125{
126 if (getConnection(s) == nullptr)
127 Throw<std::logic_error>("No connection found.");
128 return static_cast<size_t>(sqlite_api::sqlite3_memory_used() / kilobytes(1));
129}
130
132getKBUsedDB(soci::session& s)
133{
134 // This function will have to be customized when other backends are added
135 if (auto conn = getConnection(s))
136 {
137 int cur = 0, hiw = 0;
138 sqlite_api::sqlite3_db_status(conn, SQLITE_DBSTATUS_CACHE_USED, &cur, &hiw, 0);
139 return cur / kilobytes(1);
140 }
142 return 0; // Silence compiler warning.
143}
144
145void
146convert(soci::blob& from, std::vector<std::uint8_t>& to)
147{
148 to.resize(from.get_len());
149 if (to.empty())
150 return;
151 from.read(0, reinterpret_cast<char*>(&to[0]), from.get_len());
152}
153
154void
155convert(soci::blob& from, std::string& to)
156{
158 convert(from, tmp);
159 to.assign(tmp.begin(), tmp.end());
160}
161
162void
163convert(std::vector<std::uint8_t> const& from, soci::blob& to)
164{
165 if (!from.empty())
166 {
167 to.write(0, reinterpret_cast<char const*>(&from[0]), from.size());
168 }
169 else
170 {
171 to.trim(0);
172 }
173}
174
175void
176convert(std::string const& from, soci::blob& to)
177{
178 if (!from.empty())
179 {
180 to.write(0, from.data(), from.size());
181 }
182 else
183 {
184 to.trim(0);
185 }
186}
187
188namespace {
189
199
200class WALCheckpointer : public Checkpointer
201{
202public:
203 WALCheckpointer(
206 JobQueue& q,
207 ServiceRegistry& registry)
208 : id_(id)
209 , session_(std::move(session))
210 , jobQueue_(q)
211 , j_(registry.getJournal("WALCheckpointer"))
212 {
213 if (auto [conn, keepAlive] = getConnection(); conn)
214 {
215 (void)keepAlive;
216 // The checkpointer is identified to the C callback by an integer id
217 // (resolved via checkpointerFromId) rather than a raw `this`, so it
218 // cannot dangle if the checkpointer is destroyed. Passing the id
219 // through sqlite's void* user-data requires an integer-to-pointer
220 // cast.
221 // NOLINTNEXTLINE(performance-no-int-to-ptr)
222 sqlite_api::sqlite3_wal_hook(conn, &sqliteWALHook, reinterpret_cast<void*>(id_));
223 }
224 }
225
226 std::pair<sqlite_api::sqlite3*, std::shared_ptr<soci::session>>
227 getConnection() const
228 {
229 if (auto p = session_.lock())
230 {
231 return {xrpl::getConnection(*p), p};
232 }
233 return {nullptr, std::shared_ptr<soci::session>{}};
234 }
235
236 std::uintptr_t
237 id() const override
238 {
239 return id_;
240 }
241
242 ~WALCheckpointer() override = default;
243
244 void
245 schedule() override
246 {
247 {
248 std::scoped_lock const lock(mutex_);
249 if (running_)
250 return;
251 running_ = true;
252 }
253
254 // If the Job is not added to the JobQueue then we're not running_.
255 if (!jobQueue_.addJob(
256 JtWal,
257 "WAL",
258 // If the owning DatabaseCon is destroyed, no need to checkpoint
259 // or keep the checkpointer alive so use a weak_ptr to this.
260 // There is a separate check in `checkpoint` for a valid
261 // connection in the rare case when the DatabaseCon is destroyed
262 // after locking this weak_ptr
263 [wp = std::weak_ptr<Checkpointer>{shared_from_this()}]() {
264 if (auto self = wp.lock())
265 self->checkpoint();
266 }))
267 {
268 std::scoped_lock const lock(mutex_);
269 running_ = false;
270 }
271 }
272
273 void
274 checkpoint() override
275 {
276 auto [conn, keepAlive] = getConnection();
277 (void)keepAlive;
278 if (conn == nullptr)
279 return;
280
281 int log = 0, ckpt = 0;
282 int const ret = sqlite_api::sqlite3_wal_checkpoint_v2(
283 conn, nullptr, SQLITE_CHECKPOINT_PASSIVE, &log, &ckpt);
284
285 auto fname = sqlite_api::sqlite3_db_filename(conn, "main");
286 if (ret != SQLITE_OK)
287 {
288 auto jm = (ret == SQLITE_LOCKED) ? j_.trace() : j_.warn();
289 JLOG(jm) << "WAL(" << fname << "): error " << ret;
290 }
291 else
292 {
293 JLOG(j_.trace()) << "WAL(" << fname << "): frames=" << log << ", written=" << ckpt;
294 }
295
296 std::scoped_lock const lock(mutex_);
297 running_ = false;
298 }
299
300protected:
301 std::uintptr_t const id_;
302 // session is owned by the DatabaseCon parent that holds the checkpointer.
303 // It is possible (though rare) for the DatabaseCon class to be destroyed
304 // before the checkpointer.
305 std::weak_ptr<soci::session> session_;
306 std::mutex mutex_;
307 JobQueue& jobQueue_;
308
309 bool running_ = false;
310 beast::Journal const j_;
311
312 static int
313 sqliteWALHook(void* cpId, sqlite_api::sqlite3* conn, char const* dbName, int walSize)
314 {
315 if (walSize >= gCheckpointPageCount)
316 {
317 if (auto checkpointer = checkpointerFromId(reinterpret_cast<std::uintptr_t>(cpId)))
318 {
319 checkpointer->schedule();
320 }
321 else
322 {
323 sqlite_api::sqlite3_wal_hook(conn, nullptr, nullptr);
324 }
325 }
326 return SQLITE_OK;
327 }
328};
329
330} // namespace
331
332std::shared_ptr<Checkpointer>
336 JobQueue& queue,
337 ServiceRegistry& registry)
338{
339 return std::make_shared<WALCheckpointer>(id, std::move(session), queue, registry);
340}
341
342} // namespace xrpl
343
344#ifdef __clang__
345#pragma clang diagnostic pop
346#endif
T assign(T... args)
T begin(T... args)
Holds unparsed configuration information.
void legacy(std::string const &section, std::string value)
Set a value that is not a key/value pair.
Section & section(std::string const &name)
Returns the section with the given name.
DBConfig is used when a client wants to delay opening a soci::session after parsing the config parame...
Definition SociDB.h:44
void open(soci::session &s) const
Definition SociDB.cpp:85
std::string connectionString() const
Definition SociDB.cpp:79
DBConfig(std::string dbPath)
Definition SociDB.cpp:69
std::string connectionString_
Definition SociDB.h:45
A pool of threads to perform work.
Definition JobQueue.h:60
Service registry for dependency injection.
T data(T... args)
T empty(T... args)
T end(T... args)
T format(T... args)
T is_directory(T... args)
T lock(T... args)
T log(T... args)
T make_shared(T... args)
STL namespace.
std::string getSociSqliteInit(std::string const &name, std::string const &dir, std::string const &ext)
Definition SociDB.cpp:40
std::string getSociInit(BasicConfig const &config, std::string const &dbName)
Definition SociDB.cpp:54
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
std::uint32_t getKBUsedDB(soci::session &s)
Definition SociDB.cpp:132
static auto gCheckpointPageCount
Definition SociDB.cpp:35
static sqlite_api::sqlite3 * getConnection(soci::session &s)
Definition SociDB.cpp:110
std::uint32_t getKBUsedAll(soci::session &s)
Definition SociDB.cpp:124
void open(soci::session &s, BasicConfig const &config, std::string const &dbName)
Open a soci session.
Definition SociDB.cpp:91
@ JtWal
Definition Job.h:56
constexpr auto kilobytes(T value) noexcept
std::shared_ptr< Checkpointer > makeCheckpointer(std::uintptr_t id, std::weak_ptr< soci::session >, JobQueue &, ServiceRegistry &)
Returns a new checkpointer which makes checkpoints of a soci database every checkpointPageCount pages...
Definition SociDB.cpp:333
void convert(soci::blob &from, std::vector< std::uint8_t > &to)
Definition SociDB.cpp:146
std::shared_ptr< Checkpointer > checkpointerFromId(std::uintptr_t id)
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T resize(T... args)
T size(T... args)
static constexpr auto kBackend
Definition Constants.h:93
static constexpr auto kSqdb
Definition Constants.h:60
static constexpr auto kDatabasePath
Definition Constants.h:13