EOSIO Developer Portal 解説 データの永続化とインラインアクション 第2回

  • 2020.02.12
  • 2020.02.26
  • EOS
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第2回

はじめに


本記事ではEOS Developer Portalで紹介されているaddressbookコントラクトを用いて、EOSにおけるデータの永続化と、コントラクト内でアクションを実行するインラインアクションについて、全3回で解説します。

第2回では、Multi-Index APIで利用できるセカンダリーインデックスを用いて、永続化されたデータの検索や絞り込みをするための方法を解説します。

本記事は、過去にコンセンサス・ベイスが主宰していたオンラインサロンの記事です。記事は2017年~2018年にかけて執筆されたため、一部は、既に古くなっている可能性があります。あらかじめご了承ください。


関連する記事

EOSIO Developer Portal 解説 最短でHello World!
EOSIO Developer Portal 解説 独自トークンの発行 第1回
EOSIO Developer Portal 解説 独自トークンの発行 第2回
EOSIO Developer Portal 解説 独自トークンの発行 第3回
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第1回
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第2回
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第3回

セカンダリーインデックスの実装


EOSのMulti-Index APIでは、ユニークなプライマリーキーの他に、最大16個までのセカンダリーインデックスを定義できます。セカンダリーインデックスを用いることで、プライマリーキー以外の属性値による検索や絞り込みを、全データを参照することなく高速に実現できます。表1に、セカンダリーインデックスがサポートするキー型の一覧を示します。

表1. Multi-Index APIのセカンダリーインデックスがサポートするキー型

説明
idx6464 bit の符号なし整数キー
idx128128 bit の符号なし整数キー、または、128 bit の固定長辞書順キー
idx256256 bit の固定長辞書順キー
idx_double倍精度浮動小数点数キー
idx_long_double四倍精度浮動小数点数キー

セカンダリーインデックスを用いるには、インデックスを貼りたい属性からキー型のデータを返却する関数を定義します。

サンプルコントラクトへのセカンダリーインデックスの追加


第1回で用いたaddressbookのサンプルコントラクトを用いて、セカンダリーインデックスの使い方を紹介します。

addressbookコントラクトでは、personというstructを定義し、それらのデータを扱うpeopleテーブルを実装しました。今回は、このperson structにage属性を追加し、年齢を用いたデータの絞り込み機能をセカンダリーインデックスにより実装します。

addressbook.cppのperson struct定義の部分を、コード1に示すコードに修正します。このコードでは、person stcurtにuint64_t型のageという属性を追加し、その値を返すget_secondary_1関数を追加しています。

コード1. person structにage属性とget_secondary_1関数を追加
private:
  struct [[eosio::table]] person {
    name key;
    std::string first_name;
    std::string last_name;
    uint64_t age;
    std::string street;
    std::string city;
    std::string state;
  
    uint64_t primary_key() const { return key.value; }
    uint64_t get_secondary_1() const { return age; }
  
  };

続いて、address_index型のテーブル定義を、コード2に示すコードに修正します。修正点は、indexed_by<~> を追加している箇所です。ここで、byageという名前のインデックスを追加し、const_mem_funによって先程person structで定義したget_secondary_1をインデックスのキーの取得関数として設定しています。

コード2. address_indexテーブル型にセカンダリーインデックスを追加
typedef eosio::multi_index<"people"_n, person, indexed_by<"byage"_n, const_mem_fun>> address_index;

また、person structにage属性を追加したため、upsert関数の修正も必要です。コード3に、修正したupsert関数の定義を示します。upsert関数の引数にageを追加し、emplace, modifyそれぞれの場合にageの値の更新処理を追記しています。

コード3. upsert関数にage属性の扱いを追加
 [[eosio::action]]
  void upsert(name user, std::string first_name, std::string last_name, uint64_t age, std::string street, std::string city, std::string state) {
    require_auth( user );
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
    if( iterator == addresses.end() )
    {
      addresses.emplace(user, [&]( auto& row ) {
       row.key = user;
       row.first_name = first_name;
       row.last_name = last_name;
       row.age = age;
       row.street = street;
       row.city = city;
       row.state = state;
      });
    }
    else {
      std::string changes;
      addresses.modify(iterator, user, [&]( auto& row ) {
        row.key = user;
        row.first_name = first_name;
        row.last_name = last_name;
        row.age = age;
        row.street = street;
        row.city = city;
        row.state = state;
      });
    }
  }

