マルチインデックステーブルのセカンダリインデックスを使ってフィルターする

マルチインデックステーブルを使う時、基本的にはプライマリーキーでレコードを特定して操作していますが、それ以外の条件で検索する場面もやはりあるので、セカンダリーインデックスの使い方を調査してみました。

## 定義
ポイントは２つあります。

- １つ目は、マルチインデックステーブルの構造体を定義する時、セカンダリーインデックス用の関数を定義する必要があります
- ２つ目は、マルチインデックステーブルの type を定義する時、セカンダリーインデックスの定義情報を追加する必要があります

```
struct [[eosio::table]] person {
  uint64_t id;
  string name;
  uint64_t age;
  string address;
  string tel;

  auto primary_key() const { return id; }
  uint64_t get_age() const { return age; } // ① get_age 関数を追加
};

// ② index_by 部分を追加
typedef eosio::multi_index<"people"_n, person, indexed_by<"age"_n, const_mem_fun<person, uint64_t, &person::get_age>>> address_index;
```

## `cleos get table` で使う

```shell
# デフォルトで条件なしで、プライマリーキーでソートされる
$ cleos get table addressbook addressbook people
{
  "rows": [{
      "id": 0,
      "name": "山田",
      "age": 38,
      "address": "横浜",
      "tel": "0451113333"
    },{
      "id": 1,
      "name": "田中",
      "age": 29,
      "address": "渋谷",
      "tel": "031112222"
    },{
      "id": 2,
      "name": "山本",
      "age": 45,
      "address": "品川",
      "tel": "0333334444"
    }
  ],
  "more": false
}

# --index で使うインデックス番号を指定すると、そのインデックスでソートされる
$ cleos get table addressbook addressbook people --key-type i64 --index 2
{
  "rows": [{
      "id": 1,
      "name": "田中",
      "age": 29,
      "address": "渋谷",
      "tel": "031112222"
    },{
      "id": 0,
      "name": "山田",
      "age": 38,
      "address": "横浜",
      "tel": "0451113333"
    },{
      "id": 2,
      "name": "山本",
      "age": 45,
      "address": "品川",
      "tel": "0333334444"
    }
  ],
  "more": false
}

# --lower でインデックス範囲の `from` を指定できる
$ cleos get table addressbook addressbook people --key-type i64 --index 2 --lower 30
{
  "rows": [{
      "id": 0,
      "name": "山田",
      "age": 38,
      "address": "横浜",
      "tel": "0451113333"
    },{
      "id": 2,
      "name": "山本",
      "age": 45,
      "address": "品川",
      "tel": "0333334444"
    }
  ],
  "more": false
}

# --upper でインデックス範囲の `to` を指定できる
$ cleos get table addressbook addressbook people --key-type i64 --index 2 --lower 30 --upper 40
{
  "rows": [{
      "id": 0,
      "name": "山田",
      "age": 38,
      "address": "横浜",
      "tel": "0451113333"
    }
  ],
  "more": false
}
```

## アクションで検索

```c++
[[eosio::action]]
void search(uint64_t age) {
  address_index addresses(_code, _code.value);

  auto age_index = addresses.get_index<"age"_n>();
  auto itr = age_index.lower_bound(age); // age 以上のデータを検索

  for (; itr != age_index.end(); itr++)
  {
    print("id: ", itr->id, " name: ", itr->name, " age: ", itr->age, "\n");
  }
}
```

アクションを呼び出すと下記になります。
トランザクション呼び出しのコンソールには１つ目の `print`結果だけ表示されていますが、`nodeos`側のログには想定どおりに該当データ分全部表示されています。

```shell
$ cleos push action addressbook search '[30]' -p addressbook
executed transaction: 9f95fc9f805526664867048660ef825f7f8dd846f3b1c509a813c2adcd597aa8  104 bytes  9412 us
#   addressbook <= addressbook::search          {"age":30}
>> id: 0 name: 山田 age: 38
warning: transaction executed locally, but may not be confirmed by the network yet         ]

# nodeos 側のログを確認すると下記になります
debug 2018-12-12T21:42:06.546 thread-0  apply_context.cpp:28          print_debug          ]
[(addressbook,search)->addressbook]: CONSOLE OUTPUT BEGIN =====================
id: 0 name: 山田 age: 38
id: 2 name: 山本 age: 45

[(addressbook,search)->addressbook]: CONSOLE OUTPUT END   =====================
```

## 制限
- セカンダリーインデックスのデータ型は下記の型だけ

```
idx64 - Primitive 64-bit unsigned integer key
idx128 - Primitive 128-bit unsigned integer key, or a 128-bit fixed-size lexicographical key
idx256 - 256-bit fixed-size lexicographical key
idx_double - Double precision floating point key
idx_long_double - Quadruple precision floating point key
name 特別に、アカウントもサポートされている
```

- セカンダリーインデックスは、最大 16 個までしか作成できません
- 検索時は、範囲指定しか出来ません

## まとめ
プライマリーキー以外に、セカンダリーインデックスを活用することで、他の条件でも検索出来ます。