diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 79ae6b1abff..a48d973ae42 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -2408,9 +2408,136 @@ read_only::get_table_rows_result read_only::get_kv_table_rows_context( const rea walk_table_row_range( lb_itr, ub_itr, false ); } + // Set result.next_key + set_kv_next_key(p.encode_type, index_type, result); + return result; } +namespace { + template + UInt float_from_bytes(T& t, const std::string& next_key_bytes) { + std::stringstream ss; + ss << std::hex << next_key_bytes; + UInt val; + ss >> val; + UInt mask = 0; + UInt signbit = (static_cast(1) << (std::numeric_limits::digits - 1)); + if (!(val & signbit)) //flip mask if val is positive + mask = ~mask; + val ^=(mask | signbit); + return val; + } + template + void convert_from_bytes( T& t, const std::string& next_key_bytes) { + if constexpr (std::is_floating_point_v) { + if constexpr (sizeof(T) == 4) { + uint32_t val = float_from_bytes(t, next_key_bytes); + std::memcpy(&t, &val, sizeof(T)); + }else { + static_assert(sizeof(T) == 8, "Unknown floating point type"); + uint64_t val = float_from_bytes(t, next_key_bytes); + std::memcpy(&t, &val, sizeof(T)); + } + }else if constexpr (std::is_integral_v) { + std::stringstream ss; + ss << std::hex << next_key_bytes; + auto unsigned_val = static_cast>(t); + ss >> unsigned_val; + if (unsigned_val > std::numeric_limits::max()) { + t = unsigned_val + static_cast>(std::numeric_limits::min()); + } + else { + t = unsigned_val + std::numeric_limits::min(); + } + }else { + FC_THROW_EXCEPTION(chain::contract_table_query_exception, "Unsupported type to convert from bytes"); + } + } + void convert_to_hex(const int& n, string& str) { + std::stringstream ss1; + ss1<< std::hex << std::uppercase << n; + ss1 >> str; + } + template + void hex_from_bytes(string& next_key, const string& next_key_bytes) { + T val; + convert_from_bytes(val, next_key_bytes); + convert_to_hex(val, next_key); + } + template + void dec_from_bytes(string& next_key, const string& next_key_bytes) { + T val; + convert_from_bytes(val, next_key_bytes); + next_key = std::to_string(val); + } +} + +void read_only::set_kv_next_key(const string& encode_type, const string& index_type, read_only::get_table_rows_result& result) const { + if (result.more == true) { + if (encode_type == "bytes") { + result.next_key = result.next_key_bytes; + }else if (encode_type == "string") { + result.next_key = boost::algorithm::unhex(result.next_key_bytes); + boost::algorithm::trim_right_if( result.next_key, []( char c ){ return c == '\0'; } ); + }else if (encode_type == "name") { + uint64_t ull; + convert_from_bytes(ull, result.next_key_bytes); + name nm(ull); + result.next_key = nm.to_string(); + }else if (encode_type == "hex") { + if (index_type == "uint64") { + hex_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "uint32") { + hex_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "uint16") { + hex_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "uint8") { + hex_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "int64") { + hex_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "int32") { + hex_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "int16") { + hex_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "int8") { + hex_from_bytes(result.next_key, result.next_key_bytes); + }else if( index_type == "sha256" || index_type == "i256" ) { + result.next_key = result.next_key_bytes; + } else if( index_type == "ripemd160" ) { + result.next_key = result.next_key_bytes; + }else { + FC_THROW_EXCEPTION(chain::contract_table_query_exception, "Unsupported index type/encode_type: ${t}/${e} ", ("t", index_type)("e", encode_type)); + } + }else if (encode_type == "dec") { + if (index_type == "float64") { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "float32") { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "uint64" ) { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "uint32") { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "uint16") { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "uint8") { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "int64") { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "int32") { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "int16") { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else if (index_type == "int8") { + dec_from_bytes(result.next_key, result.next_key_bytes); + }else { + FC_THROW_EXCEPTION(chain::contract_table_query_exception, "Unsupported index type/encode_type: ${t}/${e} ", ("t", index_type)("e", encode_type)); + } + } + } +} + + struct table_receiver : chain::backing_store::table_only_error_receiver { table_receiver(read_only::get_table_by_scope_result& result, const read_only::get_table_by_scope_params& params) diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index d39fb7b05d8..076a5639b23 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -375,6 +375,7 @@ class read_only { void make_prefix(eosio::name table_name, eosio::name index_name, uint8_t status, vector &prefix)const; get_table_rows_result get_kv_table_rows( const get_kv_table_rows_params& params )const; get_table_rows_result get_kv_table_rows_context( const read_only::get_kv_table_rows_params& p, eosio::chain::kv_context &kv_context, const abi_def &abi )const; + void set_kv_next_key(const string& encode_type, const string& index_type, read_only::get_table_rows_result& result) const; struct get_table_by_scope_params { name code; // mandatory diff --git a/tests/get_kv_table_nodeos_tests.cpp b/tests/get_kv_table_nodeos_tests.cpp index a5bf375b45d..599e2eb3d54 100644 --- a/tests/get_kv_table_nodeos_tests.cpp +++ b/tests/get_kv_table_nodeos_tests.cpp @@ -230,6 +230,88 @@ BOOST_FIXTURE_TEST_CASE( get_kv_table_nodeos_test, TESTER ) try { chk_result(3, 9); chk_result(4, 10); + // test next_key + p.index_name = "primarykey"_n; + p.index_value = ""; + p.reverse = false; + p.encode_type = "name"; + p.lower_bound = "boba"; + p.upper_bound = ""; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, "bobc"); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "3D0E800000000000"); + chk_result(0, 1); + chk_result(1, 2); + + p.lower_bound = result.next_key; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.next_key, "bobe"); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "3D0EA00000000000"); + chk_result(0, 3); + chk_result(1, 4); + + p.lower_bound = result.next_key; + p.limit = 1; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(1u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, "bobf"); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "3D0EB00000000000"); + chk_result(0, 5); + + p.lower_bound = result.next_key; + p.limit = 20; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(5u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, false); + BOOST_REQUIRE_EQUAL(result.next_key, ""); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, ""); + chk_result(0, 6); + chk_result(1, 7); + chk_result(2, 8); + chk_result(3, 9); + chk_result(4, 10); + + p.reverse = true; + p.upper_bound = "bobj"; + p.lower_bound = ""; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, "bobh"); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "3D0ED00000000000"); + chk_result(0, 10); + chk_result(1, 9); + + p.upper_bound = result.next_key; + p.limit = 7; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(7u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, "boba"); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "3D0E600000000000"); + chk_result(0, 8); + chk_result(1, 7); + chk_result(2, 6); + chk_result(3, 5); + chk_result(4, 4); + chk_result(5, 3); + chk_result(6, 2); + + p.upper_bound = result.next_key; + p.limit = 20; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(1u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, false); + BOOST_REQUIRE_EQUAL(result.next_key, ""); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, ""); + chk_result(0, 1); + ////////////////////////////// // foo ////////////////////////////// @@ -257,6 +339,7 @@ BOOST_FIXTURE_TEST_CASE( get_kv_table_nodeos_test, TESTER ) try { BOOST_REQUIRE_EQUAL(10u, result.rows.size()); + p.index_value = ""; p.encode_type = "dec"; p.lower_bound = "0"; @@ -357,6 +440,97 @@ BOOST_FIXTURE_TEST_CASE( get_kv_table_nodeos_test, TESTER ) try { chk_result(3, 9); chk_result(4, 10); + // test next_key + p.index_name = "foo"_n; + p.reverse = false; + p.index_value = ""; + p.encode_type = "dec"; + + p.lower_bound = "0"; + p.upper_bound = ""; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, std::to_string(3)); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "0000000000000003"); + chk_result(0, 1); + chk_result(1, 2); + + p.lower_bound = result.next_key; + p.limit = 3; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(3u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, std::to_string(6)); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "0000000000000006"); + chk_result(0, 3); + chk_result(1, 4); + chk_result(2, 5); + + p.lower_bound = result.next_key; + p.limit = 4; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(4, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, std::to_string(10)); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "000000000000000A"); + chk_result(0, 6); + chk_result(1, 7); + chk_result(2, 8); + chk_result(3, 9); + + p.lower_bound = result.next_key; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(1, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, false); + BOOST_REQUIRE_EQUAL(result.next_key, ""); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, ""); + chk_result(0, 10); + + p.encode_type = "hex"; + p.lower_bound = "0"; + p.upper_bound = ""; + p.limit = 4; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(4u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, std::to_string(5)); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "0000000000000005"); + chk_result(0, 1); + chk_result(1, 2); + chk_result(2, 3); + chk_result(3, 4); + + p.lower_bound = result.next_key; + p.limit = 5; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(5u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, "A"); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "000000000000000A"); + chk_result(0, 5); + chk_result(1, 6); + chk_result(2, 7); + chk_result(3, 8); + chk_result(4, 9); + + p.lower_bound = result.next_key; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(1u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, false); + BOOST_REQUIRE_EQUAL(result.next_key, ""); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, ""); + chk_result(0, 10); + + p.lower_bound = "10"; + p.limit = 20; //maximize limit for the following test cases + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(0u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, false); + BOOST_REQUIRE_EQUAL(result.next_key, ""); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, ""); + ////////////////////////////// // bar ////////////////////////////// @@ -514,6 +688,46 @@ BOOST_FIXTURE_TEST_CASE( get_kv_table_nodeos_test, TESTER ) try { chk_result(3, 9); chk_result(4, 10); + // test next_key + p.index_name = "bar"_n; + p.encode_type = "string"; + p.reverse = false; + p.index_value = ""; + p.lower_bound = "boba"; + p.upper_bound = ""; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, "bobc"); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "626F62630000"); + chk_result(0, 1); + chk_result(1, 2); + + p.lower_bound = result.next_key; + p.limit = 3; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(3u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, "bobf"); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "626F62660000"); + chk_result(0, 3); + chk_result(1, 4); + chk_result(2, 5); + + p.lower_bound = result.next_key; + p.limit = 20; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(5u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, false); + BOOST_REQUIRE_EQUAL(result.next_key, ""); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, ""); + chk_result(0, 6); + chk_result(1, 7); + chk_result(2, 8); + chk_result(3, 9); + chk_result(4, 10); + ////////////////////////////// // uint32_t : 0, 10, 20,..., 80, 90 ////////////////////////////// @@ -609,6 +823,33 @@ BOOST_FIXTURE_TEST_CASE( get_kv_table_nodeos_test, TESTER ) try { chk_result(0, 3); chk_result(1, 4); + // test next_key + p.reverse = false; + p.index_name = "i"_n; + p.index_value = ""; + p.encode_type = "dec"; + p.lower_bound = "-100"; + p.upper_bound = "100"; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, std::to_string(-20)); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "7FFFFFEC"); + chk_result(0, 1); + chk_result(1, 2); + + p.lower_bound = result.next_key; + p.upper_bound = ""; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, std::to_string(0)); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "80000000"); + chk_result(0, 3); + chk_result(1, 4); + ////////////////////////////// // int64_t : -400, -300,...,400, 500 ////////////////////////////// @@ -669,6 +910,33 @@ BOOST_FIXTURE_TEST_CASE( get_kv_table_nodeos_test, TESTER ) try { chk_result(0, 3); chk_result(1, 4); + // test next_key + p.reverse = false; + p.index_name = "ii"_n; + p.encode_type = "dec"; + p.index_value = ""; + p.lower_bound = "-1000"; + p.upper_bound = "1000"; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, std::to_string(-200)); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "7FFFFFFFFFFFFF38"); + chk_result(0, 1); + chk_result(1, 2); + + p.lower_bound = result.next_key; + p.upper_bound = ""; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key, std::to_string(0)); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "8000000000000000"); + chk_result(0, 3); + chk_result(1, 4); + ////////////////////////////// // double: 0, 1.01, 2.02,..., 9.09 ////////////////////////////// @@ -796,6 +1064,61 @@ BOOST_FIXTURE_TEST_CASE( get_kv_table_nodeos_test, TESTER ) try { chk_result(0, 4); chk_result(1, 5); + // test next_key + p.reverse = false; + p.index_name = "ff"_n; + p.index_value = ""; + p.encode_type = "dec"; + + p.lower_bound = "-0.02"; + p.upper_bound = "-0.01"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(0u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, false); + BOOST_REQUIRE_EQUAL(result.next_key, ""); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, ""); + + p.upper_bound = ""; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key.find("2.02") != string::npos, true); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "C00028F5C28F5C29"); + chk_result(0, 1); + chk_result(1, 2); + + p.lower_bound = result.next_key; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key.find("4.04") != string::npos, true); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "C01028F5C28F5C29"); + chk_result(0, 3); + chk_result(1, 4); + + p.lower_bound = "0.02"; + p.upper_bound = "3.03000001"; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key.find("3.03") != string::npos, true); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "C0083D70A3D70A3D"); + chk_result(0, 2); + chk_result(1, 3); + + p.lower_bound = result.next_key; + p.upper_bound = ""; + p.limit = 2; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + BOOST_REQUIRE_EQUAL(result.next_key.find("5.05") != string::npos, true); + BOOST_REQUIRE_EQUAL(result.next_key_bytes, "C014333333333333"); + chk_result(0, 4); + chk_result(1, 5); + } FC_LOG_AND_RETHROW()