それでは、修正したサンプルコントラクトをデプロイし、セカンダリーインデックスの動作を確認してみましょう。

サンプルコントラクトによる動作確認


修正した addressbook.cpp から、コマンド1を用いて前回と同様にwasmファイルとabiファイルを生成します。

コマンド1. コントラクトのコンパイル
$ eosio-cpp -o addressbook.wasm addressbook.cpp --abigen

コンパイルが完了したら、コマンド2を用いてコントラクトのデプロイをおこないます。

コマンド2. コントラクトのデプロイ
$ cleos set contract addressbook CONTRACTS_DIR/addressbook

無事にコントラクトのデプロイが完了したら、データを追加してセカンダリーインデックスの挙動を試してみます。

コマンド3を用いてaliceのデータを追加し、コマンド4を用いてbobのデータを追加します。aliceとbobのデータには、それぞれageとして9, 49を追加しています。

コマンド3. aliceのデータを追加
$ cleos push action addressbook upsert '["alice", "alice", "liddell", 9, "123 drink me way", "wonderland", "amsterdam"]' -p alice@active

コマンド4. bobのデータを追加
$ cleos push action addressbook upsert '["bob", "bob", "is a guy", 49, "doesnt exist", "somewhere", "someplace"]' -p bob@active

セカンダリーインデックスを用いたデータ抽出


テーブルに追加されたデータを確認するには、cleos get tableコマンドを使用します。表2に、インデックスを用いてtableのデータを絞り込む際によく使用するパラメタを紹介します。

表2. cleos get tableコマンドのパラメタ抜粋

パラメタ説明
-L,–lower TEXT絞り込みたいデータのキーの値の下限値を指定する。
-U,–upper TEXT絞り込みたいデータのキーの値の上限値を指定する。
–index TEXT使用したいインデックスの番号を指定する。1であればプライマリーキー、2であればセカンダリーキー、3であれば3番目のキーを表す。
–key-type TEXTインデックスのキー型を指定する。(i64, i128, i256, float64, float128, ripemd160, sha256など)
-r,–reverseデータを逆順でイテレーションする。

具体的に、addressbookコントラクトに実装したbyageインデックスを用いたデータの絞り込みを実践してみましょう。コマンド5は、byageインデックスを用いてageが10以下のデータに絞り込むクエリ例を示します。byageインデックスは、uint64型でセカンダリーインデックスの最初に定義しているため、key-typeにはi64、indexには2を指定します。上限値として、upperには10を指定します。

このクエリにより、ageが49のbobのデータはフィルタされ、aliceのデータのみが表示されます。

コマンド5. byageインデックスを用いてageが10以下のデータに絞り込む
$ cleos get table addressbook addressbook people --upper 10 \
--key-type i64 \
--index 2

{
  "rows": [{
      "key": "alice",
      "first_name": "alice",
      "last_name": "liddell",
      "age": 9,
      "street": "123 drink me way",
      "city": "wonderland",
      "state": "amsterdam"
    }
  ],
  "more": false
}

コマンド6のとおり、upperを50に変更すると、ageが49のbobのデータが表示されることが分かります。aliceのデータもageが50以下である条件を満たしているので、同時に表示されています。

コマンド6. byageインデックスを用いてageが50以下のデータに絞り込む
$ cleos get table addressbook addressbook people --upper 50 \
--key-type i64 \
--index 2

{
  "rows": [{
      "key": "alice",
      "first_name": "alice",
      "last_name": "liddell",
      "age": 9,
      "street": "123 drink me way",
      "city": "wonderland",
      "state": "amsterdam"
    },{
      "key": "bob",
      "first_name": "bob",
      "last_name": "is a loser",
      "age": 49,
      "street": "doesnt exist",
      "city": "somewhere",
      "state": "someplace"
    }
  ],
  "more": false
}

もし、セカンダリーインデックスをコントラクトのコード内で参照したい場合は、multi_indexのget_indexメソッドを用います。コード4に、コントラクト内からbyageインデックスを用いてデータを検索するfindByAge関数の実装例を示します。

コード4. セカンダリーインデックスを用いたfindByAge関数の実装例
[[eosio::action]]
void findByAge(name user, uint64_t age) {
 require_auth(user); 
 
 address_index addresses(_self, _code.value);
 
 auto age_index = addresses.get_index<"byage"_n>();
 auto itr = age_index.find(age);
 eosio_assert(itr != age_index.end(), "Yes, we have someone with that age");
}

