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