Multi Index Table Usage Guide
Description
Below is a usage guide for the multi index table.
In the interest of being thorough and providing clarity, sections of the final .cpp file will be broken out and discussed in further detail. Note that the complete .cpp file can be found at the bottom of this page.
Glossary
code
- Refers to anaccount_name
where a contract has been published.scope
- Anaccount_name
that the data in question belongs to.table_name
- The name of the table that is stored in memory.
Code Break Down
Struct to be stored##
The data to be stored in the multi index table is the limit_order
struct. The functions primary_key()
, get_expiration()
, get_price()
are used to return the table. The returned table will be sorted based on which function was called.
struct limit_order {
uint64_t id;
uint128_t price;
uint64_t expiration;
account_name owner;
auto primary_key() const { return id; }
uint64_t get_expiration() const { return expiration; }
uint128_t get_price() const { return price; }
EOSLIB_SERIALIZE( limit_order, ( id )( price )( expiration )( owner ) )
};
Different from other RDBs, primary key cannot be defined from multiple columns. Only one columns can be a primary key. If you want to implement it, use secondary index and implement the logic manually.
Creating the multi index table##
auto payer = ilm.get_account();
...
payer
is the variable that holds the account who will be "billed" for adding elements to the multi index table and modifying elements already in the multi index table.
...
eosio::multi_index< N( orders ), limit_order,
...
N( orders )
is the name of the multi index table and limit_orders
is the data to be stored in the table.
...
indexed_by< N( byexp ), const_mem_fun< limit_order, uint64_t,
&limit_order::get_expiration> >,
...
indexed_by< N( byexp ), const_mem_fun< limit_order, uint64_t, &limit_order::get_expiration> >
is the definition of a way the orders
multi index table can be indexed. N( byexp )
is the name of this index. The const_mem_fun
indicates the data type being retrieved, limit_order
, the type of variable being sorted by, uint64_t
, and the function that will be used get the variable, get_expiration
.
...
indexed_by< N( byprice ), const_mem_fun< limit_order, uint128_t, &limit_order::get_price> >
...
indexed_by< N( byprice ), const_mem_fun< limit_order, uint128_t, &limit_order::get_price> >
is the definition of a way the orders
multi index table can be indexed. N( byprice )
is the name of this index. The const_mem_fun
indicates the data type being retrieved, limit_order
, the type of variable being sorted by, uint128_t
, and the function that will be used get the variable, get_price
.
orders( N( limitorders ), N( limitorders )
orders
is the multi index table.
auto payer = ilm.get_account();
print("Creating multi index table 'orders'.\n");
eosio::multi_index< N( orders ), limit_order,
indexed_by< N( byexp ), const_mem_fun< limit_order, uint64_t, &limit_order::get_expiration> >,
indexed_by< N( byprice ), const_mem_fun< limit_order, uint128_t, &limit_order::get_price> >
> orders( N( limitorders ), N( limitorders ) );
Adding to the multi index table##
Below, two limit_order
s are added to the orders
table. Note that payer
is the account that is being "billed" for the modification to the orders
table.
orders.emplace( payer, [&]( auto& o ) {
o.id = 1;
o.expiration = 300;
o.owner = N(dan);
});
auto order2 = orders.emplace( payer, [&]( auto& o ) {
o.id = 2;
o.expiration = 200;
o.owner = N(thomas);
});
If you want to use auto-increment feature, use available_primary_key().
Sorted by primary key##
By default, the orders
table is sorted by primary key.
print("Items sorted by primary key:\n");
for( const auto& item : orders ) {
print(" ID=", item.id, ", expiration=", item.expiration, ", owner=", name{item.owner}, "\n");
}
Sorted by secondary index - expiration##
The orders
table gets sorted by expiration and assigned to expidx
.
auto expidx = orders.get_index<N(byexp)>();
print("Items sorted by expiration:\n");
for( const auto& item : expidx ) {
print(" ID=", item.id, ", expiration=", item.expiration, ", owner=", name{item.owner}, "\n");
}
Sorted by secondary index - price##
The orders
table gets sorted by price and assigned to pridx
.
auto pridx = orders.get_index<N(byprice)>();
print("Items sorted by price:\n");
for( const auto& item : pridx ) {
print(" ID=", item.id, ", expiration=", item.expiration, ", owner=", name{item.owner}, "\n");
}
Modify an entry##
Below, the entry with "ID=2" gets modified. Note that payer
is the account that is being "billed" for the modification to the orders
table.
print("Modifying expiration of order with ID=2 to 400.\n");
orders.modify( order2, payer, [&]( auto& o ) {
o.expiration = 400;
});
Getting the lower bound##
auto lower = expidx.lower_bound(100);
print("First order with an expiration of at least 100 has ID=", lower->id, " and expiration=", lower->get_expiration(), "\n");
Complete .cpp file
#include <eosiolib/eosio.hpp>
#include <eosiolib/dispatcher.hpp>
#include <eosiolib/multi_index.hpp>
using namespace eosio;
namespace limit_order_table {
struct limit_order {
uint64_t id;
uint128_t price;
uint64_t expiration;
account_name owner;
auto primary_key() const { return id; }
uint64_t get_expiration() const { return expiration; }
uint128_t get_price() const { return price; }
EOSLIB_SERIALIZE( limit_order, ( id )( price )( expiration )( owner ) )
};
class limit_order_table {
public:
ACTION( N( limitorders ), issue_limit_order ) {
EOSLIB_SERIALIZE( issue_limit_order )
};
static void on( const issue_limit_order& ilm ) {
auto payer = ilm.get_account();
print("Creating multi index table 'orders'.\n");
eosio::multi_index< N( orders ), limit_order,
indexed_by< N( byexp ), const_mem_fun< limit_order, uint64_t, &limit_order::get_expiration> >,
indexed_by< N( byprice ), const_mem_fun< limit_order, uint128_t, &limit_order::get_price> >
> orders( N( limitorders ), N( limitorders ) );
orders.emplace( payer, [&]( auto& o ) {
o.id = 1;
o.expiration = 300;
o.owner = N(dan);
});
auto order2 = orders.emplace( payer, [&]( auto& o ) {
o.id = 2;
o.expiration = 200;
o.owner = N(thomas);
});
print("Items sorted by primary key:\n");
for( const auto& item : orders ) {
print(" ID=", item.id, ", expiration=", item.expiration, ", owner=", name{item.owner}, "\n");
}
auto expidx = orders.get_index<N(byexp)>();
print("Items sorted by expiration:\n");
for( const auto& item : expidx ) {
print(" ID=", item.id, ", expiration=", item.expiration, ", owner=", name{item.owner}, "\n");
}
auto pridx = orders.get_index<N(byprice)>();
print("Items sorted by price:\n");
for( const auto& item : pridx ) {
print(" ID=", item.id, ", expiration=", item.expiration, ", owner=", name{item.owner}, "\n");
}
print("Modifying expiration of order with ID=2 to 400.\n");
orders.modify( order2, payer, [&]( auto& o ) {
o.expiration = 400;
});
auto lower = expidx.lower_bound(100);
print("First order with an expiration of at least 100 has ID=", lower->id, " and expiration=", lower->get_expiration(), "\n");
};
} /// limit_order_table
namespace limit_order_table {
extern "C" {
/// The apply method implements the dispatch of events to this contract
void apply( uint64_t code, uint64_t action ) {
require_auth( code );
eosio_assert( eosio::dispatch< limit_order_table, limit_order_table::issue_limit_order >( code, action ), "Could not dispatch" );
}
}
}
Deleting a Table
Tables cannot be directly deleted, however, a table will delete itself automatically after all rows have been deleted.
Modifying a Table
Table cannot be directly modified. If you want to do this, follow this step.
1. Create another table
struct limit_order { // Old table
uint64_t id;
uint128_t price;
uint64_t expiration;
account_name owner;
auto primary_key() const { return id; }
};
typedef eosio::multi_index< N( orders ), limit_order> _limit_order;
struct limit_order2 { // New table
uint64_t id;
uint128_t price;
uint64_t expiration;
account_name owner;
asset byeos; // added column
auto primary_key() const { return id; }
};
typedef eosio::multi_index< N( orders2 ), limit_order2> _limit_order2;
2. Implement migration function
void sth::migrate(){
require_auth(_self);
_limit_order old_table(_self, _self);
_limit_order2 new_table(_self, _self);
auto itr = old_table.begin();
while ( itr != old_table.end() ){
new_table.emplace( _self, [&]( auto& o ) {
o.id = itr.id;
o.price = itr.price;
o.expiration = itr.expiration;
o.owner = itr.owner;
o.byeos = itr.price / 3; // implement yourself
});
itr++;
}
}
3. After migration, you may delete records of the old table
Updated less than a minute ago