美文网首页
EOS智能合约开发系列(七): 多索引table

EOS智能合约开发系列(七): 多索引table

作者: 鹏飞_3870 | 来源:发表于2018-08-19 18:14 被阅读0次

本文介绍eosio提供的一个可持久化状态的类:多索引table。可以理解为这是一组操作数据库的方法所组成的类。在写智能合约的时候,势必要持久化数据,要持久化数据就势必用到多索引table。

多索引table就是我们上一篇中遇到的这个类了:

eosio::multi_index

它提供了一组持久化数据的操作。它提供的功能类似于DB的功能,所以有人把它也称之为多索引DB。 不过,因为这个类更相当于db中的一个table(每次写入的数据都相当于的table中的一条record),所以我有时候会把这个类称之为table了。

为什么需要持久化存储

EOSIO合约是通过action来触发执行的。action在每次执行的时候都会有一个自己的上下文环境。 每个上下文会在每次action触发前,为其开辟一块干净的内存。所以在内存中的数据状态是无法在action之间传递的,也就是说,如果你在一个action中设置了某个变量,在另一个action中是无法获取到你对应的值的。在action之间传递状态的唯一方法,是将其持久化并从EOSIO数据库中检索它。

你可能会说,我修改类的字段应该是可以在action之间传递的吧?总不能全局变量也不行吧?是的,都不行。验证也很简单,比如我们修改一下我们之前写的hello合约的hello.cpp,像下面这样:

#include <eosiolib/eosio.hpp>
using namespace eosio;

int b = 2;

class hello : public eosio::contract {
  public:
      using contract::contract;

      /// @abi action 
      void hi( account_name user ) {
         a++;
         b++;
         require_auth( user );
         print( "Hi, ", name{user} , ";a=", a, ";b=", a);
      }

      void say( account_name user ) {
         a += 10;
         require_auth( user );
         print( "Say, ", name{user} , ";a=", a);
      }

  private: 
      int a = 0;
};

EOSIO_ABI( hello, (hi)(say) )

部署完之后,我们多次触发 hi和say,你会发现,每次输出的ab的结果都是一样的。

也就是说,持久化是在action之间传递状态的唯一方式。而table是我们目前持久化的唯一方法。

Multi-Index table 的特点

  • 它可以可以支持多索引。除了一个主索引外,还可以支持16个二级索引。每个索引的排序方式都是可以自定义的。
  • 支持迭代器,并且符合常规的C++迭代器模式。每个迭代器都有const版本,而且支持反向迭代。

如何创建table

  • 给你要持久化的对象定义一个或者struct,一个table只能存储一种类型的对象
  • 在该struct中,定义一个primary_key方法,它返回一个uint64_t类型的值作为该table的主索引键
  • 确定二级索引,最多可以达到16个,二级索引支持以下类型:
    • idx64 - 64位无符号整形
    • idx128 - 128位无符号整形,或者128bit的固定长度的字符串(按字典序)
    • idx256 - 256bit的固定长度的字符串(按字典序)
    • idx_double - 双精度浮点型
    • idx_long_double - 四精度浮点型

如何使用

  • 可以使用 emplace方法来插入记录,modify方法来修改记录,erase方法来删除记录。
  • 可以使用getfind方法,以及iterator查找记录。

举例说明

创建一个结构(要存储的数据结构类型)

      /// @abi table
      struct mystruct 
      {
         uint64_t     key; 
         uint64_t     secondid;
         std::string  name; 
         std::string  account; 

         uint64_t primary_key() const { return key; } // getter for primary key
         uint64_t by_id() const {return secondid; } // getter for additional key
      };

上面代码中,我们把想要存储的数据类型定义出来,并定义一些方法获取我们想要生成的索引的值。记住,我们必须要有一个primary_key方法,它返回我们表的主键。另外还可以定义最多16个其他的索引,每个所有都有一个getter方法对应,比如上面的by_id,就是一个secondidgetter

