diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 673f1443985..d8855294a8f 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1987,7 +1987,7 @@ void read_only::convert_key(const string& index_type, const string& encode_type, string bytes_data = boost::algorithm::unhex(index_value); auto bytes_datasize = bytes_data.size(); if( bin.size() < bytes_datasize ) { - bin.resize(index_value.size()); + bin.resize(bytes_datasize); } memcpy(bin.data(), bytes_data.data(), bytes_datasize); return; @@ -2127,7 +2127,6 @@ read_only::get_table_rows_result read_only::get_kv_table_rows( const read_only:: const abi_def abi = eosio::chain_apis::get_abi(db, p.code); name database_id = chain::kvram_id; - // Enable the code block once rocksdb_nodeos_integratin is merged const chain::kv_database_config &limits = kv_config; auto &kv_database = const_cast(db).kv_db(); // To do: provide kv_resource_manmager to create_kv_context @@ -2136,7 +2135,8 @@ read_only::get_table_rows_result read_only::get_kv_table_rows( const read_only:: return get_kv_table_rows_context(p, *kv_context, abi); } -read_only::get_table_rows_result read_only::get_kv_table_rows_context( const read_only::get_kv_table_rows_params& p, kv_context &kv_context, const abi_def &abi )const { +read_only::get_table_rows_result read_only::get_kv_table_rows_context( const read_only::get_kv_table_rows_params& pp, kv_context &kv_context, const abi_def &abi )const { + read_only::get_kv_table_rows_params p(pp); string tbl_name = p.table.to_string(); // Check valid table name @@ -2172,49 +2172,14 @@ read_only::get_table_rows_result read_only::get_kv_table_rows_context( const rea auto end_time = cur_time + fc::microseconds(1000 * 10); auto wait_time = end_time - cur_time; + bool unbounded = false; /////////////////////////////////////////////////////////// // point query /////////////////////////////////////////////////////////// if( point_query ) { const string &index_value = *p.index_value; - vector iv; - convert_key(index_type, p.encode_type, index_value, iv); - vector key; - key.resize(prefix.size() + iv.size()); - memcpy(key.data(), prefix.data(), prefix.size()); - memcpy(key.data() + prefix.size(), iv.data(), iv.size()); - - fc::variant row_var; - auto success = kv_context.kv_get(p.code.to_uint64_t(), key.data(), key.size(), value_size); - if( success ) { - vector value; - value.resize(value_size); - auto return_size = kv_context.kv_get_data(offset, value.data(), value_size); - EOS_ASSERT(return_size == value_size, chain::contract_table_query_exception, "point query value size mismatch: ${s1} ${s2}", ("s1", return_size)("s2", value_size)); - - if( !is_primary_idx) { - success = kv_context.kv_get(p.code.to_uint64_t(), value.data(), value.size(), value_size); - if (success) { - value.resize(value_size); - return_size = kv_context.kv_get_data(offset, value.data(), value_size); - EOS_ASSERT(return_size == value_size, chain::contract_table_query_exception, "point query value size mismatch: ${s1} ${s2}", ("s1", return_size)("s2", value_size)); - } - } - - fc::variant row_var; - if( p.json ) { - try { - row_var = abis.binary_to_variant( p.table.to_string(), value, abi_serializer::create_yield_function( wait_time ), shorten_abi_errors ); - } catch ( fc::exception &e ) { - row_var = fc::variant(value); - } - } else { - row_var = fc::variant(value); - } - result.rows.emplace_back( std::move(row_var) ); - } - - return result; + p.lower_bound = p.index_value; + unbounded = true; } /////////////////////////////////////////////////////////// @@ -2222,30 +2187,44 @@ read_only::get_table_rows_result read_only::get_kv_table_rows_context( const rea /////////////////////////////////////////////////////////// bool has_lower_bound = p.lower_bound.has_value() && !p.lower_bound.value().empty(); bool has_upper_bound = p.upper_bound.has_value() && !p.upper_bound.value().empty(); - EOS_ASSERT(has_lower_bound && has_upper_bound, chain::contract_table_query_exception, "Unknown range: ${t} ${i}", ("t", p.table)("i", p.index_name)); + unbounded = !has_lower_bound || !has_upper_bound; + // reverse mode has upper_bound value, non-reverse and point query has lower bound value + EOS_ASSERT((p.reverse && has_upper_bound) || (!p.reverse && has_lower_bound) || (has_lower_bound && point_query), chain::contract_table_query_exception, "Unknown/Invalid range: ${t} ${i}", ("t", p.table)("i", p.index_name)); vector lb_key; const string &lower_bound = *p.lower_bound; vector lv; - convert_key(index_type, p.encode_type, lower_bound, lv); - lb_key.resize(prefix.size() + lv.size()); - memcpy(lb_key.data(), prefix.data(), prefix.size()); - memcpy(lb_key.data() + prefix.size(), lv.data(), lv.size()); + + if( has_lower_bound ) { + convert_key(index_type, p.encode_type, lower_bound, lv); + lb_key.resize(prefix.size() + lv.size()); + memcpy(lb_key.data(), prefix.data(), prefix.size()); + memcpy(lb_key.data() + prefix.size(), lv.data(), lv.size()); + } vector ub_key; const string &upper_bound = *p.upper_bound; vector uv; - convert_key(index_type, p.encode_type, upper_bound, uv); - ub_key.resize(prefix.size() + uv.size()); - memcpy(ub_key.data(), prefix.data(), prefix.size()); - memcpy(ub_key.data() + prefix.size(), uv.data(), uv.size()); + if( has_upper_bound ) { + convert_key(index_type, p.encode_type, upper_bound, uv); + ub_key.resize(prefix.size() + uv.size()); + memcpy(ub_key.data(), prefix.data(), prefix.size()); + memcpy(ub_key.data() + prefix.size(), uv.data(), uv.size()); + } // Iterate the range vector row_key; vector row_value; - auto lb_itr = kv_context.kv_it_create(p.code.to_uint64_t(), prefix.data(), prefix.size()); - auto ub_itr = kv_context.kv_it_create(p.code.to_uint64_t(), prefix.data(), prefix.size()); + std::unique_ptr lb_itr; + std::unique_ptr ub_itr; + + if( has_lower_bound ) { + lb_itr = kv_context.kv_it_create(p.code.to_uint64_t(), prefix.data(), prefix.size()); + } + if( has_upper_bound ) { + ub_itr = kv_context.kv_it_create(p.code.to_uint64_t(), prefix.data(), prefix.size()); + } uint32_t lb_key_size = 0; uint32_t lb_value_size = 0; @@ -2255,19 +2234,33 @@ read_only::get_table_rows_result read_only::get_kv_table_rows_context( const rea uint32_t ub_key_actual_size = 0; // Find lower bound iterator - auto status_lb = lb_itr->kv_it_lower_bound(lb_key.data(), lb_key.size(), &lb_key_size, &lb_value_size); - EOS_ASSERT(status_lb != chain::kv_it_stat::iterator_erased, chain::contract_table_query_exception, "Invalid lower bound iterator in ${t} ${i}", ("t", p.table)("i", p.index_name)); - lb_key.resize(lb_key_size); - status_lb = lb_itr->kv_it_key(0, lb_key.data(), lb_key_size, lb_key_actual_size); + auto status_lb = chain::kv_it_stat::iterator_end; + if( has_lower_bound ) { + status_lb = lb_itr->kv_it_lower_bound(lb_key.data(), lb_key.size(), &lb_key_size, &lb_value_size); + EOS_ASSERT(status_lb != chain::kv_it_stat::iterator_erased, chain::contract_table_query_exception, "Invalid lower bound iterator in ${t} ${i}", ("t", p.table)("i", p.index_name)); + + if( !point_query ) { + lb_key.resize(lb_key_size); + status_lb = lb_itr->kv_it_key(0, lb_key.data(), lb_key_size, lb_key_actual_size); + } + } // Find upper bound iterator - auto status_ub = ub_itr->kv_it_lower_bound(ub_key.data(), ub_key.size(), &ub_key_size, &ub_value_size); - EOS_ASSERT(status_ub != chain::kv_it_stat::iterator_erased, chain::contract_table_query_exception, "Invalid upper bound iterator in ${t} ${i}", ("t", p.table)("i", p.index_name)); - ub_key.resize(ub_key_size); - status_ub = ub_itr->kv_it_key(0, ub_key.data(), ub_key_size, ub_key_actual_size); + auto status_ub = chain::kv_it_stat::iterator_end; + if( has_upper_bound ) { + auto exact_match = kv_context.kv_get(p.code.to_uint64_t(), ub_key.data(), ub_key.size(), value_size); + status_ub = ub_itr->kv_it_lower_bound(ub_key.data(), ub_key.size(), &ub_key_size, &ub_value_size); + if( p.reverse && !point_query && !exact_match ) { + status_ub = ub_itr->kv_it_prev(&ub_key_size, &ub_value_size); + } + + EOS_ASSERT(status_ub != chain::kv_it_stat::iterator_erased, chain::contract_table_query_exception, "Invalid upper bound iterator in ${t} ${i}", ("t", p.table)("i", p.index_name)); + ub_key.resize(ub_key_size); + status_ub = ub_itr->kv_it_key(0, ub_key.data(), ub_key_size, ub_key_actual_size); + } kv_it_stat status; - // iterate the range + //=============================== iterate the range ============================ auto walk_table_row_range = [&]( auto &itr, auto &end_itr, bool reverse ) { uint32_t actual_size = 0; @@ -2279,25 +2272,42 @@ read_only::get_table_rows_result read_only::get_kv_table_rows_context( const rea key_size = lb_key_size; value_size = lb_value_size; } - auto cmp = itr->kv_it_key_compare(end_key.data(), end_key.size()); - if( reverse ) cmp *= -1; + + auto cur_status = itr->kv_it_status(); + int32_t cmp = 0; + if( unbounded ) { + cmp = (cur_status == chain::kv_it_stat::iterator_ok ) ? -1 : 1; + } else { + cmp = itr->kv_it_key_compare(end_key.data(), end_key.size()); + if( reverse ) { + cmp *= -1; + } + } unsigned int count = 0; for( count = 0; cur_time <= end_time && count < p.limit && cmp < 0; cur_time = fc::time_point::now() ) { + row_key.resize(key_size); + status = itr->kv_it_key(0, row_key.data(), key_size, value_size); + if( point_query ) { + if( row_key.size() != lb_key_size || row_key != lb_key) { + cmp = 1; + continue; + } + } + row_value.clear(); row_value.resize(value_size); - status = itr->kv_it_value(offset, row_value.data(), value_size, actual_size); - EOS_ASSERT(status_lb == chain::kv_it_stat::iterator_ok, chain::contract_table_query_exception, "Invalid key in ${t} ${i}", ("t", p.table)("i", p.index_name)); + status = itr->kv_it_value(offset, row_value.data(), key_size, value_size); + EOS_ASSERT(status == chain::kv_it_stat::iterator_ok, chain::contract_table_query_exception, "Invalid key in ${t} ${i}", ("t", p.table)("i", p.index_name)); if (!is_primary_idx) { - std::swap(row_key, row_value); - auto success = kv_context.kv_get(p.code.to_uint64_t(), row_key.data(), row_key.size(), value_size); + auto success = kv_context.kv_get(p.code.to_uint64_t(), row_value.data(), row_value.size(), value_size); if (success) { row_value.resize(value_size); actual_size = kv_context.kv_get_data(offset, row_value.data(), value_size); EOS_ASSERT(value_size == actual_size, chain::contract_table_query_exception, "range query value size mismatch: ${s1} ${s2}", ("s1", value_size)("s2", actual_size)); } else { - EOS_ASSERT(false, chain::contract_table_query_exception, "range query failed to get data: ${t} ${i}", ("t", p.table)("i", p.index_name)); + EOS_ASSERT(value_size == actual_size, chain::contract_table_query_exception, "range query value size mismatch: ${s1} ${s2}", ("s1", value_size)("s2", actual_size)); } } @@ -2325,16 +2335,29 @@ read_only::get_table_rows_result read_only::get_kv_table_rows_context( const rea } EOS_ASSERT(status != chain::kv_it_stat::iterator_erased, chain::contract_table_query_exception, "Invalid lower bound iterator in ${t} ${i}", ("t", p.table)("i", p.index_name)); - cmp = itr->kv_it_key_compare(end_key.data(), end_key.size()); - if( reverse ) cmp *= -1; + if (unbounded) { + cur_status = itr->kv_it_status(); + cmp = (cur_status == chain::kv_it_stat::iterator_ok) ? -1 : 1; + if( point_query && cmp != 0) { + cmp = 1; + } + } else { + cmp = itr->kv_it_key_compare(end_key.data(), end_key.size()); + if (reverse) { + cmp *= -1; + } + } - if( count == p.limit && cmp < 0 ) { + if (count == p.limit && cmp < 0) + { result.more = true; row_key.resize(key_size); auto status = itr->kv_it_key(0, row_key.data(), key_size, value_size); EOS_ASSERT(status != chain::kv_it_stat::iterator_erased && value_size >= prefix_size(), chain::contract_table_query_exception, "Invalid lower bound iterator in ${t} ${i}", ("t", p.table)("i", p.index_name)); - result.next_key = string(&row_key.data()[prefix_size()], row_key.size() - prefix_size()); - } + auto next_key = string(&row_key.data()[prefix_size()], row_key.size() - prefix_size()); + + boost::algorithm::hex(next_key.begin(), next_key.end(), std::back_inserter(result.next_key)); + } } // end of for }; diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index ca8e9821d34..cdcc0cda203 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2881,12 +2881,13 @@ int main( int argc, char** argv ) { getKvTable->add_option( "table", table, localized("The name of the kv_table as specified by the contract abi") )->required(); getKvTable->add_option( "index_name", index_name, localized("The name of the kv_table index as specified by the contract abi") )->required(); getKvTable->add_option( "-l,--limit", limit, localized("The maximum number of rows to return") ); - getKvTable->add_option( "-k,--key", index_value, localized("Index value") ); - getKvTable->add_option( "-L,--lower", lower, localized("JSON representation of lower bound value of key, defaults to first") ); - getKvTable->add_option( "-U,--upper", upper, localized("JSON representation of upper bound value of key, defaults to last") ); + getKvTable->add_option("-i,--index", index_value, localized("Index value")); + getKvTable->add_option( "-L,--lower", lower, localized("lower bound value of index, optional with -r") ); + getKvTable->add_option( "-U,--upper", upper, localized("upper bound value of index, optional without -r") ); getKvTable->add_option( "--encode-type", encode_type, localized("The encoding type of index_value, lower bound, upper bound" " 'bytes' for hexdecimal encoded bytes" + " 'string' for string value" " 'dec' for decimal encoding of (uint[64|32|16|8], int[64|32|16|8], float64)" " 'hex' for hexdecimal encoding of (uint[64|32|16|8], int[64|32|16|8], sha256, ripemd160" )); getKvTable->add_flag("-b,--binary", binary, localized("Return the value as BINARY rather than using abi to interpret as JSON")); diff --git a/tests/get_kv_table_nodeos_tests.cpp b/tests/get_kv_table_nodeos_tests.cpp index bb3be65598e..3d4181730c4 100644 --- a/tests/get_kv_table_nodeos_tests.cpp +++ b/tests/get_kv_table_nodeos_tests.cpp @@ -33,10 +33,10 @@ using namespace eosio::chain; using namespace eosio::testing; using namespace fc; -BOOST_AUTO_TEST_SUITE(get_kv_table_cleos_tests) +BOOST_AUTO_TEST_SUITE(get_kv_table_nodeos_tests) -BOOST_FIXTURE_TEST_CASE( get_kv_table_cleos_test, TESTER ) try { +BOOST_FIXTURE_TEST_CASE( get_kv_table_nodeos_test, TESTER ) try { produce_blocks(2); create_accounts({ "kvtable"_n }); @@ -65,102 +65,253 @@ BOOST_FIXTURE_TEST_CASE( get_kv_table_cleos_test, TESTER ) try { p.encode_type = "name"; p.lower_bound = ""; p.upper_bound = ""; - p.json = true; + p.json = false; p.reverse = false; eosio::chain_apis::read_only::get_table_rows_result result = plugin.read_only::get_kv_table_rows(p); BOOST_REQUIRE_EQUAL(1u, result.rows.size()); p.index_name = "primarykey"_n; - p.index_value = "bobd"; + p.index_value = "bobj"; p.encode_type = "name"; result = plugin.read_only::get_kv_table_rows(p); BOOST_REQUIRE_EQUAL(1u, result.rows.size()); - p.index_name = "primarykey"_n; p.index_value = "bobx"; - p.encode_type = "name"; result = plugin.read_only::get_kv_table_rows(p); BOOST_REQUIRE_EQUAL(0u, result.rows.size()); - p.index_name = "primarykey"_n; p.index_value = ""; - p.encode_type = "name"; p.lower_bound = "bobb"; p.upper_bound = "bobe"; result = plugin.read_only::get_kv_table_rows(p); BOOST_REQUIRE_EQUAL(3u, result.rows.size()); - p.code = "kvtable"_n; - p.table = "kvtable"_n; - p.index_name = "foo"_n; - p.index_value = "1"; - p.encode_type = "hex"; - p.lower_bound = ""; + p.lower_bound = "aaaa"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(10u, result.rows.size()); + + p.lower_bound = "boba"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(10u, result.rows.size()); + + p.lower_bound = "bobj"; p.upper_bound = ""; result = plugin.read_only::get_kv_table_rows(p); BOOST_REQUIRE_EQUAL(1u, result.rows.size()); - p.code = "kvtable"_n; - p.table = "kvtable"_n; - p.index_name = "foo"_n; - p.index_value = "3"; - p.encode_type = "dec"; + p.lower_bound = "bobz"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(0u, result.rows.size()); + + p.reverse = true; p.lower_bound = ""; + p.upper_bound = "bobz"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(10u, result.rows.size()); + + p.lower_bound = "bobc"; p.upper_bound = ""; + BOOST_CHECK_THROW(plugin.read_only::get_kv_table_rows(p), chain::contract_table_query_exception); + + p.reverse = false; + p.lower_bound = ""; + p.upper_bound = "bobz"; + BOOST_CHECK_THROW(plugin.read_only::get_kv_table_rows(p), chain::contract_table_query_exception); + + p.reverse = true; + p.lower_bound = "bobj"; + p.upper_bound = "bobz"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(0u, result.rows.size()); + + p.lower_bound = "bobb"; + p.upper_bound = "bobe"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(3u, result.rows.size()); + + p.lower_bound = ""; + p.upper_bound = "bobe"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(5u, result.rows.size()); + + p.reverse = false; + 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()); + p.lower_bound = result.next_key; + p.encode_type = "bytes"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(2u, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, true); + p.lower_bound = result.next_key; + p.encode_type = "bytes"; + 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); + p.lower_bound = result.next_key; + p.encode_type = "bytes"; + 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); - p.code = "kvtable"_n; - p.table = "kvtable"_n; + p.reverse = false; p.index_name = "foo"_n; - p.index_value = "999"; - p.encode_type = "dec"; + p.index_value = "A"; // 10 + p.encode_type = "hex"; p.lower_bound = ""; p.upper_bound = ""; result = plugin.read_only::get_kv_table_rows(p); - BOOST_REQUIRE_EQUAL(0u, result.rows.size()); + BOOST_REQUIRE_EQUAL(1u, result.rows.size()); + + p.index_value = "10"; + p.encode_type = "dec"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(1u, result.rows.size()); - p.code = "kvtable"_n; - p.table = "kvtable"_n; - p.index_name = "foo"_n; p.index_value = ""; p.encode_type = "dec"; + p.lower_bound = "0"; + p.upper_bound = "10"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(9u, result.rows.size()); + p.lower_bound = "2"; - p.upper_bound = "7"; + p.upper_bound = "9"; result = plugin.read_only::get_kv_table_rows(p); - BOOST_REQUIRE_EQUAL(5u, result.rows.size()); + BOOST_REQUIRE_EQUAL(7u, result.rows.size()); - p.code = "kvtable"_n; - p.table = "kvtable"_n; - p.index_name = "bar"_n; - p.index_value = "boba"; - p.encode_type = "string"; - p.lower_bound = ""; + p.lower_bound = "0"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(10u, result.rows.size()); + + p.lower_bound = "1"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(10u, result.rows.size()); + + p.lower_bound = "10"; p.upper_bound = ""; result = plugin.read_only::get_kv_table_rows(p); BOOST_REQUIRE_EQUAL(1u, result.rows.size()); - p.code = "kvtable"_n; - p.table = "kvtable"_n; + p.lower_bound = "11"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(0u, result.rows.size()); + + p.reverse = false; + 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); + p.lower_bound = result.next_key; + p.encode_type = "bytes"; + 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); + p.lower_bound = result.next_key; + p.encode_type = "bytes"; + p.limit = 20; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(5, result.rows.size()); + BOOST_REQUIRE_EQUAL(result.more, false); + p.index_name = "bar"_n; - p.index_value = "x"; + p.index_value = "boba"; p.encode_type = "string"; p.lower_bound = ""; p.upper_bound = ""; + p.json = false; + p.reverse = false; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(1u, result.rows.size()); + + p.index_value = "bobj"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(1u, result.rows.size()); + + p.index_value = "bobx"; result = plugin.read_only::get_kv_table_rows(p); BOOST_REQUIRE_EQUAL(0u, result.rows.size()); - p.code = "kvtable"_n; - p.table = "kvtable"_n; - p.index_name = "bar"_n; p.index_value = ""; - p.encode_type = "string"; p.lower_bound = "bobb"; p.upper_bound = "bobe"; result = plugin.read_only::get_kv_table_rows(p); BOOST_REQUIRE_EQUAL(3u, result.rows.size()); -} FC_LOG_AND_RETHROW() + p.lower_bound = "aaaa"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(10u, result.rows.size()); + p.lower_bound = "boba"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(10u, result.rows.size()); + + p.lower_bound = "bobj"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(1u, result.rows.size()); + + p.lower_bound = "bobz"; + p.upper_bound = ""; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(0u, result.rows.size()); + + p.reverse = true; + p.lower_bound = ""; + p.upper_bound = "bobz"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(10u, result.rows.size()); + + p.lower_bound = "bobj"; + p.upper_bound = "bobz"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(0u, result.rows.size()); + + p.lower_bound = "bobb"; + p.upper_bound = "bobe"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(3u, result.rows.size()); + + p.lower_bound = ""; + p.upper_bound = "bobe"; + result = plugin.read_only::get_kv_table_rows(p); + BOOST_REQUIRE_EQUAL(5u, result.rows.size()); + + p.reverse = false; + 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); + p.lower_bound = result.next_key; + p.encode_type = "bytes"; + 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); + p.lower_bound = result.next_key; + p.encode_type = "bytes"; + 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); + +} FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/get_kv_table_tests.cpp b/tests/get_kv_table_tests.cpp deleted file mode 100644 index 23a2734041d..00000000000 --- a/tests/get_kv_table_tests.cpp +++ /dev/null @@ -1,563 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include - -#include -#include - -#include -#include - -#ifdef NON_VALIDATING_TEST -#define TESTER tester -#else -#define TESTER validating_tester -#endif - -using namespace eosio; -using namespace eosio::chain; -using namespace eosio::testing; -using namespace fc; - -BOOST_AUTO_TEST_SUITE(get_kv_table_tests) - -fc::microseconds max_serialization_time = fc::seconds(1); - -const char *abi_def_abi = R"=====( -{ -"____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", -"version": "eosio::abi/1.2", -"types": [], -"structs": [ - { - "name": "mystruct", - "base": "", - "fields": [ - { - "name": "primarykey", - "type": "name" - }, - { - "name": "foo", - "type": "string" - }, - { - "name": "bar", - "type": "uint64" - } - ] - } -], -"actions": [], -"tables": [], -"kv_tables": { - "testtable": { - "type": "mystruct", - "primary_index": { - "name": "primarykey", - "type": "name" - }, - "secondary_indices": { - "foo": { - "type": "string" - }, - "bar": { - "type": "uint64" - } - } - } -}, -"ricardian_clauses": [], -"variants": [], -"action_results": [] -} -)====="; - -const char *test_data1 = R"=====( -{ - "type": "mystruct", - "primarykey": "pid1", - "foo": "aaa", - "bar": 1000 -} -)====="; - -const char *test_data2 = R"=====( -{ - "type": "mystruct", - "primarykey": "pid2", - "foo": "bbb", - "bar": 2000 -} -)====="; - -const char *test_data3 = R"=====( -{ - "type": "mystruct", - "primarykey": "pid3", - "foo": "ccc", - "bar": 3000 -} -)====="; - -const char *test_data4 = R"=====( -{ - "type": "mystruct", - "primarykey": "pid4", - "foo": "ddd", - "bar": 4000 -} -)====="; - - -const char *test_data5 = R"=====( -{ - "type": "mystruct", - "primarykey": "pid5", - "foo": "eee", - "bar": 5000 -} -)====="; - -BOOST_FIXTURE_TEST_CASE( kv_table_make_prefix_test, TESTER ) try { - chain_apis::read_only plugin(*(this->control), std::optional(), max_serialization_time); - kv_database_config limits; - - vector prefix; - vector expected = {1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1}; - - auto check = [&](const vector &expect) { - bool matched = true; - for( int i = 0; i < prefix.size(); ++i ) { - if( (unsigned char)prefix[i] != expect[i] ) { - matched = false; - break; - } - } - return matched; - }; - - eosio::name table(0x01); - eosio::name index(0x01); - BOOST_CHECK_THROW(plugin.make_prefix(table, index, 1, *reinterpret_cast *>(&prefix)), chain::contract_table_query_exception); - - prefix.resize(2 * sizeof(uint64_t) + 1); - plugin.make_prefix(table, index, 1, *reinterpret_cast *>(&prefix)); - - BOOST_TEST(check(expected)); - - eosio::name table2("testtable"); - eosio::name index2("primarykey"); - prefix[0] = 0; - vector expected2 = {0, 0xca, 0xb1, 0x9c, 0x98, 0xf1, 0x50, 0x00, 0x00, 0xad, 0xdd, 0x23, 0x5f, 0xd0, 0x57, 0x80, 0x00}; - plugin.make_prefix(table2, index2, 0, *reinterpret_cast *>(&prefix)); - BOOST_TEST(check(expected2)); -} FC_LOG_AND_RETHROW() - -// Mock kv_iterator and kv_context -struct test_empty_kv_iterator : public kv_iterator { - - ~test_empty_kv_iterator() {} - - bool is_kv_chainbase_context_iterator() const { return true; } - bool is_kv_rocksdb_context_iterator() const override { return false; } - kv_it_stat kv_it_status() { return kv_it_stat::iterator_ok; } - int32_t kv_it_compare(const kv_iterator& rhs) { return 0; } - int32_t kv_it_key_compare(const char* key, uint32_t size) { return 0; } - kv_it_stat kv_it_move_to_end() { return kv_it_stat::iterator_ok; } - kv_it_stat kv_it_next(uint32_t* found_key_size, uint32_t* found_value_size) { return kv_it_stat::iterator_ok; } - kv_it_stat kv_it_prev(uint32_t* found_key_size, uint32_t* found_value_size) { return kv_it_stat::iterator_ok; } - kv_it_stat kv_it_lower_bound(const char* key, uint32_t size, - uint32_t* found_key_size, uint32_t* found_value_size) { return kv_it_stat::iterator_ok; } - kv_it_stat kv_it_key(uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size) { return kv_it_stat::iterator_ok; } - kv_it_stat kv_it_value(uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size) { return kv_it_stat::iterator_ok; } -}; - -struct test_empty_kv_context : public kv_context { - int64_t kv_erase(uint64_t contract, const char* key, uint32_t key_size) { return 0; } - int64_t kv_set(uint64_t contract, const char* key, uint32_t key_size, const char* value, - uint32_t value_size, account_name payer) { return 0; } - bool kv_get(uint64_t contract, const char* key, uint32_t key_size, uint32_t& value_size) { return true; } - uint32_t kv_get_data(uint32_t offset, char* data, uint32_t data_size) { return 0; } - - std::unique_ptr kv_it_create(uint64_t contract, const char* prefix, uint32_t size) { - return std::unique_ptr(new test_empty_kv_iterator()); - } -}; - - -struct test_point_query_kv_context : public test_empty_kv_context { - test_point_query_kv_context(const abi_def &abidef) : abi(abidef), abis() { - abis.set_abi(abi, abi_serializer::create_yield_function(max_serialization_time)); - - auto var = fc::json::from_string(test_data1); - auto binary = abis.variant_to_binary("testtable", var, abi_serializer::create_yield_function(max_serialization_time)); - binaries.push_back(binary); - auto var2 = abis.binary_to_variant("testtable", binary, abi_serializer::create_yield_function(max_serialization_time)); - - var = fc::json::from_string(test_data2); - binary = abis.variant_to_binary("testtable", var, abi_serializer::create_yield_function(max_serialization_time)); - binaries.push_back(binary); - } - - bool kv_get(uint64_t contract, const char *key, uint32_t key_size, uint32_t &value_size) { - const auto &binary = binaries[current_data]; - value_size = binary.size(); - return true; - } - - uint32_t kv_get_data(uint32_t offset, char* data, uint32_t data_size) { - const auto &binary = binaries[current_data]; - memcpy(data, binary.data(), binary.size()); - current_data = (++current_data) % 2; - return data_size; - } - - fc::microseconds max_serialization_time = fc::seconds(1); // some test machines are very slow - std::vector binaries; - int current_data = 0; - abi_def abi; - abi_serializer abis; -}; - -struct test_range_query_kv_context; - -struct test_range_query_kv_iterator : public test_empty_kv_iterator { - test_range_query_kv_iterator( map &binaries, vector &prefix, const string ¤t_key) - : binaries(binaries), prefix(prefix), current_key(current_key) { - } - - int32_t kv_it_key_compare(const char* key, uint32_t size) { - string other_key(key, size); - if( current_key == other_key) return 0; - - return current_key > other_key ? 1 : -1; - } - - kv_it_stat kv_it_next(uint32_t* found_key_size, uint32_t* found_value_size) { - auto next_itr = binaries.upper_bound(current_key); - if (next_itr == binaries.end()) - { - return kv_it_stat::iterator_end; - } - *found_key_size = next_itr->first.size(); - *found_value_size = next_itr->second.size(); - current_key = next_itr->first; - - return kv_it_stat::iterator_ok; - } - - kv_it_stat kv_it_prev(uint32_t *found_key_size, uint32_t *found_value_size) - { - auto prev_itr = binaries.lower_bound(current_key); - --prev_itr; - *found_key_size = prev_itr->first.size(); - *found_value_size = prev_itr->second.size(); - current_key = prev_itr->first; - - return kv_it_stat::iterator_ok; - } - - kv_it_stat kv_it_lower_bound(const char* key, uint32_t size, uint32_t* found_key_size, uint32_t* found_value_size) { - string skey(key, size); - auto lower_itr = binaries.lower_bound(skey); - if( lower_itr == binaries.end() ) { - return kv_it_stat::iterator_end; - } - *found_key_size = lower_itr->first.size(); - *found_value_size = lower_itr->second.size(); - current_key = lower_itr->first; - - return kv_it_stat::iterator_ok; - } - - kv_it_stat kv_it_key(uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size) { - actual_size = current_key.size(); - memcpy(dest, current_key.data(), actual_size); - - return kv_it_stat::iterator_ok; - } - - kv_it_stat kv_it_value(uint32_t offset, char* dest, uint32_t size, uint32_t& actual_size) { - auto binary = binaries[current_key]; - actual_size = binary.size(); - memcpy(dest, binary.data(), actual_size); - - return kv_it_stat::iterator_ok; - } - - map &binaries; - vector &prefix; - string current_key; -}; - - -struct test_range_query_kv_context : public test_empty_kv_context { - test_range_query_kv_context(const abi_def &abidef) : abi(abidef), abis() { - abis.set_abi(abi, abi_serializer::create_yield_function(max_serialization_time)); - - vector test_data_vec{test_data1, test_data2, test_data3, test_data4, test_data5}; - vector> prefixes{prefix1, prefix2, prefix2}; - - for( auto &prefix : prefixes ) { - size_t prefix_size = prefix.size(); - vector key; - key.resize(prefix_size + 6); // index value "pidN\0\0" is appended where N is [1-5] - for( int i = 0; i < prefix_size; ++i ) { - key[i] = static_cast(prefix[i]); - } - key[prefix_size] = static_cast('p'); - key[prefix_size + 1] = static_cast('i'); - key[prefix_size + 2] = static_cast('d'); - key[prefix_size + 4] = 0; - key[prefix_size + 5] = 0; - - - for( int i = 0; i < 5; ++i ) { - auto &test_data = test_data_vec[i]; - auto var = fc::json::from_string(test_data); - auto binary = abis.variant_to_binary("testtable", var, abi_serializer::create_yield_function(max_serialization_time)); - key[prefix_size + 3] = static_cast('1' + i); - string skey1(reinterpret_cast(key.data()), key.size()); - binaries[skey1] = binary; - } - } - } - - bool kv_get(uint64_t contract, const char *key, uint32_t key_size, uint32_t &value_size) { - string skey(key, key_size); - current_key = skey; - const auto &binary = binaries[skey]; - value_size = binary.size(); - return true; - } - - uint32_t kv_get_data(uint32_t offset, char* data, uint32_t data_size) { - const auto &binary = binaries[current_key]; - memcpy(data, binary.data(), binary.size()); - return data_size; - } - - std::unique_ptr kv_it_create(uint64_t contract, const char* prefix, uint32_t size) { - return std::unique_ptr(new test_range_query_kv_iterator(binaries, this->prefix2, current_key)); - } - - fc::microseconds max_serialization_time = fc::seconds(1); // some test machines are very slow - map binaries; - vector prefix1{0x00, 0xca, 0xb1, 0x9c, 0x98, 0xf1, 0x50, 0x00, 0x00, 0xad, 0xdd, 0x23, 0x5f, 0xd0, 0x57, 0x80, 0x00}; - vector prefix2{0x01, 0xca, 0xb1, 0x9c, 0x98, 0xf1, 0x50, 0x00, 0x00, 0xad, 0xdd, 0x23, 0x5f, 0xd0, 0x57, 0x80, 0x00}; - vector prefix3{0x01, 0xca, 0xb1, 0x9c, 0x98, 0xf1, 0x50, 0x00, 0x00, 0xad, 0xdd, 0x23, 0x5f, 0xd0, 0x57, 0x80, 0x01}; - - string current_key; - abi_def abi; - abi_serializer abis; - - friend struct test_range_query_kv_iterator; - -}; - -BOOST_FIXTURE_TEST_CASE(get_kv_table_rows_point_query_test, TESTER) try { - chain_apis::read_only plugin(*(this->control), std::optional(), fc::microseconds::maximum()); - kv_database_config limits; - - auto var = fc::json::from_string(abi_def_abi); - auto abi = var.as(); - - chain_apis::read_only::get_kv_table_rows_params p{ - .json = true, - .code = name("kvmindices"), - .table = name("testtable"), - .index_name = name("primarykey"), - .encode_type = "string", - .index_value = "pid1", - .limit = 100, - .reverse = false, - .show_payer = false - }; - - // mock kv_context - test_point_query_kv_context context(abi); - - auto result = plugin.get_kv_table_rows_context(p, context, abi); - BOOST_TEST(result.rows[0]["primarykey"] == "pid1"); - BOOST_TEST(result.rows[0]["foo"] == "aaa"); - BOOST_TEST(result.rows[0]["bar"] == 1000); - - p.table = name("wrongtable"); - BOOST_CHECK_THROW( plugin.get_kv_table_rows_context(p, context, abi), fc::exception ); - - p.index_name = name("wrongindex"); - BOOST_CHECK_THROW( plugin.get_kv_table_rows_context(p, context, abi), fc::exception ); -} FC_LOG_AND_RETHROW() - -BOOST_FIXTURE_TEST_CASE(get_kv_table_rows_range_query_test, TESTER) try { - chain_apis::read_only plugin(*(this->control), std::optional(), fc::microseconds::maximum()); - kv_database_config limits; - - auto var = fc::json::from_string(abi_def_abi); - auto abi = var.as(); - abi_serializer abis; - abis.set_abi(abi, abi_serializer::create_yield_function(abi_serializer_max_time)); - - test_range_query_kv_context context(abi); - - // missing index_value, lower_bound, upper_bound - chain_apis::read_only::get_kv_table_rows_params range0{ - .json = true, - .code = name("kvmindices"), - .table = name("testtable"), - .index_name = name("primarykey"), - .encode_type = "string", - .limit = 100, - .reverse = false, - .show_payer = false - }; - BOOST_CHECK_THROW(plugin.get_kv_table_rows_context(range0, context, abi), fc::exception); - - chain_apis::read_only::get_kv_table_rows_params range1{ - .json = true, - .code = name("kvmindices"), - .table = name("testtable"), - .index_name = name("primarykey"), - .encode_type = "string", - .index_value = "", - .lower_bound = "pid2", - .upper_bound = "pid4", - .limit = 100, - .reverse = false, - .show_payer = false - }; - - auto result = plugin.get_kv_table_rows_context(range1, context, abi); - BOOST_TEST(result.rows.size() == 2); - BOOST_TEST(result.rows[0]["primarykey"] == "pid2"); - BOOST_TEST(result.rows[0]["foo"] == "bbb"); - BOOST_TEST(result.rows[0]["bar"] == 2000); - BOOST_TEST(result.rows[1]["primarykey"] == "pid3"); - BOOST_TEST(result.rows[1]["foo"] == "ccc"); - BOOST_TEST(result.rows[1]["bar"] == 3000); - - range1.json = false; - result = plugin.get_kv_table_rows_context(range1, context, abi); - vector bytes; - from_variant(result.rows[0], bytes); - auto bin_var = abis.binary_to_variant("testtable", bytes, abi_serializer::create_yield_function(max_serialization_time)); - BOOST_TEST(bin_var["primarykey"] == "pid2"); - BOOST_TEST(bin_var["foo"] == "bbb"); - BOOST_TEST(bin_var["bar"] == 2000); - from_variant(result.rows[1], bytes); - bin_var = abis.binary_to_variant("testtable", bytes, abi_serializer::create_yield_function(max_serialization_time)); - BOOST_TEST(bin_var["primarykey"] == "pid3"); - BOOST_TEST(bin_var["foo"] == "ccc"); - BOOST_TEST(bin_var["bar"] == 3000); - - range1.json = true; - range1.reverse = true; - result = plugin.get_kv_table_rows_context(range1, context, abi); - BOOST_TEST(result.rows.size() == 2); - BOOST_TEST(result.rows[0]["primarykey"] == "pid4"); - BOOST_TEST(result.rows[0]["foo"] == "ddd"); - BOOST_TEST(result.rows[0]["bar"] == 4000); - BOOST_TEST(result.rows[1]["primarykey"] == "pid3"); - BOOST_TEST(result.rows[1]["foo"] == "ccc"); - BOOST_TEST(result.rows[1]["bar"] == 3000); - - range1.json = false; - result = plugin.get_kv_table_rows_context(range1, context, abi); - BOOST_TEST(result.rows.size() == 2); - from_variant(result.rows[0], bytes); - bin_var = abis.binary_to_variant("testtable", bytes, abi_serializer::create_yield_function(max_serialization_time)); - BOOST_TEST(bin_var["primarykey"] == "pid4"); - BOOST_TEST(bin_var["foo"] == "ddd"); - BOOST_TEST(bin_var["bar"] == 4000); - from_variant(result.rows[1], bytes); - bin_var = abis.binary_to_variant("testtable", bytes, abi_serializer::create_yield_function(max_serialization_time)); - BOOST_TEST(bin_var["primarykey"] == "pid3"); - BOOST_TEST(bin_var["foo"] == "ccc"); - BOOST_TEST(bin_var["bar"] == 3000); - - chain_apis::read_only::get_kv_table_rows_params range2{ - .json = true, - .code = name("kvmindices"), - .table = name("testtable"), - .index_name = name("primarykey"), - .encode_type = "string", - .index_value = "", - .lower_bound = "pid1", - .upper_bound = "pid5", - .limit = 100, - .reverse = true, - .show_payer = false - }; - result = plugin.get_kv_table_rows_context(range2, context, abi); - BOOST_TEST(result.rows.size() == 4); - BOOST_TEST(result.rows.size() == 4); - BOOST_TEST(result.rows[0]["primarykey"] == "pid5"); - BOOST_TEST(result.rows[1]["primarykey"] == "pid4"); - BOOST_TEST(result.rows[2]["primarykey"] == "pid3"); - BOOST_TEST(result.rows[3]["primarykey"] == "pid2"); - - chain_apis::read_only::get_kv_table_rows_params range3{ - .json = true, - .code = name("kvmindices"), - .table = name("testtable"), - .index_name = name("primarykey"), - .encode_type = "string", - .lower_bound = "pid1", - .upper_bound = "pid2", - .limit = 100, - .reverse = false, - .show_payer = false - }; - result = plugin.get_kv_table_rows_context(range3, context, abi); - BOOST_TEST(result.rows.size() == 1); - - chain_apis::read_only::get_kv_table_rows_params range4{ - .json = true, - .code = name("kvmindices"), - .table = name("testtable"), - .index_name = name("primarykey"), - .encode_type = "string", - .lower_bound = "pid5", - .upper_bound = "pid5", - .limit = 100, - .reverse = false, - .show_payer = false}; - result = plugin.get_kv_table_rows_context(range4, context, abi); - BOOST_TEST(result.rows.size() == 0); - - chain_apis::read_only::get_kv_table_rows_params range5{ - .json = true, - .code = name("kvmindices"), - .table = name("testtable"), - .index_name = name("primarykey"), - .encode_type = "string", - .lower_bound = "pid1", - .upper_bound = "pid5", - .limit = 2, - .reverse = false, - .show_payer = false - }; - result = plugin.get_kv_table_rows_context(range5, context, abi); - BOOST_TEST(result.rows.size() == 2); - range5.lower_bound = "pid3"; - result = plugin.get_kv_table_rows_context(range5, context, abi); - BOOST_TEST(result.rows.size() == 2); - BOOST_TEST(result.more == false); -} FC_LOG_AND_RETHROW() - -BOOST_AUTO_TEST_SUITE_END()