EOS 資料庫開發實戰
上次的文章詳細講解了 EOS 資料庫的架構,本文將以官方示例為基礎,詳解 EOS 資料庫的開發實戰。
基本步驟
在智慧合約裡與 EOS 資料庫互動,首先要定義儲存的資料:
- 定義物件:具體就是定義一個 C++ 類或者 C++ 結構體,資料表就由一個個物件組成。
- 定義主鍵:在剛才的類/結構體中,定義一個
const
型別的成員函式primary_key()
,返回值必須為uint64_t
型別,返回值即為主鍵。 - 定義索引:EOS 資料表不光可以按照主鍵搜尋資料,還可以定義多達 16 種索引。而且索引鍵(Key)不止支援64位無符號整數,還支援 128、256位整數以及雙精度、四精度浮點數。
- 為每個索引定義 鍵提取器(key extractor)。
儲存資料定義好之後,就可以與資料庫互動了:
- 建立資料表:例項化
multi_index
,建立資料表。 - 增刪資料:使用
emplace
方法在表中新增資料;使用erace
方法刪除資料。 - 修改資料:使用
modify
方法修改資料。 - 查詢資料:使用
get
、find
方法和其他迭代器操作查詢資料。
需求分析
我們參考 EOS 的官方示例,建立一個“汽車修理店”智慧合約所需要的資料庫。資料庫服務的物件是維修技師和車主。每次車輛維修保養後,維修技師都可以新增本次維修服務的資訊,可以更科學地管理每位客戶的車輛維修保養服務。而且維修技師和車主都可以更新車輛目前的里程,以便技師確定車輛是否應該保養。我們需要一個資料表:維修資料表(service
Table)。
建立資料物件
維修資料表中,每一條資料物件就是一次車輛維修保養的資料,包含以下成員:
- 主鍵:因為資料表主鍵必須是唯一的,所以無法用顧客的賬戶名作為主鍵(同一個顧客有多條維修記錄)。這裡我們讓系統自動生成主鍵。
- 顧客賬戶:儲存每次維修服務的顧客賬戶名。
- 維修日期:每次維修服務的日期。
- 車輛里程:每次服務時,車輛的里程資訊。
我們還想方便的查詢每個顧客的維修記錄,所以需要一個以顧客賬戶名為鍵(Key)的索引。
這樣我們就得到了 service_rec
結構體:
struct service_rec {
uint64_t pkey; // 主鍵
account_name customer; // 顧客賬戶
uint32_t service_date; // 維修日期
uint32_t odometer; // 車輛里程
//設定主鍵
auto primary_key()const { return pkey; }
//設定索引
account_name get_customer()const { return customer; }
//SERIALIZE 巨集可以幫助提高編譯速度
EOSLIB_SERIALIZE( service_rec, (pkey)(customer)(service_date)(odometer) )
};
複製程式碼
建立資料表
下面就可以建立資料表了,首先,multi_index
是個模板類:(對 C++ 模板不熟悉的可以百度一下)
eosio::multi_index <uint64_t TableName, typename T, typename... Indices>
複製程式碼
我們需要填入以下multi_index
的模板引數:
TableName
為資料表名稱,12字元以內,只能使用小寫字母,數字1-5,小數點“.”。T
為資料物件型別,這裡就是我們定義的service_rec
結構體。Indices
為索引列表,最多十六個。為了降低開發難度,官方推薦使用const_mem_fun
模板,大家可以模仿官方的做法:
按照需求,我們這樣設定multi_index
的模板引數:
using service_table_type = multi_index<service/*<-資料表名稱*/, service_rec,/*<-資料物件型別*/
/*設定索引->*/indexed_by< N(bycustomer), const_mem_fun<service_rec, account_name, &service_rec::get_customer> >
>;
複製程式碼
這裡並沒有例項化multi_index
,只是將填入相應模板引數的multi_index
設定了一個別名:service_table_type
。依然,對這裡的做法不熟悉的可以看一下 C++ 模板類以及 C++ 的 using 關鍵字。
下面我們例項化multi_index
,建構函式需要兩個引數:
multi_index( uint64_t code, uint64_t scope )
複製程式碼
其中,code
為資料表的擁有者,scope
為資料表的細分名稱。這裡有兩種理解,一種理解是不同的 scope
就是不同的資料表,也就是說,在同一個賬戶下,存在著TableName
相同的多個資料表,他們的scope
互不相同;另一種理解:scope
表示了同一個資料表的不同部分,互相獨立讀寫。這兩種理解的結果是一樣的,就是唯一確定一個資料表需要三個引數:TableName
,code
,scope
。
例項化multi_index
:
service_table_type service_table( current_receiver(), mechanic );
複製程式碼
上面的code
= current_receiver()
,表示當前的智慧合約,即“汽車維修店合約”。如果這裡的code
為其他合約,那麼說明這個multi_index
指向了其他賬戶名下的資料表,在本合約中就只能進行讀取操作了。scope
= mechanic
表明例項化的這個multi_index
指向了細分名稱為mechanic
(以維修技師賬戶命名)的資料表。
我們所建立的資料表結構如下圖所示。
運算元據
一般資料庫的基本操作是增、刪、改、查,EOS 資料庫當然也具有這些功能。
新增資料
新增資料需要用到multi_index
的emplace
方法:
const_iterator emplace( unit64_t payer, Lambda&& constructor )
複製程式碼
其中的payer
引數位儲存空間支付賬戶,也就是由誰來提供新加入的這個資料物件的儲存空間,這裡填入維修技師mechanic
賬戶。constructor
是個 Lambda 表示式,也叫匿名函式,是向emplace
方法傳入了一個建構函式,用來構造這個新的資料物件。
service_table.emplace(mechanic,/*<-儲存空間支付賬戶*/ [&]( auto& s_rec )/*<-匿名函式*/ {
s_rec.pkey = service_table.available_primary_key(); /*<-系統生成可用主鍵*/ //匿名函式體
s_rec.customer = eosio::chain::string_to_name(customer_name); //匿名函式體
s_rec.service_date = service_date; //匿名函式體
s_rec.odometer = odometer; //匿名函式體
});
複製程式碼
其中的customer_name
、service_date
、odometer
要在實際開發時使用有意義的變數。
查詢資料
由於service_table
資料表的主鍵是沒有意義的,所以我們需要使用bycustomer
索引來根據顧客賬戶名(customer
)查詢資料。
auto customer_index = service_table.template get_index<N(bycustomer)>();
複製程式碼
這樣我們就得到了bycustomer
索引,我們可以使用索引的find
方法來按照索引查詢特定customer
的資料物件。
//建立要查詢的賬戶,注意這裡的customer_name要使用有意義的字串
account_name customer_acct = eosio::chain::string_to_name(customer_name);
//使用`find`方法查詢資料,使cust_itr(迭代器)指向所需資料
auto cust_itr = customer_index.find(customer_acct);
複製程式碼
如果沒有查詢到,cust_itr
(迭代器)就是service_table.end()
,也就是搜尋到最後也沒有找到對應的資料。如果查詢成功,cust_itr
(迭代器)就會指向所需的資料物件。
之後,可以使用下面的程式碼可以遍歷資料表中所有我們所需的條目。(因為顧客賬戶名不是唯一的,用find
方法會找到符合條件的第一條資料)
while (cust_itr != service_table.end() /*<-判斷迭代器位置*/&& cust_itr->customer == customer_acct/*<-判斷資料是否符合*/) {
// 業務邏輯,對資料進行處理
cust_itr++;//迭代器自增,指向下一條資料
}
複製程式碼
修改資料
在迭代器指向資料後,可以對資料進行修改,使用modify
方法:
service_table.modify(cust_itr,/*<-迭代器*/, mechanic, /*<-儲存空間支付賬戶*/ [&]( auto& s_rec )/*<-匿名函式*/ {
s_rec.customer = new_customer; //匿名函式體
s_rec.service_date = new_service_date; //匿名函式體
s_rec.odometer = new_odometer; //匿名函式體
});
複製程式碼
匿名函式中的new_customer
、new_service_date
、new_odometer
請使用有意義的變數。也可以只修改其中部分變數。
刪除資料
在迭代器指向資料後,可以對資料進行刪除,使用erase
方法:
service_table.erase( cust_itr/*<-迭代器*/ );
複製程式碼
至此,帶領大家了初步解了 EOS 資料庫開發的思路與方法,EOS 資料庫還有很多 API 可以供智慧合約使用,大家可以查閱官方 Wiki: github.com/EOSIO/eos/w…
相關文章和視訊推薦
圓方圓學院彙集大批區塊鏈名師,打造精品的區塊鏈技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。 公開課地址:ke.qq.com/course/3451…