还有两点需要注意:

  • //@abi table 这个注释的作用是公开数据库中的数据。在有这个注释的情况下,任何人都可以获取此table的数据。如果没有该注释,除了代码可以访问该table外,任何人都无法通过命令行来获取table数据。我个人认为,这是EOS对table设计的一个缺憾,对于table中的数据获取权限没有一个灵活的权限设置。理应在有相应许可的情况下,允许某些许可通过命令行来获取table数据。
  • 上面这个类的名字必须小于12个字符,并且都是小写。

table的索引

typedef eosio::multi_index<N(mystruct), mystruct> datastore;

这个是C++的语法,typedef一个新的类型datastore,这样之后就可以用datastore声明变量,不需要eosio::multi_index<N(mystruct), mystruct>这么长了。第一个模板参数N(mystruct)代表table的名字,N的作用是可以把mystruct字符串转化为一个uint64_t类型的整数。第二个参数mystruct代表要存储的记录的类型。

如何指定二级索引呢?可以用indexed_by模板参数,像下面这样:

typedef eosio::multi_index<N(mystruct), mystruct, indexed_by<N(secondid), const_mem_fun<mystruct, uint64_t, &mystruct::by_id>>> datastore;

其中
indexed_by<N(secondid), const_mem_fun<mystruct, uint64_t, &mystruct::by_id>>
的意思是:

  • 索引的字段名被转化为一个整数,N(secondid)
  • const_mem_fun代表提取哪个索引。这里的mystruct代表要对哪个table进行排序,uint64_t代表依照哪种类型进行排序,&mystruct::by_id代表提取排序索引的方法。这里的uint64_t的类型应该与mystruct::by_id的返回类型保持一致。

如果有更多的索引,可以像下面的代码这样:

      /// @abi table
      struct mystruct 
      {
         uint64_t     key; 
         uint64_t     secondid;
         uint64_t           anotherid;
         std::string  name; 
         std::string  account; 

         uint64_t primary_key() const { return key; }
         uint64_t by_id() const {return secondid; }
         uint64_t by_anotherid() const {return anotherid; }
      };
      
typedef eosio::multi_index<N(mystruct), mystruct, indexed_by<N(secondid), const_mem_fun<mystruct, uint64_t, &mystruct::by_id>>, indexed_by<N(anotherid), const_mem_fun<mystruct, uint64_t, &mystruct::by_anotherid>>> datastore;

注意:

这个记录类型mystruct必须与table的名字保持一致。也就是第一个模板参数必须是这种形式的N(mystruct);如果记录类型是tablename,那么第一个模板参数必须是N(tablename),按此类推。
如果你想让你的table对于外界是可见的,也就是可以通过cleos get table获取,那么table的名字必须小于12个字符,并且都是小写的;而且在记录类型声明的时候,一定要加上//@abi table字样。

一个完整的例子

using namespace eosio;
//所有智能合约继承contract类
class truelove : public eosio::contract {
  public:
      // 初始化lovertable, scope和contract都是自己
      truelove(account_name s):eosio::contract(s), lovetable(s, s)
      {}


      // @abi action
      void transfer(account_name sender, account_name receiver, asset quanity, string memo) {
        
        lovetable.emplace( _self, [&]( auto& s ) {
          //获取下一个可用的主键值,这样可以实现id的自动增长
          s.id = lovetable.available_primary_key();
          s.sender = sender;
        });
        
      }

  private:
      // @abi table 
      struct lover{
        uint64_t        id; //auto increment id
        account_name    sender;
        
        account_name primary_key()const { return id; }
        
        EOSLIB_SERIALIZE( lover, (id)(amount)(sender)(txHash)(letter) )
      };
      
      typedef eosio::multi_index<N(lover), lover> records;
      records lovetable;
};

虽然table的内部实现很复杂,不过用起来还是蛮简单的。这个例子只用到了emplace这个方法,其他方法也都是类似的,我就不一一举例展示了。后面需要用到这些方法的时候,我再强调一下。
我们在后面的智能合约的学习中,会经常用table类。

今天就到这里了,明天见。
简介:不羁,一名程序员;专研EOS技术,玩转EOS智能合约开发。
微信公众号:know_it_well
知识星球地址:https://t.zsxq.com/QvbuzFM

相关文章

网友评论

      本文标题:EOS智能合约开发系列(七): 多索引table

      本文链接:https://www.haomeiwen.com/subject/hqkciftx.html