Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
Schema.hpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of clio: https://github.com/XRPLF/clio
4 Copyright (c) 2023, the clio developers.
5
6 Permission to use, copy, modify, and distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#pragma once
21
22#include "data/cassandra/Concepts.hpp"
23#include "data/cassandra/Handle.hpp"
24#include "data/cassandra/Types.hpp"
25#include "util/log/Logger.hpp"
26
27#include <boost/json/string.hpp>
28#include <fmt/compile.h>
29
30#include <functional>
31#include <string>
32#include <string_view>
33#include <vector>
34
35namespace data::cassandra {
36
45template <SomeSettingsProvider SettingsProviderType>
46[[nodiscard]] std::string inline qualifiedTableName(
47 SettingsProviderType const& provider,
48 std::string_view name
49)
50{
51 return fmt::format(
52 "{}.{}{}", provider.getKeyspace(), provider.getTablePrefix().value_or(""), name
53 );
54}
55
59template <SomeSettingsProvider SettingsProviderType>
60class Schema {
61protected:
62 util::Logger log_{"Backend"};
63 std::reference_wrapper<SettingsProviderType const> settingsProvider_;
64
65public:
66 virtual ~Schema() = default;
67
73 explicit Schema(SettingsProviderType const& settingsProvider)
74 : settingsProvider_{std::cref(settingsProvider)}
75 {
76 }
77
78 std::string createKeyspace = [this]() {
79 return fmt::format(
80 R"(
81 CREATE KEYSPACE IF NOT EXISTS {}
82 WITH replication = {{
83 'class': 'SimpleStrategy',
84 'replication_factor': '{}'
85 }}
86 AND durable_writes = True
87 )",
88 settingsProvider_.get().getKeyspace(),
89 settingsProvider_.get().getReplicationFactor()
90 );
91 }();
92
93 // =======================
94 // Schema creation queries
95 // =======================
96
97 std::vector<Statement> createSchema = [this]() {
98 std::vector<Statement> statements;
99
100 statements.emplace_back(
101 fmt::format(
102 R"(
103 CREATE TABLE IF NOT EXISTS {}
104 (
105 key blob,
106 sequence bigint,
107 object blob,
108 PRIMARY KEY (key, sequence)
109 )
110 WITH CLUSTERING ORDER BY (sequence DESC)
111 )",
112 qualifiedTableName(settingsProvider_.get(), "objects")
113 )
114 );
115
116 statements.emplace_back(
117 fmt::format(
118 R"(
119 CREATE TABLE IF NOT EXISTS {}
120 (
121 hash blob PRIMARY KEY,
122 ledger_sequence bigint,
123 date bigint,
124 transaction blob,
125 metadata blob
126 )
127 )",
128 qualifiedTableName(settingsProvider_.get(), "transactions")
129 )
130 );
131
132 statements.emplace_back(
133 fmt::format(
134 R"(
135 CREATE TABLE IF NOT EXISTS {}
136 (
137 ledger_sequence bigint,
138 hash blob,
139 PRIMARY KEY (ledger_sequence, hash)
140 )
141 )",
142 qualifiedTableName(settingsProvider_.get(), "ledger_transactions")
143 )
144 );
145
146 statements.emplace_back(
147 fmt::format(
148 R"(
149 CREATE TABLE IF NOT EXISTS {}
150 (
151 key blob,
152 seq bigint,
153 next blob,
154 PRIMARY KEY (key, seq)
155 )
156 )",
157 qualifiedTableName(settingsProvider_.get(), "successor")
158 )
159 );
160
161 statements.emplace_back(
162 fmt::format(
163 R"(
164 CREATE TABLE IF NOT EXISTS {}
165 (
166 seq bigint,
167 key blob,
168 PRIMARY KEY (seq, key)
169 )
170 )",
171 qualifiedTableName(settingsProvider_.get(), "diff")
172 )
173 );
174
175 statements.emplace_back(
176 fmt::format(
177 R"(
178 CREATE TABLE IF NOT EXISTS {}
179 (
180 account blob,
181 seq_idx tuple<bigint, bigint>,
182 hash blob,
183 PRIMARY KEY (account, seq_idx)
184 )
185 WITH CLUSTERING ORDER BY (seq_idx DESC)
186 )",
187 qualifiedTableName(settingsProvider_.get(), "account_tx")
188 )
189 );
190
191 statements.emplace_back(
192 fmt::format(
193 R"(
194 CREATE TABLE IF NOT EXISTS {}
195 (
196 sequence bigint PRIMARY KEY,
197 header blob
198 )
199 )",
200 qualifiedTableName(settingsProvider_.get(), "ledgers")
201 )
202 );
203
204 statements.emplace_back(
205 fmt::format(
206 R"(
207 CREATE TABLE IF NOT EXISTS {}
208 (
209 hash blob PRIMARY KEY,
210 sequence bigint
211 )
212 )",
213 qualifiedTableName(settingsProvider_.get(), "ledger_hashes")
214 )
215 );
216
217 statements.emplace_back(
218 fmt::format(
219 R"(
220 CREATE TABLE IF NOT EXISTS {}
221 (
222 is_latest boolean PRIMARY KEY,
223 sequence bigint
224 )
225 )",
226 qualifiedTableName(settingsProvider_.get(), "ledger_range")
227 )
228 );
229
230 statements.emplace_back(
231 fmt::format(
232 R"(
233 CREATE TABLE IF NOT EXISTS {}
234 (
235 token_id blob,
236 sequence bigint,
237 owner blob,
238 is_burned boolean,
239 PRIMARY KEY (token_id, sequence)
240 )
241 WITH CLUSTERING ORDER BY (sequence DESC)
242 )",
243 qualifiedTableName(settingsProvider_.get(), "nf_tokens")
244 )
245 );
246
247 statements.emplace_back(
248 fmt::format(
249 R"(
250 CREATE TABLE IF NOT EXISTS {}
251 (
252 issuer blob,
253 taxon bigint,
254 token_id blob,
255 PRIMARY KEY (issuer, taxon, token_id)
256 )
257 WITH CLUSTERING ORDER BY (taxon ASC, token_id ASC)
258 )",
259 qualifiedTableName(settingsProvider_.get(), "issuer_nf_tokens_v2")
260 )
261 );
262
263 statements.emplace_back(
264 fmt::format(
265 R"(
266 CREATE TABLE IF NOT EXISTS {}
267 (
268 token_id blob,
269 sequence bigint,
270 uri blob,
271 PRIMARY KEY (token_id, sequence)
272 )
273 WITH CLUSTERING ORDER BY (sequence DESC)
274 )",
275 qualifiedTableName(settingsProvider_.get(), "nf_token_uris")
276 )
277 );
278
279 statements.emplace_back(
280 fmt::format(
281 R"(
282 CREATE TABLE IF NOT EXISTS {}
283 (
284 token_id blob,
285 seq_idx tuple<bigint, bigint>,
286 hash blob,
287 PRIMARY KEY (token_id, seq_idx)
288 )
289 WITH CLUSTERING ORDER BY (seq_idx DESC)
290 )",
291 qualifiedTableName(settingsProvider_.get(), "nf_token_transactions")
292 )
293 );
294
295 statements.emplace_back(
296 fmt::format(
297 R"(
298 CREATE TABLE IF NOT EXISTS {}
299 (
300 mpt_id blob,
301 holder blob,
302 PRIMARY KEY (mpt_id, holder)
303 )
304 WITH CLUSTERING ORDER BY (holder ASC)
305 )",
306 qualifiedTableName(settingsProvider_.get(), "mp_token_holders")
307 )
308 );
309
310 statements.emplace_back(
311 fmt::format(
312 R"(
313 CREATE TABLE IF NOT EXISTS {}
314 (
315 migrator_name TEXT,
316 status TEXT,
317 PRIMARY KEY (migrator_name)
318 )
319 )",
320 qualifiedTableName(settingsProvider_.get(), "migrator_status")
321 )
322 );
323
324 statements.emplace_back(
325 fmt::format(
326 R"(
327 CREATE TABLE IF NOT EXISTS {}
328 (
329 node_id UUID,
330 message TEXT,
331 PRIMARY KEY (node_id)
332 )
333 WITH default_time_to_live = 2
334 )",
335 qualifiedTableName(settingsProvider_.get(), "nodes_chat")
336 )
337 );
338
339 return statements;
340 }();
341
346 protected:
347 std::reference_wrapper<SettingsProviderType const> settingsProvider_;
348 std::reference_wrapper<Handle const> handle_;
349
350 public:
357 Statements(SettingsProviderType const& settingsProvider, Handle const& handle)
358 : settingsProvider_{settingsProvider}, handle_{std::cref(handle)}
359 {
360 }
361
362 //
363 // Insert queries
364 //
365
366 PreparedStatement insertObject = [this]() {
367 return handle_.get().prepare(
368 fmt::format(
369 R"(
370 INSERT INTO {}
371 (key, sequence, object)
372 VALUES (?, ?, ?)
373 )",
374 qualifiedTableName(settingsProvider_.get(), "objects")
375 )
376 );
377 }();
378
379 PreparedStatement insertTransaction = [this]() {
380 return handle_.get().prepare(
381 fmt::format(
382 R"(
383 INSERT INTO {}
384 (hash, ledger_sequence, date, transaction, metadata)
385 VALUES (?, ?, ?, ?, ?)
386 )",
387 qualifiedTableName(settingsProvider_.get(), "transactions")
388 )
389 );
390 }();
391
392 PreparedStatement insertLedgerTransaction = [this]() {
393 return handle_.get().prepare(
394 fmt::format(
395 R"(
396 INSERT INTO {}
397 (ledger_sequence, hash)
398 VALUES (?, ?)
399 )",
400 qualifiedTableName(settingsProvider_.get(), "ledger_transactions")
401 )
402 );
403 }();
404
405 PreparedStatement insertSuccessor = [this]() {
406 return handle_.get().prepare(
407 fmt::format(
408 R"(
409 INSERT INTO {}
410 (key, seq, next)
411 VALUES (?, ?, ?)
412 )",
413 qualifiedTableName(settingsProvider_.get(), "successor")
414 )
415 );
416 }();
417
418 PreparedStatement insertDiff = [this]() {
419 return handle_.get().prepare(
420 fmt::format(
421 R"(
422 INSERT INTO {}
423 (seq, key)
424 VALUES (?, ?)
425 )",
426 qualifiedTableName(settingsProvider_.get(), "diff")
427 )
428 );
429 }();
430
431 PreparedStatement insertAccountTx = [this]() {
432 return handle_.get().prepare(
433 fmt::format(
434 R"(
435 INSERT INTO {}
436 (account, seq_idx, hash)
437 VALUES (?, ?, ?)
438 )",
439 qualifiedTableName(settingsProvider_.get(), "account_tx")
440 )
441 );
442 }();
443
444 PreparedStatement insertNFT = [this]() {
445 return handle_.get().prepare(
446 fmt::format(
447 R"(
448 INSERT INTO {}
449 (token_id, sequence, owner, is_burned)
450 VALUES (?, ?, ?, ?)
451 )",
452 qualifiedTableName(settingsProvider_.get(), "nf_tokens")
453 )
454 );
455 }();
456
457 PreparedStatement insertIssuerNFT = [this]() {
458 return handle_.get().prepare(
459 fmt::format(
460 R"(
461 INSERT INTO {}
462 (issuer, taxon, token_id)
463 VALUES (?, ?, ?)
464 )",
465 qualifiedTableName(settingsProvider_.get(), "issuer_nf_tokens_v2")
466 )
467 );
468 }();
469
470 PreparedStatement insertNFTURI = [this]() {
471 return handle_.get().prepare(
472 fmt::format(
473 R"(
474 INSERT INTO {}
475 (token_id, sequence, uri)
476 VALUES (?, ?, ?)
477 )",
478 qualifiedTableName(settingsProvider_.get(), "nf_token_uris")
479 )
480 );
481 }();
482
483 PreparedStatement insertNFTTx = [this]() {
484 return handle_.get().prepare(
485 fmt::format(
486 R"(
487 INSERT INTO {}
488 (token_id, seq_idx, hash)
489 VALUES (?, ?, ?)
490 )",
491 qualifiedTableName(settingsProvider_.get(), "nf_token_transactions")
492 )
493 );
494 }();
495
496 PreparedStatement insertMPTHolder = [this]() {
497 return handle_.get().prepare(
498 fmt::format(
499 R"(
500 INSERT INTO {}
501 (mpt_id, holder)
502 VALUES (?, ?)
503 )",
504 qualifiedTableName(settingsProvider_.get(), "mp_token_holders")
505 )
506 );
507 }();
508
509 PreparedStatement insertLedgerHeader = [this]() {
510 return handle_.get().prepare(
511 fmt::format(
512 R"(
513 INSERT INTO {}
514 (sequence, header)
515 VALUES (?, ?)
516 )",
517 qualifiedTableName(settingsProvider_.get(), "ledgers")
518 )
519 );
520 }();
521
522 PreparedStatement insertLedgerHash = [this]() {
523 return handle_.get().prepare(
524 fmt::format(
525 R"(
526 INSERT INTO {}
527 (hash, sequence)
528 VALUES (?, ?)
529 )",
530 qualifiedTableName(settingsProvider_.get(), "ledger_hashes")
531 )
532 );
533 }();
534
535 //
536 // Update (and "delete") queries
537 //
538
539 PreparedStatement deleteLedgerRange = [this]() {
540 return handle_.get().prepare(
541 fmt::format(
542 R"(
543 UPDATE {}
544 SET sequence = ?
545 WHERE is_latest = False
546 )",
547 qualifiedTableName(settingsProvider_.get(), "ledger_range")
548 )
549 );
550 }();
551
552 PreparedStatement insertMigratorStatus = [this]() {
553 return handle_.get().prepare(
554 fmt::format(
555 R"(
556 INSERT INTO {}
557 (migrator_name, status)
558 VALUES (?, ?)
559 )",
560 qualifiedTableName(settingsProvider_.get(), "migrator_status")
561 )
562 );
563 }();
564
565 PreparedStatement updateClioNodeMessage = [this]() {
566 return handle_.get().prepare(
567 fmt::format(
568 R"(
569 UPDATE {}
570 SET message = ?
571 WHERE node_id = ?
572 )",
573 qualifiedTableName(settingsProvider_.get(), "nodes_chat")
574 )
575 );
576 }();
577
578 //
579 // Select queries
580 //
581
582 PreparedStatement selectSuccessor = [this]() {
583 return handle_.get().prepare(
584 fmt::format(
585 R"(
586 SELECT next
587 FROM {}
588 WHERE key = ?
589 AND seq <= ?
590 ORDER BY seq DESC
591 LIMIT 1
592 )",
593 qualifiedTableName(settingsProvider_.get(), "successor")
594 )
595 );
596 }();
597
598 PreparedStatement selectDiff = [this]() {
599 return handle_.get().prepare(
600 fmt::format(
601 R"(
602 SELECT key
603 FROM {}
604 WHERE seq = ?
605 )",
606 qualifiedTableName(settingsProvider_.get(), "diff")
607 )
608 );
609 }();
610
611 PreparedStatement selectObject = [this]() {
612 return handle_.get().prepare(
613 fmt::format(
614 R"(
615 SELECT object, sequence
616 FROM {}
617 WHERE key = ?
618 AND sequence <= ?
619 ORDER BY sequence DESC
620 LIMIT 1
621 )",
622 qualifiedTableName(settingsProvider_.get(), "objects")
623 )
624 );
625 }();
626
627 PreparedStatement selectTransaction = [this]() {
628 return handle_.get().prepare(
629 fmt::format(
630 R"(
631 SELECT transaction, metadata, ledger_sequence, date
632 FROM {}
633 WHERE hash = ?
634 )",
635 qualifiedTableName(settingsProvider_.get(), "transactions")
636 )
637 );
638 }();
639
640 PreparedStatement selectAllTransactionHashesInLedger = [this]() {
641 return handle_.get().prepare(
642 fmt::format(
643 R"(
644 SELECT hash
645 FROM {}
646 WHERE ledger_sequence = ?
647 )",
648 qualifiedTableName(settingsProvider_.get(), "ledger_transactions")
649 )
650 );
651 }();
652
653 PreparedStatement getToken = [this]() {
654 return handle_.get().prepare(
655 fmt::format(
656 R"(
657 SELECT TOKEN(key)
658 FROM {}
659 WHERE key = ?
660 LIMIT 1
661 )",
662 qualifiedTableName(settingsProvider_.get(), "objects")
663 )
664 );
665 }();
666
667 PreparedStatement selectAccountTx = [this]() {
668 return handle_.get().prepare(
669 fmt::format(
670 R"(
671 SELECT hash, seq_idx
672 FROM {}
673 WHERE account = ?
674 AND seq_idx < ?
675 LIMIT ?
676 )",
677 qualifiedTableName(settingsProvider_.get(), "account_tx")
678 )
679 );
680 }();
681
682 PreparedStatement selectAccountTxForward = [this]() {
683 return handle_.get().prepare(
684 fmt::format(
685 R"(
686 SELECT hash, seq_idx
687 FROM {}
688 WHERE account = ?
689 AND seq_idx > ?
690 ORDER BY seq_idx ASC
691 LIMIT ?
692 )",
693 qualifiedTableName(settingsProvider_.get(), "account_tx")
694 )
695 );
696 }();
697
698 PreparedStatement selectNFT = [this]() {
699 return handle_.get().prepare(
700 fmt::format(
701 R"(
702 SELECT sequence, owner, is_burned
703 FROM {}
704 WHERE token_id = ?
705 AND sequence <= ?
706 ORDER BY sequence DESC
707 LIMIT 1
708 )",
709 qualifiedTableName(settingsProvider_.get(), "nf_tokens")
710 )
711 );
712 }();
713
714 PreparedStatement selectNFTURI = [this]() {
715 return handle_.get().prepare(
716 fmt::format(
717 R"(
718 SELECT uri
719 FROM {}
720 WHERE token_id = ?
721 AND sequence <= ?
722 ORDER BY sequence DESC
723 LIMIT 1
724 )",
725 qualifiedTableName(settingsProvider_.get(), "nf_token_uris")
726 )
727 );
728 }();
729
730 PreparedStatement selectNFTTx = [this]() {
731 return handle_.get().prepare(
732 fmt::format(
733 R"(
734 SELECT hash, seq_idx
735 FROM {}
736 WHERE token_id = ?
737 AND seq_idx < ?
738 ORDER BY seq_idx DESC
739 LIMIT ?
740 )",
741 qualifiedTableName(settingsProvider_.get(), "nf_token_transactions")
742 )
743 );
744 }();
745
746 PreparedStatement selectNFTTxForward = [this]() {
747 return handle_.get().prepare(
748 fmt::format(
749 R"(
750 SELECT hash, seq_idx
751 FROM {}
752 WHERE token_id = ?
753 AND seq_idx >= ?
754 ORDER BY seq_idx ASC
755 LIMIT ?
756 )",
757 qualifiedTableName(settingsProvider_.get(), "nf_token_transactions")
758 )
759 );
760 }();
761
762 PreparedStatement selectNFTIDsByIssuerTaxon = [this]() {
763 return handle_.get().prepare(
764 fmt::format(
765 R"(
766 SELECT token_id
767 FROM {}
768 WHERE issuer = ?
769 AND taxon = ?
770 AND token_id > ?
771 ORDER BY taxon ASC, token_id ASC
772 LIMIT ?
773 )",
774 qualifiedTableName(settingsProvider_.get(), "issuer_nf_tokens_v2")
775 )
776 );
777 }();
778
779 PreparedStatement selectMPTHolders = [this]() {
780 return handle_.get().prepare(
781 fmt::format(
782 R"(
783 SELECT holder
784 FROM {}
785 WHERE mpt_id = ?
786 AND holder > ?
787 ORDER BY holder ASC
788 LIMIT ?
789 )",
790 qualifiedTableName(settingsProvider_.get(), "mp_token_holders")
791 )
792 );
793 }();
794
795 PreparedStatement selectLedgerByHash = [this]() {
796 return handle_.get().prepare(
797 fmt::format(
798 R"(
799 SELECT sequence
800 FROM {}
801 WHERE hash = ?
802 LIMIT 1
803 )",
804 qualifiedTableName(settingsProvider_.get(), "ledger_hashes")
805 )
806 );
807 }();
808
809 PreparedStatement selectLedgerBySeq = [this]() {
810 return handle_.get().prepare(
811 fmt::format(
812 R"(
813 SELECT header
814 FROM {}
815 WHERE sequence = ?
816 )",
817 qualifiedTableName(settingsProvider_.get(), "ledgers")
818 )
819 );
820 }();
821
822 PreparedStatement selectLatestLedger = [this]() {
823 return handle_.get().prepare(
824 fmt::format(
825 R"(
826 SELECT sequence
827 FROM {}
828 WHERE is_latest = True
829 )",
830 qualifiedTableName(settingsProvider_.get(), "ledger_range")
831 )
832 );
833 }();
834
835 PreparedStatement selectLedgerRange = [this]() {
836 return handle_.get().prepare(
837 fmt::format(
838 R"(
839 SELECT sequence
840 FROM {}
841 WHERE is_latest in (True, False)
842 )",
843 qualifiedTableName(settingsProvider_.get(), "ledger_range")
844 )
845 );
846 }();
847
848 PreparedStatement selectMigratorStatus = [this]() {
849 return handle_.get().prepare(
850 fmt::format(
851 R"(
852 SELECT status
853 FROM {}
854 WHERE migrator_name = ?
855 )",
856 qualifiedTableName(settingsProvider_.get(), "migrator_status")
857 )
858 );
859 }();
860
861 PreparedStatement selectClioNodesData = [this]() {
862 return handle_.get().prepare(
863 fmt::format(
864 R"(
865 SELECT node_id, message
866 FROM {}
867 )",
868 qualifiedTableName(settingsProvider_.get(), "nodes_chat")
869 )
870 );
871 }();
872 };
873
879 virtual void
880 prepareStatements(Handle const& handle) = 0;
881};
882
883} // namespace data::cassandra
Represents a handle to the cassandra database cluster.
Definition Handle.hpp:46
Statements(SettingsProviderType const &settingsProvider, Handle const &handle)
Construct a new Statements object.
Definition Schema.hpp:357
Schema(SettingsProviderType const &settingsProvider)
Shared Schema's between all Schema classes (Cassandra and Keyspace).
Definition Schema.hpp:73
virtual void prepareStatements(Handle const &handle)=0
Recreates the prepared statements.
A simple thread-safe logger for the channel specified in the constructor.
Definition Logger.hpp:96
This namespace implements a wrapper for the Cassandra C++ driver.
Definition CassandraBackendFamily.hpp:66
std::string qualifiedTableName(SettingsProviderType const &provider, std::string_view name)
Returns the table name qualified with the keyspace and table prefix.
Definition Schema.hpp:46