本文介绍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,你会发现,每次输出的a
和b
的结果都是一样的。
也就是说,持久化是在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
方法来删除记录。 - 可以使用
get
和find
方法,以及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
,就是一个secondid
的getter
。
还有两点需要注意:
- //@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
网友评论