BSV Planaria框架技术总结二 Bitquery
此文是变形虫技术总结的第二篇,阅读此文之前建议先阅读关于变形虫的前两篇文章。
前面的文章说过变形虫是一个持久层框架,通过planaria组件爬取区块链上的交易数据,提取并加工所需的数据后将数据存储到MongoDB中,然后由planarium对外提供接口给应用程序来调取数据。在搭建好数据库之后,我们下一步关注的重点就是如何对数据库进行读写。前文已经介绍过,变形虫与传统数据库最大的区别就是读写分离,不能直接写数据库,只能通过客户端发起链上交易来修改状态机,因此向变形虫写数据本质上就是构造交易,这将在下一篇文章中总结。本文的重点就是如何高效优雅地从变形虫中读取数据。
Bitquery简介
planaria使用一套简单高效的查询语言,称为Bitquery,类似于sql语言,可以将变形虫中的数据进行各种图灵完备的组合和处理,输出各种形态的数据。由于变形虫的开发目的是一个可以满足一切基于比特币链上数据需求的持久层框架,因此必须具备一种专为链上交易数据服务的查询语言,bitquery也就因此而产生。
bitquery查询语句本质上是一个json对象,之所以是这种形态是为了适应MongoDB的查询。这里有必要提及一下,变形虫框架为何内置数据库采用的是MongoDB而不是类似mysql的关系型数据库。照理说,链上交易数据是充分结构化的,各个交易之间,地址之间有很强的关联性,查询交易更适合使用关系型数据库,这样连表操作更为方便,性能也更好。但是对于变形虫而言,它关注的重点并不是交易本身,而是交易上携带的千变万化,各行各业的杂乱无章的非标准数据,这些数据没有统一的范式,也没有统一的关联性。在这种情形下,Nosql的优势就会凸显出来,将一个个非标准数据看做一个个”文件“,不强制要求文件内容的格式,更加符合变形虫的需求目标。我有一种设想,以后的变形虫甚至可以在内部同时集成关系型和非关系型两种数据库,关系型数据库专门处理关联性高的交易数据,提升关联连表查询效率,而非关系型数据库处理各种应用的非标准数据,提升拓展性和兼容性。两种内置的数据库组件各司其职,将变形虫的性能发挥到极致。
bitquery示例如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PipQL7QC-1594465117826)(https://docs.planaria.network/bitquery.png)]
bitquery基于两种非常强大的技术,MongoDB Query Language(mongoDB 查询语言)和JQ(一个基于栈操作的图灵完备的json处理语句)。查询语句如上图所示分为3部分,第一部分是协议的版本号,目前是3,第二部分是基于MongoDB的查询语句,筛选出数据,第三部分是返回结果处理语句,对第二部筛选出来的结果进行整理和包装,处理成应用程序需要的样式。
第二部分查询语句主要基于MongoDB的查询语句,但是与原生的MongoDB查询略有不同,针对Planaria做了一定的适配,选取了部分查询功能,使用json对象的各个成员来标记搜索条件。
第三部分处理搜索结果,第二步返回的结果是一个json,可能包含我们不需要的数据,或者不是我们所希望的数据格式,需要将其整理成我们需要的格式,这时候就需要用到JQ,变形虫内置JQ的处理组件,可以直接帮我们把数据在服务器端就处理好,不需要客户端自行进行数据的处理。
query字段(q)可以包含下列成员(后文会依次详细说明):
- find:同MongoDB的find条件语句。
- aggregate:对应MongoDB的聚合语句。
- project:对应mongoDB的project操作符
- sort:对应MongoDB的排序操作符
- limit:对应MongoDB的个数限制操作
- skip:对应跳过操作,通过limit和skip可以实现翻页
- db:选择db,目前有c(已确认交易库)和u(未确认交易库)
response字段(r)包含一个f成员,就是后处理function,里面是一个JQ表达式,用于处理query返回的结果。
Genesis数据储存格式
目前我运行的是一个全量的Genesis数据库,因此在这里详细说明一下返回值的字段含义,如果不知道这些字段的含义,也就无从写query语句和Jq表达式了。没有节点的同学可以直接访问Unwriter的公开endpoint:
打开后会看到默认的查询语句如下:
{
"v": 3,
"q": {
"find": {},
"limit": 10
}
}
这语句的意思是取最近的十条记录,没有其他条件。然后我们会看到下方有一个巨大的表格,里面填充了各种数据,并且有一些奇怪的表头名称。为了方便描述,我们修改一下查询语句,因为默认查询语句经常会查出一些巨大的交易数据体(二进制图片等数据),在本文有限的空间下没办法展示出来,我们采用如下的查询:
{
"v": 3,
"q": {
"find": { "out.h1": "6d02" },
"limit": 1
}
}
转换成get请求如下:
GET /q/1FnauZ9aUH2Bex6JzdcV4eNX7oLSSEbxtN/ewogICJ2IjogMywKICAicSI6IHsKICAgICJmaW5kIjogeyAib3V0LmgxIjogIjZkMDIiIH0sCiAgICAibGltaXQiOiAxCiAgfQp9 HTTP/1.1
Host: genesis.bitdb.network
key: 1KWqy2WbNpEPC7hwvfJbvXy2vekS2LwGim
Cache-Control: no-cache
Postman-Token: 6445346e-1444-8450-4cb5-088197fe921e
这个语句的意思是取出最近的一条memo.sv(一个bsv链上微博)的交易数据,原理我们后文介绍。
可以看到这个查询语句返回了一系列数据,填充在表格中,我们将其json源码(从postman直接请求)贴下来,如下:
{
"u": [
],
"c": [
{
"_id": "5ccfe65cd7faee16774712b0",
"tx": {
"h": "15ec948360c54b0864fed10addc9370e7acf70624bae3e46f0db16ba33728e60"
},
"in": [
{
"i": 0,
"b0": "MEQCIGbAWT8cYm+sHj+/kJI6Ir7HznAZl+uI/Op1w119nyBWAiAmaWlVHAw7F1lPjkremq5YfDyuGr2bMQC7Ft2/uUwor0E=",
"b1": "A+8emT7UM3oOjjqstBg4sjn2I4wnIPPwY7ARQ6VkrGue",
"str": "3044022066c0593f1c626fac1e3fbf90923a22bec7ce701997eb88fcea75c35d7d9f20560220266969551c0c3b17594f8e4ade9aae587c3cae1abd9b3100bb16ddbfb94c28af41 03ef1e993ed4337a0e8e3aacb41838b239f6238c2720f3f063b01143a564ac6b9e",
"e": {
"h": "175553b769f70d93b6c3961e8bdefe20ab274f6bbb8bbbcbfbbe205fb347a610",
"i": 0,
"a": "1Gt8ZMCxkGbkTQfMuPnUZWx8y9ydWfuQA1"
},
"h0": "3044022066c0593f1c626fac1e3fbf90923a22bec7ce701997eb88fcea75c35d7d9f2056022026