Advanced EOS Series — Part 3 — Secondary Indexes
Welcome back to the Advanced EOS development series, here I’ll be touching on advanced techniques and functionality which is rarely covered by tutorials or courses. The purpose of this series is to bring together the missing pieces you’ll need to complete your skills as a distributed application developer on the EOS network. Each post is ordered by difficulty, so if you’d like a general overview I’d recommend starting with Part 1 and working your way up. The full code for these examples can be found here on GitHub.
As these are advanced or extended topics, I’m dangerously assuming you already know the basics and are looking to further your knowledge. For that reason, the code shared in these articles will be concise only to the purpose being discussed.
Primary Indexes
By now you should know how to define a multi-index table in EOS using a struct, but for the sake of completeness let’s briefly glance over our basic table definition.
Defining the struct
we’re going to start by defining our struct
and it’s required primary_key
function which is the index used when we call find(KEY)
. For now let’s use the users account_name
for uniqueness and cover a more versatile method in the following chapter.
// @abi table items i64
struct Item {
account_name id;
string name;
uint64_t attack;
account_name owner;
auto primary_key() const { return id; };
EOSLIB_SERIALIZE(Item, (id)(name)(attack)(owner));
};
Creating the Table
Now we’ve outlined our structure. we can define our multi-index table using the multi_index<TABLE_NAME, TABLE_STRUCT>
method from the EOS library;
typedef multi_index<N(items), Item> items_table;
After our typedef
, we can simply define a reference and then initialize our table in our constructor, or we can initialize a reference within each function as we need it.
Secondary Indexes
Let’s expand the functionality of our table by adding a secondary index to fetch items by their respective owner. Our Item
struct will now look like;
// @abi table items i64
struct Item {
auto id;
string name;
uint64_t attack;
account_name owner;
auto primary_key() const { return id; };
uint64_t get_owner() const { return owner; };
EOSLIB_SERIALIZE(Item, (id)(name)(attack)(owner));
};
I’ve added the line unit64_t get_owner() const { return owner; };
below our primary_key
index. This function simply returns the value for owner
when called.
Defining the Secondary Index
Let’s define this as a secondary index in our multi-index table so we can access it later;
typedef multi_index<N(items), Item, indexed_by<N(byowner), const_mem_fun<Item, uint64_t, &Item::get_owner>>> item_table;
Don’t let that line scare you, it’s just our previous declaration with one additional argument.
indexed_by<N(byowner), const_mem_fun<Item, uint64_t, &Item::get_owner>>
This is the line which defines our secondary index. We’re using the indexed_by<INDEX_NAME, LOOKUP_FUNCTION_DEFINITION>
to specify the index namedbyowner
.
- INDEX_NAME; can be whatever you like and doesn’t need to syntactically match our function
get_owner
, just remember it’s the name we will use to access the secondary index later in our actions. - LOOKUP_FUNCTION_DEFINITION: which looks like
cons_mem_fun<STRUCT, RETURN_VALUE, LOOKUP_FUNCTION>
in our example, constructs our secondary index function and assigns it to the function of our index definition.
Using the Secondary Index
So we’ve indexed our item’s owner
, but how can we use our index to find items owned by a particular user? For this we will be stepping into an action and using the multi-index table method get_index<INDEX_NAME>()
, let’s create an action called inventory
to fetch a users items.
// @abi action
void inventory(const account_name account) {
item_table items(_self, _self);
auto playerItems = items.get_index<N(byowner)>();
auto iter = playerItems.lower_bound(account);
while (iter != playerItems.end()) {
print("Item ", iter->name);
iter++;
}
}
Let’s break out the following line and delve a little deeper into what it’s responsible for;
auto playerItems = items.get_index<N(byowner)>();
This line is using the get_index
function of our item_table
instance to fetch the index we defined previously. Notice we’re using the same name N(byowner)
we specified earlier to reference the index we’re seeking.
And that’s secondary indexes, now we have our index, we can perform all the functions we would normally do with the primary_index. In the example we’re using lower_bound
, but you could use find
, get
, upper_bound
, begin
, or end
to suit your particular use case.