美文网首页
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