CouchDB
Apache CouchDB 是一个面向文档的数据库管理系统。它提供以 JSON 作为数据格式的 REST 接口来对其进行操作,并可以通过视图来操纵文档的组织和呈现。 CouchDB 是 Apache 基金会的顶级开源项目。
介绍
couchDB的字段只有三个:文档ID、文档版本号和内容。内容字段可以看到是一个text类型的文本,里面可以随意定义数据,而不用关注数据类型,但数据必须以json的形式表示并存放。例如一个表述用户的文档可以表示为:[_id:1001, _rev:1-32443289, {‘name’:’wentrue’, ‘location’:’beijing’}]。couchDB把所有的属性忽略schema都统一放在一个字段里,那么当有两个程序同时修改这个字段时,就有可能造成先写入的数据被后写入的数据覆盖的现象,导致数据不一致。couchDB利用版本号来解决这个问题,当有程序需要修改一条数据时,它必须先把这个数据的版本号读出来,写入的时候,连版本号一并写入,此时couchDB会检查该版本号是否与原数据的一致,如果一致则写入,如果不一致,则说明数据在该程序读出后已经被修改过,couchDB会写入不成功,返回一个冲突的信号,待客户端程序处理。
couchDB的底层是erlang语言,以RESTful API的格式提供服务,couchDB所有的读写能力都可以通过简单的调用它的HTTP请求来实现。正因为采用那么一种统一且简洁的服务接口,可以很方便地开发各种语言的客户端,以方便不同程序员的使用。
couchDB的主要特点在于易用性及并发性。couchDB的底层是一个B-tree的存储结构,为提高效率,所有的数据的插入或更新都是直接在树的叶子节点添加,不删除旧节点,通过版本号来确定最新的数据--版本号还能用来解决并发写的冲突。所以数据文件会越来越大,可以在适当地时间运行compact过程或replication过程,会删除旧文件,使得数据文件得到压缩。
couchDB的属性可以灵活增删,要增加一个新属性只需要往数据字段多写入一个属性即可。couchDB以json格式存储数据,返回的数据也默认为json格式,这对于前端的javascript处理是非常方便的。
区块链中的使用
在Fabric链码中,不管是通过 GetState()
、GetPrivateData
或GetStateByPartialCompositeKey
等来从账本中获取数据时,它们都是通过账本数据的 Key
来确定数据。在某些场景下,可能会需要通过账本数据中的 Value
值来筛选数据(如获取性别为男的所有数据)。当然,使用组合键也能达到这种需求,但组合键的创建和存储都比较麻烦(需要所有的组合键都需要被创建和存储到账本数据中,本质上组合键也是通过Key来筛选数据)。
富查询
为了能够简单的通过Value值来筛选数据,在链码中可以使用CouchDB的富查询功能。 shim.ChaincodeStubInterface
实例所提供的的 GetQueryResult
方法可实现富查询功能。该方法需传入一个Json对象,key 为 selector
,value为待筛选的属性值。如 {"selector":{"sex":"男"},{"province":"浙江"}}
func GetCommodityByName(stub shim.ChaincodeStubInterface, args []string) pb.Response {
//todo
if len(args) != 1 {
return shim.Error("参数非法!参数顺序:name")
}
// `{"selector":{"属性名":"属性值"}}`
query := fmt.Sprintf(`{"selector":{"name":"%v"}}`,args[0])
var commodities []Commodity
iterator,_ := stub.GetQueryResult(query)
defer iterator.Close()
for iterator.HasNext() {
kv,_ := iterator.Next()
var commodity Commodity
_ = json.Unmarshal(kv.Value,&commodity)
fmt.Println(commodity)
commodities = append(commodities,commodity)
}
data,_ := json.Marshal(commodities)
return shim.Success(data)
}
索引
智能合约代码中业务如果涉及到couchdb的富查询,例如根据供应商去查询一些相关的信息,如果没有建立索引的话,在查询中会慢的很痛苦,而且数据量大的情况下会出现莫名的error。
couchDB的索引创建很简单,只需在链码的同级目录下建立文件夹META-INF/statedb/couchdb/indexes/,并添加一个json文件:
{
"index": {
"fields": [
"属性名"
]
},
"ddoc": "自定义名称",
"name": "自定义名称",
"type": "json"
}
Fabric-samples项目中的链码包 marbles02
所使用的索引文件为:
{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"}
查询格式
CouchDB的查询语句是一个JSON格式的字符串,示例如下:
{
"selector": {
"year": {"$gt": 2010}
},
"fields": ["_id", "_rev", "year", "title"],
"sort": [{"year": "asc"}],
"limit": 2,
"skip": 0,
"execution_stats": true
}
selector
- 选择器 参数为Json格式的数据筛选条件,类似于SQL中的WHERE
必填limit
- 条数 参数为整数,查询返回的最大条数 可选skip
- 跳过 参数为整数,跳过前面的指定条 可选sort
- 排序 参数为JSON数组,结果排序 可选fields
- 过滤 参数为数组,过滤掉指定字段 可选
更多CouchDB查询资料可查看 CouchDB 官网文档 。
选择器
组合字符列表:
-
$and
- 数组参数{ $and: [ { <表达式1> },{ <表达式2> }, ... ] }
同时满足数组中的所有表达式 -
$or
- 数组参数{ $or: [ { <表达式1> }, { <表达式2> }, ... ] }
满足数组中任意表达式 -
$not
- 单一参数{ $not: [ { <表达式1> }, { <表达式2> }, ... } ] }
不满足数组中的任意表达式 -
$nor
- 数组参数{ $nor: [ { <表达式1> }, { <表达式2> }, ... } ] }
同时不满足数组中的全部表达式 -
$all
- 数组参数(操作对象为数组){ <字段名>: { $all: [ <值1>, <值2>, ... ] } }
选择对象应该为数组字段,数据中的该数组字段包含所有数组中的表达式。匹配非数组类型的字段时与$eq
一致 -
$elemMatch
- 单一参数(操作对象为数组){ <字段名>: { $elemMatch: { <子字段名>: <表达式>, ... } } }
选择对象应该为数组字段,字段中至少有一条数据包含了数组中的表达式。
条件参数列表
平等运算符
-
$lt
- 任意JSON{ <字段名>: { $lt: <值> } }
字段小于给定值时less than
-
$lte
- 任意JSON{ <字段名>: { $lte: <值> } }
字段小于等于给定值时less than equal
-
$eq
- 任意JSON{ <字段名>: { $eq: <值> } }
字段等于给定值时equal
-
$ne
- 任意JSON{ <字段名>: { $ne: <值> } }
字段小于给定值时not equal
-
$gte
- 任意JSON{ <字段名>: { $lt: <值> } }
字段大于等于给定值时greater than equal
-
$gt
- 任意JSON{ <字段名>: { $lt: <值> } }
字段大于给定值时greater than
对象相关运算符
-
$exists
- Boolean{ <字段名>: { $exists: <值> } }
字段是否存在 -
$type
- 字符串{ <字段名>: { $type: <值> } }
字段类型null
,boolean
,number
,array
,object
,string
数组相关运算符
-
$in
- JSON数组(操作对象为数组){ <字段名>: { $in: [ { <值1> },{ <值2> }, ... ] } }
数组字段中包含了某一个给定值 -
$nin
- JSON数组(操作对象为数组) -
$nin
- JSON数组{ <字段名>: { $nin: [ { <值1> },{ <值2> }, ... ] } }
数组字段中不包含了某一个给定值 -
$size
- 整数(操作对象为数组){ <字段名>: { $size: <值> } }
数组长度等于给定值
其它运算符
-
$mod
- [Divisor, Remainder] (操作对象只能是整数) -
{ <字段名>: { $mod: [ Divisor,Remainder] } }
字段 % Divisor == Remainder -
$regex
- 字符串{ <字段名>: { $regex: <正则表达式> } }
字段值匹配正则表达式