本記事では、セカンダリーインデックスを用いて永続化されたデータを効率的に取得する方法について紹介しました。複数のインデックスを用いることにより、さまざまな条件でデータを検索・絞り込みできます。

最後に、修正したサンプルコントラクトのコード全体をコード5に示します。

コード5. セカンダリーインデックスを追加したaddressbook.cpp
#include 
#include 

using namespace eosio;

class [[eosio::contract]] addressbook : public eosio::contract {

public:
  using contract::contract;
  
  addressbook(name receiver, name code,  datastream ds): contract(receiver, code, ds) {}

  [[eosio::action]]
  void upsert(name user, std::string first_name, std::string last_name, uint64_t age, std::string street, std::string city, std::string state) {
    require_auth( user );
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
    if( iterator == addresses.end() )
    {
      addresses.emplace(user, [&]( auto& row ) {
       row.key = user;
       row.first_name = first_name;
       row.last_name = last_name;
       row.age = age;
       row.street = street;
       row.city = city;
       row.state = state;
      });
    }
    else {
      std::string changes;
      addresses.modify(iterator, user, [&]( auto& row ) {
        row.key = user;
        row.first_name = first_name;
        row.last_name = last_name;
        row.age = age;
        row.street = street;
        row.city = city;
        row.state = state;
      });
    }
  }

  [[eosio::action]]
  void erase(name user) {
    require_auth(user);

    address_index addresses(_self, _code.value);

    auto iterator = addresses.find(user.value);
    eosio_assert(iterator != addresses.end(), "Record does not exist");
    addresses.erase(iterator);
  }

private:
  struct [[eosio::table]] person {
    name key;
    std::string first_name;
    std::string last_name;
    uint64_t age;
    std::string street;
    std::string city;
    std::string state;
  
    uint64_t primary_key() const { return key.value; }
    uint64_t get_secondary_1() const { return age; }
  
  };

  typedef eosio::multi_index<"people"_n, person, indexed_by<"byage"_n, const_mem_fun>> address_index;
  
};

EOSIO_DISPATCH( addressbook, (upsert)(erase))

次回は、コントラクト内でアクションを実行するインラインアクションについて解説します。

参考文献:

  • EOSIO Developer Portal – 2.5 Secondary Indices

https://developers.eos.io/eosio-home/docs/secondary-indices
  • EOSIO Developer Portal – Multi-Index DB API

https://developers.eos.io/eosio-cpp/v1.3.1/docs/db-api
     

免責事項

本記事に掲載されている記事の内容につきましては、正しい情報を提供することに務めてはおりますが、提供している記事の内容及び参考資料からいかなる損失や損害などの被害が発生したとしても、弊社では責任を負いかねます。実施される際には、法律事務所にご相談ください。

技術・サービス・実装方法等のレビュー、その他解説・分析・意見につきましてはblock-chani.jp運営者の個人的見解です。正確性・正当性を保証するものではありません。本記事掲載の記事内容のご利用は読者様個人の判断により自己責任でお願いいたします。

     

コンセンサス・ベイス(株)とブロックチェーン事業を行なってみませんか?

当サイトを運営するコンセンサス・ベイス株式会社は、2015年設立の国内で最も古いブロックチェーン専門企業です。これまでに、大手企業の顧客を中心に、日本トップクラスのブロックチェーンの開発・コンサルティング実績があります。

ブロックチェーンに関わるビジネスコンサル・システム開発・教育・講演などご希望でしたら、お気軽にお問い合わせください。

     
     

ブロックチェーン学習に最適の書籍の紹介

図解即戦力 ブロックチェーンのしくみと開発がこれ1冊でしっかりわかる教科書

ブロックチェーン イーサリアムへの入り口 第二版 (ブロックチェーン技術書籍)

本書は、ブロックチェーン技術に興味を持ったエンジニアや、その仕組みを学び、自分の仕事に活かしたいビジネスパーソンを対象にして、ブロックチェーンのコア技術とネットワーク維持の仕組みを平易な言葉で解説しています。この本を読んだうえで、実際にコードを書くような専門書、ブロックチェーンビジネスの解説書を読むことで、理解度が飛躍的に高まるでしょう。(はじめにより)

EOSカテゴリの最新記事