From c98d9b58ded749d091c17bd0d40491f933f653ef Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Thu, 13 Feb 2025 17:31:39 +1100 Subject: [PATCH] mysql debugging --- src/ripple/app/rdb/backend/MySQLDatabase.h | 304 +++++++++++------- src/ripple/basics/BasicConfig.h | 17 +- src/ripple/basics/impl/BasicConfig.cpp | 9 +- src/ripple/core/impl/Config.cpp | 2 +- src/ripple/nodestore/backend/MySQLFactory.cpp | 115 ++++--- 5 files changed, 283 insertions(+), 164 deletions(-) diff --git a/src/ripple/app/rdb/backend/MySQLDatabase.h b/src/ripple/app/rdb/backend/MySQLDatabase.h index f37b7a7db..52234e505 100644 --- a/src/ripple/app/rdb/backend/MySQLDatabase.h +++ b/src/ripple/app/rdb/backend/MySQLDatabase.h @@ -13,13 +13,13 @@ namespace ripple { -class MySQLDatabase : public SQLiteDatabase +class MySQLDatabase : public SQLiteDatabase { private: Application& app_; bool const useTxTables_; std::unique_ptr mysql_; - + // Schema creation statements static constexpr auto CREATE_LEDGERS_TABLE = R"SQL( CREATE TABLE IF NOT EXISTS ledgers ( @@ -60,10 +60,22 @@ private: )SQL"; public: - MySQLDatabase( - Application& app, - Config const& config, - JobQueue& jobQueue) + // In the MySQLDatabase constructor, after the mysql_real_connect call: + + // Add this to the private section with other table definitions + static constexpr auto CREATE_NODES_TABLE = R"SQL( + CREATE TABLE IF NOT EXISTS nodes ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + public_key VARCHAR(64) NOT NULL, + ledger_hash VARCHAR(64) NOT NULL, + type VARCHAR(32) NOT NULL, + data MEDIUMBLOB NOT NULL, + UNIQUE INDEX idx_key_hash (public_key, ledger_hash) + ) +)SQL"; + + // Then modify the constructor: + MySQLDatabase(Application& app, Config const& config, JobQueue& jobQueue) : app_(app) , useTxTables_(config.useTxTables()) , mysql_(mysql_init(nullptr), mysql_close) @@ -72,7 +84,8 @@ public: throw std::runtime_error("Failed to initialize MySQL"); if (!config.mysql.has_value()) - throw std::runtime_error("[mysql_settings] stanza missing from config!"); + throw std::runtime_error( + "[mysql_settings] stanza missing from config!"); // Read MySQL connection details from config auto* conn = mysql_real_connect( @@ -80,7 +93,7 @@ public: config.mysql->host.c_str(), config.mysql->user.c_str(), config.mysql->pass.c_str(), - config.mysql->name.c_str(), + nullptr, // Don't select database in connection config.mysql->port, nullptr, 0); @@ -90,6 +103,28 @@ public: std::string("Failed to connect to MySQL: ") + mysql_error(mysql_.get())); + // Create database if it doesn't exist + std::string create_db = + "CREATE DATABASE IF NOT EXISTS " + config.mysql->name; + std::cout << "create_db: `" << create_db << "`\n"; + + if (mysql_query(mysql_.get(), create_db.c_str())) + throw std::runtime_error( + std::string("Failed to create database (2): ") + + mysql_error(mysql_.get())); + + // Select the database + if (mysql_select_db(mysql_.get(), config.mysql->name.c_str())) + throw std::runtime_error( + std::string("Failed to select database: ") + + mysql_error(mysql_.get())); + + // Create nodes table first + if (mysql_query(mysql_.get(), CREATE_NODES_TABLE)) + throw std::runtime_error( + std::string("Failed to create nodes table: ") + + mysql_error(mysql_.get())); + // Create schema if not exists if (mysql_query(mysql_.get(), CREATE_LEDGERS_TABLE)) throw std::runtime_error( @@ -105,7 +140,8 @@ public: if (mysql_query(mysql_.get(), CREATE_ACCOUNT_TRANSACTIONS_TABLE)) throw std::runtime_error( - std::string("Failed to create account_transactions table: ") + + std::string( + "Failed to create account_transactions table: ") + mysql_error(mysql_.get())); } } @@ -130,8 +166,7 @@ public: sql << "INSERT INTO ledgers (" << "ledger_seq, ledger_hash, parent_hash, total_coins, " << "closing_time, prev_closing_time, close_time_resolution, " - << "close_flags, account_hash, tx_hash) VALUES (" - << seq << ", " + << "close_flags, account_hash, tx_hash) VALUES (" << seq << ", " << "'" << strHex(ledger->info().hash) << "', " << "'" << strHex(ledger->info().parentHash) << "', " << ledger->info().drops.drops() << ", " @@ -153,7 +188,8 @@ public: if (mysql_query(mysql_.get(), sql.str().c_str())) { - JLOG(j.fatal()) << "Failed to save ledger: " << mysql_error(mysql_.get()); + JLOG(j.fatal()) + << "Failed to save ledger: " << mysql_error(mysql_.get()); return false; } @@ -162,7 +198,8 @@ public: std::shared_ptr aLedger; try { - aLedger = app_.getAcceptedLedgerCache().fetch(ledger->info().hash); + aLedger = + app_.getAcceptedLedgerCache().fetch(ledger->info().hash); if (!aLedger) { aLedger = std::make_shared(ledger, app_); @@ -179,8 +216,8 @@ public: // Start a transaction for saving all transactions if (mysql_query(mysql_.get(), "START TRANSACTION")) { - JLOG(j.fatal()) << "Failed to start transaction: " - << mysql_error(mysql_.get()); + JLOG(j.fatal()) << "Failed to start transaction: " + << mysql_error(mysql_.get()); return false; } @@ -195,9 +232,9 @@ public: // Save transaction std::stringstream txSql; txSql << "INSERT INTO transactions (" - << "tx_hash, ledger_seq, tx_seq, raw_tx, meta_data) VALUES (" - << "'" << strHex(id) << "', " - << seq << ", " + << "tx_hash, ledger_seq, tx_seq, raw_tx, meta_data) " + "VALUES (" + << "'" << strHex(id) << "', " << seq << ", " << acceptedLedgerTx->getTxnSeq() << ", " << "?, ?) " // Using placeholders for BLOB data << "ON DUPLICATE KEY UPDATE " @@ -209,10 +246,12 @@ public: MYSQL_STMT* stmt = mysql_stmt_init(mysql_.get()); if (!stmt) { - throw std::runtime_error("Failed to initialize statement"); + throw std::runtime_error( + "Failed to initialize statement"); } - if (mysql_stmt_prepare(stmt, txSql.str().c_str(), txSql.str().length())) + if (mysql_stmt_prepare( + stmt, txSql.str().c_str(), txSql.str().length())) { mysql_stmt_close(stmt); throw std::runtime_error("Failed to prepare statement"); @@ -230,7 +269,7 @@ public: Serializer s2; meta.getAsObject().addWithoutSigningFields(s2); - + bind[1].buffer_type = MYSQL_TYPE_BLOB; bind[1].buffer = (void*)s2.data(); bind[1].buffer_length = s2.size(); @@ -254,13 +293,13 @@ public: { std::stringstream accTxSql; accTxSql << "INSERT INTO account_transactions (" - << "account_id, tx_hash, ledger_seq, tx_seq) VALUES (" - << "'" << strHex(account) << "', " - << "'" << strHex(id) << "', " - << seq << ", " - << acceptedLedgerTx->getTxnSeq() << ") " - << "ON DUPLICATE KEY UPDATE " - << "tx_hash = VALUES(tx_hash)"; + << "account_id, tx_hash, ledger_seq, tx_seq) " + "VALUES (" + << "'" << strHex(account) << "', " + << "'" << strHex(id) << "', " << seq << ", " + << acceptedLedgerTx->getTxnSeq() << ") " + << "ON DUPLICATE KEY UPDATE " + << "tx_hash = VALUES(tx_hash)"; if (mysql_query(mysql_.get(), accTxSql.str().c_str())) { @@ -269,7 +308,10 @@ public: } app_.getMasterTransaction().inLedger( - id, seq, acceptedLedgerTx->getTxnSeq(), app_.config().NETWORK_ID); + id, + seq, + acceptedLedgerTx->getTxnSeq(), + app_.config().NETWORK_ID); } if (mysql_query(mysql_.get(), "COMMIT")) @@ -316,7 +358,8 @@ public: if (!useTxTables_) return {}; - if (mysql_query(mysql_.get(), "SELECT MIN(ledger_seq) FROM transactions")) + if (mysql_query( + mysql_.get(), "SELECT MIN(ledger_seq) FROM transactions")) return std::nullopt; MYSQL_RES* result = mysql_store_result(mysql_.get()); @@ -364,7 +407,8 @@ public: return; std::stringstream sql; - sql << "DELETE FROM account_transactions WHERE ledger_seq = " << ledgerSeq; + sql << "DELETE FROM account_transactions WHERE ledger_seq = " + << ledgerSeq; mysql_query(mysql_.get(), sql.str().c_str()); sql.str(""); @@ -378,7 +422,8 @@ public: if (useTxTables_) { std::stringstream sql; - sql << "DELETE FROM account_transactions WHERE ledger_seq < " << ledgerSeq; + sql << "DELETE FROM account_transactions WHERE ledger_seq < " + << ledgerSeq; mysql_query(mysql_.get(), sql.str().c_str()); sql.str(""); @@ -398,7 +443,8 @@ public: return; std::stringstream sql; - sql << "DELETE FROM account_transactions WHERE ledger_seq < " << ledgerSeq; + sql << "DELETE FROM account_transactions WHERE ledger_seq < " + << ledgerSeq; mysql_query(mysql_.get(), sql.str().c_str()); sql.str(""); @@ -413,7 +459,8 @@ public: return; std::stringstream sql; - sql << "DELETE FROM account_transactions WHERE ledger_seq < " << ledgerSeq; + sql << "DELETE FROM account_transactions WHERE ledger_seq < " + << ledgerSeq; mysql_query(mysql_.get(), sql.str().c_str()); } @@ -448,7 +495,8 @@ public: if (!useTxTables_) return 0; - if (mysql_query(mysql_.get(), "SELECT COUNT(*) FROM account_transactions")) + if (mysql_query( + mysql_.get(), "SELECT COUNT(*) FROM account_transactions")) return 0; MYSQL_RES* result = mysql_store_result(mysql_.get()); @@ -470,8 +518,10 @@ public: CountMinMax getLedgerCountMinMax() override { - if (mysql_query(mysql_.get(), - "SELECT COUNT(*), MIN(ledger_seq), MAX(ledger_seq) FROM ledgers")) + if (mysql_query( + mysql_.get(), + "SELECT COUNT(*), MIN(ledger_seq), MAX(ledger_seq) FROM " + "ledgers")) return {0, 0, 0}; MYSQL_RES* result = mysql_store_result(mysql_.get()); @@ -488,8 +538,7 @@ public: CountMinMax ret{ std::stoull(row[0]), static_cast(std::stoll(row[1])), - static_cast(std::stoll(row[2])) - }; + static_cast(std::stoll(row[2]))}; mysql_free_result(result); return ret; } @@ -500,7 +549,7 @@ public: std::stringstream sql; sql << "SELECT ledger_hash, parent_hash, total_coins, closing_time, " << "prev_closing_time, close_time_resolution, close_flags, " - << "account_hash, tx_hash FROM ledgers WHERE ledger_seq = " + << "account_hash, tx_hash FROM ledgers WHERE ledger_seq = " << ledgerSeq; if (mysql_query(mysql_.get(), sql.str().c_str())) @@ -522,8 +571,10 @@ public: info.hash = uint256(row[0]); info.parentHash = uint256(row[1]); info.drops = XRPAmount(std::stoull(row[2])); - info.closeTime = NetClock::time_point{NetClock::duration{std::stoll(row[3])}}; - info.parentCloseTime = NetClock::time_point{NetClock::duration{std::stoll(row[4])}}; + info.closeTime = + NetClock::time_point{NetClock::duration{std::stoll(row[3])}}; + info.parentCloseTime = + NetClock::time_point{NetClock::duration{std::stoll(row[4])}}; info.closeTimeResolution = NetClock::duration{std::stoll(row[5])}; info.closeFlags = std::stoul(row[6]); info.accountHash = uint256(row[7]); @@ -537,7 +588,7 @@ public: getLimitedOldestLedgerInfo(LedgerIndex ledgerFirstIndex) override { std::stringstream sql; - sql << "SELECT ledger_seq FROM ledgers WHERE ledger_seq >= " + sql << "SELECT ledger_seq FROM ledgers WHERE ledger_seq >= " << ledgerFirstIndex << " ORDER BY ledger_seq ASC LIMIT 1"; if (mysql_query(mysql_.get(), sql.str().c_str())) @@ -563,7 +614,7 @@ public: getLimitedNewestLedgerInfo(LedgerIndex ledgerFirstIndex) override { std::stringstream sql; - sql << "SELECT ledger_seq FROM ledgers WHERE ledger_seq >= " + sql << "SELECT ledger_seq FROM ledgers WHERE ledger_seq >= " << ledgerFirstIndex << " ORDER BY ledger_seq DESC LIMIT 1"; if (mysql_query(mysql_.get(), sql.str().c_str())) @@ -615,7 +666,7 @@ public: getHashByIndex(LedgerIndex ledgerIndex) override { std::stringstream sql; - sql << "SELECT ledger_hash FROM ledgers WHERE ledger_seq = " + sql << "SELECT ledger_hash FROM ledgers WHERE ledger_seq = " << ledgerIndex; if (mysql_query(mysql_.get(), sql.str().c_str())) @@ -641,7 +692,8 @@ public: getHashesByIndex(LedgerIndex ledgerIndex) override { std::stringstream sql; - sql << "SELECT ledger_hash, parent_hash FROM ledgers WHERE ledger_seq = " + sql << "SELECT ledger_hash, parent_hash FROM ledgers WHERE ledger_seq " + "= " << ledgerIndex; if (mysql_query(mysql_.get(), sql.str().c_str())) @@ -682,10 +734,10 @@ public: MYSQL_ROW row; while ((row = mysql_fetch_row(sqlResult))) { - LedgerIndex const seq = + LedgerIndex const seq = static_cast(std::stoull(row[0])); - result.emplace(seq, - LedgerHashPair{uint256{row[1]}, uint256{row[2]}}); + result.emplace( + seq, LedgerHashPair{uint256{row[1]}, uint256{row[2]}}); } mysql_free_result(sqlResult); @@ -698,8 +750,9 @@ public: if (!useTxTables_) return {}; - if (mysql_query(mysql_.get(), - "SELECT MIN(ledger_seq) FROM account_transactions")) + if (mysql_query( + mysql_.get(), + "SELECT MIN(ledger_seq) FROM account_transactions")) return std::nullopt; MYSQL_RES* result = mysql_store_result(mysql_.get()); @@ -718,12 +771,13 @@ public: return seq; } - std::optional getNewestLedgerInfo() override { - if (mysql_query(mysql_.get(), - "SELECT ledger_seq FROM ledgers ORDER BY ledger_seq DESC LIMIT 1")) + if (mysql_query( + mysql_.get(), + "SELECT ledger_seq FROM ledgers ORDER BY ledger_seq DESC LIMIT " + "1")) return std::nullopt; MYSQL_RES* result = mysql_store_result(mysql_.get()); @@ -757,8 +811,8 @@ public: if (range) { - sql << " AND t.ledger_seq BETWEEN " - << range->first() << " AND " << range->last(); + sql << " AND t.ledger_seq BETWEEN " << range->first() << " AND " + << range->last(); } if (mysql_query(mysql_.get(), sql.str().c_str())) @@ -816,7 +870,9 @@ public: auto txn = std::make_shared(sit); auto meta = std::make_shared( - id, static_cast(std::stoull(row[2])), Blob(row[1], row[1] + lengths[1])); + id, + static_cast(std::stoull(row[2])), + Blob(row[1], row[1] + lengths[1])); mysql_free_result(result); @@ -861,9 +917,8 @@ public: << "FROM account_transactions at " << "JOIN transactions t ON at.tx_hash = t.tx_hash " << "WHERE at.account_id = '" << strHex(options.account) << "' " - << "AND at.ledger_seq BETWEEN " << options.minLedger - << " AND " << options.maxLedger - << " ORDER BY at.ledger_seq, at.tx_seq" + << "AND at.ledger_seq BETWEEN " << options.minLedger << " AND " + << options.maxLedger << " ORDER BY at.ledger_seq, at.tx_seq" << " LIMIT " << (options.limit + 1); if (mysql_query(mysql_.get(), sql.str().c_str())) @@ -877,7 +932,7 @@ public: std::size_t count = 0; MYSQL_ROW row; - while ((row = mysql_fetch_row(result)) && + while ((row = mysql_fetch_row(result)) && (!options.limit || count < options.limit)) { unsigned long* lengths = mysql_fetch_lengths(result); @@ -886,9 +941,9 @@ public: Blob rawTxn(row[0], row[0] + lengths[0]); Blob rawMeta(row[1], row[1] + lengths[1]); - std::uint32_t ledgerSeq = + std::uint32_t ledgerSeq = static_cast(std::stoull(row[2])); - std::uint32_t txSeq = + std::uint32_t txSeq = static_cast(std::stoull(row[3])); if (count == options.limit) @@ -897,7 +952,8 @@ public: break; } - onTransaction(ledgerSeq, "COMMITTED", std::move(rawTxn), std::move(rawMeta)); + onTransaction( + ledgerSeq, "COMMITTED", std::move(rawTxn), std::move(rawMeta)); ++count; } @@ -930,8 +986,8 @@ public: << "FROM account_transactions at " << "JOIN transactions t ON at.tx_hash = t.tx_hash " << "WHERE at.account_id = '" << strHex(options.account) << "' " - << "AND at.ledger_seq BETWEEN " << options.minLedger - << " AND " << options.maxLedger + << "AND at.ledger_seq BETWEEN " << options.minLedger << " AND " + << options.maxLedger << " ORDER BY at.ledger_seq DESC, at.tx_seq DESC" << " LIMIT " << (options.limit + 1); @@ -946,7 +1002,7 @@ public: std::size_t count = 0; MYSQL_ROW row; - while ((row = mysql_fetch_row(result)) && + while ((row = mysql_fetch_row(result)) && (!options.limit || count < options.limit)) { unsigned long* lengths = mysql_fetch_lengths(result); @@ -955,9 +1011,9 @@ public: Blob rawTxn(row[0], row[0] + lengths[0]); Blob rawMeta(row[1], row[1] + lengths[1]); - std::uint32_t ledgerSeq = + std::uint32_t ledgerSeq = static_cast(std::stoull(row[2])); - std::uint32_t txSeq = + std::uint32_t txSeq = static_cast(std::stoull(row[3])); if (count == options.limit) @@ -966,7 +1022,8 @@ public: break; } - onTransaction(ledgerSeq, "COMMITTED", std::move(rawTxn), std::move(rawMeta)); + onTransaction( + ledgerSeq, "COMMITTED", std::move(rawTxn), std::move(rawMeta)); ++count; } @@ -981,7 +1038,7 @@ public: return true; } - std::vector> + std::vector> getTxHistory(LedgerIndex startIndex) override { if (!useTxTables_) @@ -1013,10 +1070,9 @@ public: SerialIter sit(row[0], lengths[0]); auto txn = std::make_shared(sit); std::string reason; - auto tx = std::make_shared( - txn, reason, app_); + auto tx = std::make_shared(txn, reason, app_); - auto const ledgerSeq = + auto const ledgerSeq = static_cast(std::stoull(row[1])); tx->setStatus(COMMITTED); tx->setLedger(ledgerSeq); @@ -1052,8 +1108,8 @@ public: << "FROM account_transactions at " << "JOIN transactions t ON at.tx_hash = t.tx_hash " << "WHERE at.account_id = '" << strHex(options.account) << "' " - << "AND at.ledger_seq BETWEEN " << options.minLedger - << " AND " << options.maxLedger + << "AND at.ledger_seq BETWEEN " << options.minLedger << " AND " + << options.maxLedger << " ORDER BY at.ledger_seq ASC, at.tx_seq ASC "; if (!options.bUnlimited) @@ -1082,10 +1138,9 @@ public: SerialIter sit(row[0], lengths[0]); auto txn = std::make_shared(sit); std::string reason; - auto tx = std::make_shared( - txn, reason, app_); + auto tx = std::make_shared(txn, reason, app_); - auto const ledgerSeq = + auto const ledgerSeq = static_cast(std::stoull(row[2])); auto meta = std::make_shared( @@ -1120,8 +1175,8 @@ public: << "FROM account_transactions at " << "JOIN transactions t ON at.tx_hash = t.tx_hash " << "WHERE at.account_id = '" << strHex(options.account) << "' " - << "AND at.ledger_seq BETWEEN " << options.minLedger - << " AND " << options.maxLedger + << "AND at.ledger_seq BETWEEN " << options.minLedger << " AND " + << options.maxLedger << " ORDER BY at.ledger_seq DESC, at.tx_seq DESC "; if (!options.bUnlimited) @@ -1150,10 +1205,9 @@ public: SerialIter sit(row[0], lengths[0]); auto txn = std::make_shared(sit); std::string reason; - auto tx = std::make_shared( - txn, reason, app_); + auto tx = std::make_shared(txn, reason, app_); - auto const ledgerSeq = + auto const ledgerSeq = static_cast(std::stoull(row[2])); auto meta = std::make_shared( @@ -1188,8 +1242,8 @@ public: << "FROM account_transactions at " << "JOIN transactions t ON at.tx_hash = t.tx_hash " << "WHERE at.account_id = '" << strHex(options.account) << "' " - << "AND at.ledger_seq BETWEEN " << options.minLedger - << " AND " << options.maxLedger + << "AND at.ledger_seq BETWEEN " << options.minLedger << " AND " + << options.maxLedger << " ORDER BY at.ledger_seq ASC, at.tx_seq ASC "; if (!options.bUnlimited) @@ -1213,7 +1267,7 @@ public: if (!lengths) continue; - auto const ledgerSeq = + auto const ledgerSeq = static_cast(std::stoull(row[2])); result.emplace_back( @@ -1238,8 +1292,8 @@ public: << "FROM account_transactions at " << "JOIN transactions t ON at.tx_hash = t.tx_hash " << "WHERE at.account_id = '" << strHex(options.account) << "' " - << "AND at.ledger_seq BETWEEN " << options.minLedger - << " AND " << options.maxLedger + << "AND at.ledger_seq BETWEEN " << options.minLedger << " AND " + << options.maxLedger << " ORDER BY at.ledger_seq DESC, at.tx_seq DESC "; if (!options.bUnlimited) @@ -1263,7 +1317,7 @@ public: if (!lengths) continue; - auto const ledgerSeq = + auto const ledgerSeq = static_cast(std::stoull(row[2])); result.emplace_back( @@ -1288,8 +1342,8 @@ public: << "FROM account_transactions at " << "JOIN transactions t ON at.tx_hash = t.tx_hash " << "WHERE at.account_id = '" << strHex(options.account) << "' " - << "AND at.ledger_seq BETWEEN " << options.minLedger - << " AND " << options.maxLedger; + << "AND at.ledger_seq BETWEEN " << options.minLedger << " AND " + << options.maxLedger; if (options.marker) { @@ -1318,8 +1372,7 @@ public: { marker = AccountTxMarker{ static_cast(std::stoull(row[2])), - static_cast(std::stoull(row[3])) - }; + static_cast(std::stoull(row[3]))}; break; } @@ -1327,7 +1380,7 @@ public: if (!lengths) continue; - auto const ledgerSeq = + auto const ledgerSeq = static_cast(std::stoull(row[2])); result.emplace_back( @@ -1354,8 +1407,8 @@ public: << "FROM account_transactions at " << "JOIN transactions t ON at.tx_hash = t.tx_hash " << "WHERE at.account_id = '" << strHex(options.account) << "' " - << "AND at.ledger_seq BETWEEN " << options.minLedger - << " AND " << options.maxLedger; + << "AND at.ledger_seq BETWEEN " << options.minLedger << " AND " + << options.maxLedger; if (options.marker) { @@ -1384,8 +1437,7 @@ public: { marker = AccountTxMarker{ static_cast(std::stoull(row[2])), - static_cast(std::stoull(row[3])) - }; + static_cast(std::stoull(row[3]))}; break; } @@ -1393,7 +1445,7 @@ public: if (!lengths) continue; - auto const ledgerSeq = + auto const ledgerSeq = static_cast(std::stoull(row[2])); result.emplace_back( @@ -1414,11 +1466,12 @@ public: std::uint32_t total = 0; // Get ledger table size - if (!mysql_query(mysql_.get(), - "SELECT ROUND(SUM(data_length + index_length) / 1024) " - "FROM information_schema.tables " - "WHERE table_schema = DATABASE() " - "AND table_name = 'ledgers'")) + if (!mysql_query( + mysql_.get(), + "SELECT ROUND(SUM(data_length + index_length) / 1024) " + "FROM information_schema.tables " + "WHERE table_schema = DATABASE() " + "AND table_name = 'ledgers'")) { MYSQL_RES* result = mysql_store_result(mysql_.get()); if (result) @@ -1433,19 +1486,21 @@ public: // Get transaction tables size if (useTxTables_) { - if (!mysql_query(mysql_.get(), - "SELECT ROUND(SUM(data_length + index_length) / 1024) " - "FROM information_schema.tables " - "WHERE table_schema = DATABASE() " - "AND (table_name = 'transactions' " - "OR table_name = 'account_transactions')")) + if (!mysql_query( + mysql_.get(), + "SELECT ROUND(SUM(data_length + index_length) / 1024) " + "FROM information_schema.tables " + "WHERE table_schema = DATABASE() " + "AND (table_name = 'transactions' " + "OR table_name = 'account_transactions')")) { MYSQL_RES* result = mysql_store_result(mysql_.get()); if (result) { MYSQL_ROW row = mysql_fetch_row(result); if (row && row[0]) - total += static_cast(std::stoull(row[0])); + total += + static_cast(std::stoull(row[0])); mysql_free_result(result); } } @@ -1459,11 +1514,12 @@ public: { std::uint32_t total = 0; - if (!mysql_query(mysql_.get(), - "SELECT ROUND(SUM(data_length + index_length) / 1024) " - "FROM information_schema.tables " - "WHERE table_schema = DATABASE() " - "AND table_name = 'ledgers'")) + if (!mysql_query( + mysql_.get(), + "SELECT ROUND(SUM(data_length + index_length) / 1024) " + "FROM information_schema.tables " + "WHERE table_schema = DATABASE() " + "AND table_name = 'ledgers'")) { MYSQL_RES* result = mysql_store_result(mysql_.get()); if (result) @@ -1486,12 +1542,13 @@ public: std::uint32_t total = 0; - if (!mysql_query(mysql_.get(), - "SELECT ROUND(SUM(data_length + index_length) / 1024) " - "FROM information_schema.tables " - "WHERE table_schema = DATABASE() " - "AND (table_name = 'transactions' " - "OR table_name = 'account_transactions')")) + if (!mysql_query( + mysql_.get(), + "SELECT ROUND(SUM(data_length + index_length) / 1024) " + "FROM information_schema.tables " + "WHERE table_schema = DATABASE() " + "AND (table_name = 'transactions' " + "OR table_name = 'account_transactions')")) { MYSQL_RES* result = mysql_store_result(mysql_.get()); if (result) @@ -1519,7 +1576,6 @@ public: // No explicit closing needed for MySQL // The connection will be closed when mysql_ is destroyed } - }; // Factory function diff --git a/src/ripple/basics/BasicConfig.h b/src/ripple/basics/BasicConfig.h index db293979f..a0714324c 100644 --- a/src/ripple/basics/BasicConfig.h +++ b/src/ripple/basics/BasicConfig.h @@ -36,6 +36,8 @@ using IniFileSections = std::map>; //------------------------------------------------------------------------------ +class Config; + /** Holds a collection of configuration values. A configuration file contains zero or more sections. */ @@ -48,11 +50,22 @@ private: std::vector values_; bool had_trailing_comments_ = false; + Config const* parent_; + using const_iterator = decltype(lookup_)::const_iterator; public: + // throws if no parent for this section + Config const& + getParent() const + { + if (!parent_) + Throw("No parent_ for config section"); + return *parent_; + } + /** Create an empty section. */ - explicit Section(std::string const& name = ""); + explicit Section(std::string const& name = "", Config* parent = nullptr); /** Returns the name of this section. */ std::string const& @@ -218,6 +231,8 @@ private: std::map map_; public: + virtual ~BasicConfig() = default; + /** Returns `true` if a section with the given name exists. */ bool exists(std::string const& name) const; diff --git a/src/ripple/basics/impl/BasicConfig.cpp b/src/ripple/basics/impl/BasicConfig.cpp index f557d2e6d..504b2d9c3 100644 --- a/src/ripple/basics/impl/BasicConfig.cpp +++ b/src/ripple/basics/impl/BasicConfig.cpp @@ -24,7 +24,10 @@ namespace ripple { -Section::Section(std::string const& name) : name_(name) +class Config; + +Section::Section(std::string const& name, Config* parent) + : name_(name), parent_(parent) { } @@ -175,12 +178,14 @@ BasicConfig::legacy(std::string const& sectionName) const void BasicConfig::build(IniFileSections const& ifs) { + Config* config_this = dynamic_cast(this); for (auto const& entry : ifs) { auto const result = map_.emplace( std::piecewise_construct, std::make_tuple(entry.first), - std::make_tuple(entry.first)); + std::make_tuple( + entry.first, config_this)); // Will be nullptr if cast failed result.first->second.append(entry.second); } } diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index 076439a50..5ecc021f5 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -772,7 +772,7 @@ Config::loadFromString(std::string const& fileContents) my.host = *sec.get("host"); my.user = *sec.get("user"); my.pass = *sec.get("pass"); - my.pass = *sec.get("name"); + my.name = *sec.get("name"); std::string portStr = *sec.get("port"); my.port = beast::lexicalCastThrow(portStr); diff --git a/src/ripple/nodestore/backend/MySQLFactory.cpp b/src/ripple/nodestore/backend/MySQLFactory.cpp index 7f1ee366c..cc8016217 100644 --- a/src/ripple/nodestore/backend/MySQLFactory.cpp +++ b/src/ripple/nodestore/backend/MySQLFactory.cpp @@ -8,9 +8,9 @@ #include #include #include -#include -#include #include +#include +#include #include namespace ripple { @@ -19,11 +19,19 @@ namespace NodeStore { class MySQLBackend : public Backend { private: - std::string const name_; + std::string name_; beast::Journal journal_; bool isOpen_{false}; std::unique_ptr mysql_; + Config const& config_; + + static constexpr auto CREATE_DATABASE = R"SQL( + CREATE DATABASE IF NOT EXISTS `%s` + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci + )SQL"; + static constexpr auto CREATE_NODES_TABLE = R"SQL( CREATE TABLE IF NOT EXISTS nodes ( hash BINARY(32) PRIMARY KEY, @@ -40,24 +48,30 @@ public: : name_(get(keyValues, "path", "nodestore")) , journal_(journal) , mysql_(mysql_init(nullptr), mysql_close) + , config_(keyValues.getParent()) { + // mysql names are limited to alphanumeric + name_.erase( + std::remove_if( + name_.begin(), + name_.end(), + [](char c) { return !std::isalnum(c); }), + name_.end()); + if (!mysql_) Throw("Failed to initialize MySQL"); - std::string const host = get(keyValues, "host", "localhost"); - std::string const user = get(keyValues, "user", "ripple"); - std::string const password = get(keyValues, "pass", ""); - std::string const database = get(keyValues, "db", "rippledb"); - uint16_t const port = - static_cast(std::stoul(get(keyValues, "port", "3306"))); + if (!config_.mysql.has_value()) + throw std::runtime_error( + "[mysql_settings] stanza missing from config!"); auto* conn = mysql_real_connect( mysql_.get(), - host.c_str(), - user.c_str(), - password.c_str(), - database.c_str(), - port, + config_.mysql->host.c_str(), + config_.mysql->user.c_str(), + config_.mysql->pass.c_str(), + nullptr, + config_.mysql->port, nullptr, 0); @@ -72,6 +86,22 @@ public: mysql_options(mysql_.get(), MYSQL_OPT_RECONNECT, &reconnect); } + void + createDatabase() + { + std::string query(1024, '\0'); + int length = + snprintf(&query[0], query.size(), CREATE_DATABASE, name_.c_str()); + query.resize(length); + + if (mysql_query(mysql_.get(), query.c_str())) + { + Throw( + std::string("Failed to create database: ") + + mysql_error(mysql_.get()) + " (1)"); + } + } + ~MySQLBackend() override { close(); @@ -89,6 +119,20 @@ public: if (isOpen_) Throw("already open"); + // Ensure database is selected + if (!config_.mysql.has_value()) + throw std::runtime_error( + "[mysql_settings] stanza missing from config!"); + + createDatabase(); + + if (mysql_select_db(mysql_.get(), name_.c_str())) + { + Throw( + std::string("Failed to select database: ") + + mysql_error(mysql_.get())); + } + if (createIfMissing) { if (mysql_query(mysql_.get(), CREATE_NODES_TABLE)) @@ -126,9 +170,8 @@ public: if (!stmt) return dataCorrupt; - std::string const sql = - "SELECT data FROM nodes WHERE hash = ?"; - + std::string const sql = "SELECT data FROM nodes WHERE hash = ?"; + if (mysql_stmt_prepare(stmt, sql.c_str(), sql.length())) { mysql_stmt_close(stmt); @@ -138,7 +181,8 @@ public: MYSQL_BIND bindParam; std::memset(&bindParam, 0, sizeof(bindParam)); bindParam.buffer_type = MYSQL_TYPE_BLOB; - bindParam.buffer = const_cast(static_cast(hash.data())); + bindParam.buffer = + const_cast(static_cast(hash.data())); bindParam.buffer_length = hash.size(); if (mysql_stmt_bind_param(stmt, &bindParam)) @@ -198,13 +242,13 @@ public: mysql_stmt_close(stmt); nudb::detail::buffer decompressed; - auto const result = + auto const result = nodeobject_decompress(buffer.data(), buffer.size(), decompressed); - + DecodedBlob decoded(hash.data(), result.first, result.second); if (!decoded.wasOk()) return dataCorrupt; - + *pObject = decoded.createObject(); return ok; } @@ -250,14 +294,14 @@ public: EncodedBlob encoded(object); nudb::detail::buffer compressed; - auto const result = - nodeobject_compress(encoded.getData(), encoded.getSize(), compressed); + auto const result = nodeobject_compress( + encoded.getData(), encoded.getSize(), compressed); MYSQL_STMT* stmt = mysql_stmt_init(mysql_.get()); if (!stmt) return; - std::string const sql = + std::string const sql = "INSERT INTO nodes (hash, data) VALUES (?, ?) " "ON DUPLICATE KEY UPDATE data = VALUES(data)"; @@ -272,11 +316,13 @@ public: auto const& hash = object->getHash(); bind[0].buffer_type = MYSQL_TYPE_BLOB; - bind[0].buffer = const_cast(static_cast(hash.data())); + bind[0].buffer = + const_cast(static_cast(hash.data())); bind[0].buffer_length = hash.size(); bind[1].buffer_type = MYSQL_TYPE_BLOB; - bind[1].buffer = const_cast(static_cast(result.first)); + bind[1].buffer = + const_cast(static_cast(result.first)); bind[1].buffer_length = result.second; if (mysql_stmt_bind_param(stmt, bind)) @@ -329,8 +375,9 @@ public: if (!isOpen_) return; - if (mysql_query(mysql_.get(), - "SELECT hash, data FROM nodes ORDER BY created_at")) + if (mysql_query( + mysql_.get(), + "SELECT hash, data FROM nodes ORDER BY created_at")) return; MYSQL_RES* result = mysql_store_result(mysql_.get()); @@ -346,14 +393,10 @@ public: nudb::detail::buffer decompressed; auto const decomp_result = nodeobject_decompress( - row[1], - static_cast(lengths[1]), - decompressed); - + row[1], static_cast(lengths[1]), decompressed); + DecodedBlob decoded( - row[0], - decomp_result.first, - decomp_result.second); + row[0], decomp_result.first, decomp_result.second); if (decoded.wasOk()) f(decoded.createObject()); @@ -417,4 +460,4 @@ static MySQLFactory mysqlFactory; } // namespace NodeStore } // namespace ripple -#endif // RIPPLE_NODESTORE_MYSQLBACKEND_H_INCLUDED +#endif // RIPPLE_NODESTORE_MYSQLBACKEND_H_INCLUDED