multi_index是EOS的数据库接口,通过它可以实现对数据的增删改查,就是下面这些函数:
- emplace(增)
- erase(删)
- modify(改)
- get/find(查)
今天主要讲emplace函数
emplace
添加一个新对象(row)到表中
const_iterator emplace( unit64_t payer, Lambda&& constructor )
- 参数
payer:为新对象使用的存储付费的账户;
constructor:lambda函数,可以让新创建的对象就地初始化。
- 返回值
返回一个当前新创建的对象的主键迭代器
- 前置条件
payer是被当前Action授权的有效账户,允许为使用存储付费
- 操作结果
带有唯一主键的新对象在multi-index表中被创建;
这个对象会被序列化,然后写入表中;
如果表不存在,则创建表。
二级索引被更新,用以引用新添加的对象;
如果二级索引表不存在,则创建它们。
payer为创建新对象所使用的存储付费;
如果multi-index表和二级索引表需要被创建,则payer为表的创建付费。
- 异常
当前接收者(multi_index的code参数)不是表的拥有者时,抛出异常。
emplace的使用
这里列一个之前文章的例子:
//添加联系人
//@abi action
void add(const account_name account, const string& name, uint64_t phone) {
//获取授权,如果没有授权,Action调用会中止,事务会回滚
require_auth(account);
//address_index是自己定义的eosio::multi_index
//实例化address数据表(multi_index),参数用于建立对表的访问权限
address_index addresses(_self, _self);
//multi_index的find函数通过主键(primary_key)查询数据,返回迭代器itr
//auto关键字会自动匹配类型
auto itr = addresses.find(account);
//如果判断条件不成立,则终止执行并打印错误信息
eosio_assert(itr == addresses.end(), "Address for account already exists");
//添加数据
//使用存储需要付费,第一个参数account是付费的账户
addresses.emplace(account, [&](auto& address){
address.account = account;
address.name = name;
address.phone = phone;
});
}
更详细的内容,参考这篇文章:使用数据库的智能合约实例。
emplace源码
//函数模板
template<typename Lambda>
const_iterator emplace( uint64_t payer, Lambda&& constructor ) {
using namespace _multi_index_detail;
//合约的拥有者才能添加数据
eosio_assert( _code == current_receiver(), "cannot create objects in table of another contract" );
//make_unique是C++14新特性,这里可以理解为创建对象
auto itm = std::make_unique<item>( this, [&]( auto& i ){
T& obj = static_cast<T&>(i);
constructor( obj );
size_t size = pack_size( obj );
void* buffer = max_stack_buffer_size < size ? malloc(size) : alloca(size);
datastream<char*> ds( (char*)buffer, size );
ds << obj;
//获取主键
auto pk = obj.primary_key();
//关键代码,存储数据
i.__primary_itr = db_store_i64( _scope, TableName, payer, pk, buffer, size );
if ( max_stack_buffer_size < size ) {
free(buffer);
}
if( pk >= _next_primary_key )
_next_primary_key = (pk >= no_available_primary_key) ? no_available_primary_key : (pk + 1);
hana::for_each( _indices, [&]( auto& idx ) {
typedef typename decltype(+hana::at_c<0>(idx))::type index_type;
i.__iters[index_type::number()] = secondary_index_db_functions<typename index_type::secondary_key_type>::db_idx_store( _scope, index_type::name(), payer, obj.primary_key(), index_type::extract_secondary_key(obj) );
});
});
const item* ptr = itm.get();
auto pk = itm->primary_key();
auto pitr = itm->__primary_itr;
_items_vector.emplace_back( std::move(itm), pk, pitr );
return {this, ptr};
}
关键代码在这一行:
i.__primary_itr = db_store_i64( _scope, TableName, payer, pk, buffer, size );
这是一个定义在eosiolib/db.h中的函数:
int32_t db_store_i64(account_name scope, table_name table, account_name payer, uint64_t id, const void* data, uint32_t len);
它的实现同样在wasm_interface.cpp中,定义在database_api类:
它调用了apply_context::db_store_i64函数,这个函数是一系列对数据库的操作,源码如下:
int apply_context::db_store_i64( uint64_t code, uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ) {
//通过表名在数据库中查找表,如果不存在,则创建
const auto& tab = find_or_create_table( code, scope, table, payer );
auto tableid = tab.id;
FC_ASSERT( payer != account_name(), "must specify a valid account to pay for new record" );
//创建数据
const auto& obj = db.create<key_value_object>( [&]( auto& o ) {
o.t_id = tableid;
o.primary_key = id;
o.value.resize( buffer_size );
o.payer = payer;
memcpy( o.value.data(), buffer, buffer_size );
});
//修改count
db.modify( tab, [&]( auto& t ) {
++t.count;
});
int64_t billable_size = (int64_t)(buffer_size + config::billable_size_v<key_value_object>);
//更新数据库使用状态
update_db_usage( payer, billable_size);
keyval_cache.cache_table( tab );
return keyval_cache.add( obj );
}
特别鸣谢:@松果 的支持