Table Indexing
When it comes to indexing your tables, you have to be mindful of which data you'll need to access quickly/frequently within your contract. Less frequently accessed data can be handed using an off-chain indexer, or even a simple API call if you aren't storing tons of rows in your tables.
There are several options here, I'll outline the most common approaches below.
available_primary_key
This is the most straightforward of all. This method will just use the next available key, based on the highest value currently stored in your table. For example...
struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] balances {
uint64_t id;
eosio::name wallet;
eosio::asset balance;
uint64_t primary_key() const { return id; }
};
using balances_table = eosio::multi_index< "balances"_n, balances >;
ACTION createrow( const name& user, const asset& balance ){
balances_table bal_t = balances_table( _self, _self.value );
bal_t.emplace( user, [&](auto &_u){
_u.id = bal_t.available_primary_key();
_u.wallet = user;
_u.balance = balance;
});
}
By User
Let's say you have a table, and you are 100% certain that each user in the table will only need 1 row. You can handle this simply by using the user.value
struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] balances {
eosio::name wallet;
eosio::asset balance;
uint64_t primary_key() const { return wallet.value; }
};
using balances_table = eosio::multi_index< "balances"_n, balances >;
ACTION someaction( const name& user ){
require_auth( user );
balances_table bal_t = balances_table( _self, _self.value );
auto itr = bal_t.require_find( user.value, "you do not have a balance" );
}
By uint128_t Of 2 Values
Let's say for example, you have an NFT market contract. All of the drop purchases are stored in a single table, under the same scope. Each drop can contain multiple orders, and each user can have orders for multiple drops. Using the drop ID, or the user's wallet as a primary index will not work in this case. You can solve this using the following method.
#define mix64to128(a, b) (uint128_t(a) << 64 | uint128_t(b))
struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] orders {
uint64_t order_id;
eosio::name wallet;
uint64_t drop_id;
uint64_t primary_key() const { return order_id; }
uint128_t secondary_key() const { return mix64to128( wallet.value, drop_id ); }
};
using orders_table = eosio::multi_index<"orders"_n, orders,
eosio::indexed_by<"userdropmix"_n, eosio::const_mem_fun<orders, uint128_t, &orders::secondary_key>>
>;
ACTION findorder( const name& wallet, const uint64_t& drop_id ){
orders_table orders_t = orders_table( _self, _self.value );
auto orders_secondary = orders_t.get_index<"userdropmix"_n>();
uint128_t user_drop_key = mix64to128( wallet.value, drop_id );
auto itr = orders_secondary.require_find( user_drop_key, "no match found for this user + drop" );
}
By checksum256
Sometimes 128 bits is not enough to meet the needs for an index. For example, you may have a contract where you need to index by a combination of user, token and contract and be able to easily find that key. You can accomplish this by using a checksum256 hash, as demonstrated below.
struct [[eosio::table, eosio::contract(CONTRACT_NAME)]] tokens {
uint64_t id; // available_primary_key
eosio::name user;
eosio::extended_asset balance;
uint64_t primary_key() const { return id; }
// combine 3 values into a checksum256
static eosio::checksum256 create_checksum(const uint64_t& a, const uint64_t& b, const uint64_t& c) {
std::vector<char> data_stream(sizeof(uint64_t) * 3);
eosio::datastream<char*> ds(data_stream.data(), data_stream.size());
ds << a << b << c;
auto result_hash = eosio::sha256(data_stream.data(), data_stream.size());
return result_hash;
}
eosio::checksum256 by_user_token() const { return create_checksum( user.value, balance.quantity.symbol.code().raw(), balance.contract.value ); }
};
using tokens_table = eosio::multi_index<"tokens"_n, tokens,
eosio::indexed_by<"userandtoken"_n, eosio::const_mem_fun<tokens, eosio::checksum256, &tokens::by_user_token>>
>;
ACTION mycontract::dosomething(const name& user, const asset& quantity, const name& contract){
tokens_table tokens_t = tokens_table( _self, _self.value );
auto user_token_idx = tokens_t.get_index<"userandtoken"_n>();
checksum256 user_token_key = tokens::create_checksum( user.value, quantity.symbol.code().raw(), contract.value );
auto itr = user_token_idx.require_find( user_token_key, "could not locate user + token row" );
}
Last updated