Fabric链码开发——富查询

文章介绍了HyperledgerFabric链码中进行富查询和分页查询的方法,强调了CouchDB作为数据库在支持富查询中的重要性。通过`GetQueryResult`和`GetQueryResultWithPagination`函数实现对数据的复杂查询和分页,同时讨论了索引创建和`docType`字段的作用。示例代码展示了如何在智能合约中使用这些功能。
摘要由CSDN通过智能技术生成

Fabric链码开发——富查询

常见的链码使用PutState和GetState就可以进行简单的增加和查找了,再不济就加入DelState。因为fabric底层都是KV数据库,所以这三个接口就可以进行增删改查的基本需求了。但是,这都是单个数据的操作如果要进行功能较为强大的数据操作,只使用这个是完全不够的。查询时一切数据操作的开始,通过官方文档,找到了这两个支持富查询的函数。(富查询只支持在CouchDB 中使用)

源代码

func (s *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error)
func (s *ChaincodeStub) GetQueryResultWithPagination(query string, pageSize int32,
	bookmark string) (StateQueryIteratorInterface, *pb.QueryResponseMetadata, error)

第一个是一个简单的富查询函数,第二个是一个支持富查询的分页查询函数

// GetQueryResult documentation can be found in interfaces.go
func (s *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error) {
	// Access public data by setting the collection to empty string
	collection := ""
	// ignore QueryResponseMetadata as it is not applicable for a rich query without pagination
	iterator, _, err := s.handleGetQueryResult(collection, query, nil)

	return iterator, err
}
// GetQueryResultWithPagination ...
func (s *ChaincodeStub) GetQueryResultWithPagination(query string, pageSize int32,
	bookmark string) (StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) {
	// Access public data by setting the collection to empty string
	collection := ""

	metadata, err := createQueryMetadata(pageSize, bookmark)
	if err != nil {
		return nil, nil, err
	}
	return s.handleGetQueryResult(collection, query, metadata)
}

以上是他们的源码,实际上可以发现,他们都是调用handleGetQueryResult这个函数,分页查询函数知识多了一步创建metadata,将范围打包。之后可以和GetStateByRange(startKey, endKey string) 的函数做比较。

bookmark是书签,内容是一个无序的字符数组,如果为空则会重头查询,如果不为空,则会从会从它所代表的位置开始查询,在每次分页查询的时候都可以得到一个bookmark,也就是说,下一页的查询需要带上一页的bookmark。

func (s *ChaincodeStub) handleGetQueryResult(collection, query string,
	metadata []byte) (StateQueryIteratorInterface, *pb.QueryResponseMetadata, error) {
    //获取查询结果,这一步就使用了metadata进行分页
	response, err := s.handler.handleGetQueryResult(collection, query, metadata, s.ChannelID, s.TxID)
	if err != nil {
		return nil, nil, err
	}
    //创建迭代器
	iterator := s.createStateQueryIterator(response)
	//进行再次封装
	responseMetadata, err := createQueryResponseMetadata(response.Metadata)
	if err != nil {
		return nil, nil, err
	}

	return iterator, responseMetadata, nil
}

和这两个富查询函数一起使用handleGetQueryResult的,还有一个GetPrivateDataQueryResult函数。这个函数看起来是查询私有数据,但私有数据是什么暂且不知道,按下不提。这四个函数都是在shim/stub.go中。

索引及数据结构

富查询目前只有couchDB支持,这是一个比较依赖数据库的功能,那就必须要有考虑一个问题:索引。参考Fabric的中文文档可以建立索引。

特别注意两点:

1.索引是依据需求来的,如果太多的索引会影响查询速率

2.索引的目录是固定的,链码同目录下的META-INF/statedb/couchdb/indexes

一个完整的索引如下:

{
  "index":{
      //要查询的字段名称,一般情况下要包含docType,对于docType的解释在后面
      "fields":["docType","owner"] // Names of the fields to be queried
  },
   // (可选)将创建索引的设计文件的名称。如果是一个文件包含一个索引可以不使用,官方建议也是单个索引
  "ddoc":"indexOwnerDoc", 
  "name":"indexOwner",
  "type":"json"
}

我在官方文档中还看到了一个数据结构的规范写法

type marble struct {
    //docType用于区分状态数据库中的各种类型的对象
    ObjectType string `json:"docType"` 
    //以下的正常的数据结构
    Name       string `json:"name"` 
    Color      string `json:"color"`
    Size       int    `json:"size"`
    Owner      string `json:"owner"`
}

docType 用来识别这个文档的数据类型。 同时在链码数据库中也可能存在其他文档。数据库中的文档对于这些属性值来说都是可查询的。

常用函数

以下是我根据文档中给出的示例总结的富查询常用函数,主要使用getQueryResultForQueryStringgetQueryResultForQueryStringWithPagination,后两个函数是为分页富查询函数使用的。可以写在智能合约中调用。

=========================================================================================
// getQueryResultForQueryString执行传入的查询字符串。
// 结果集被建立并作为一个字节数组返回,其中包含JSON结果。
=========================================================================================
func getQueryResultForQueryString(APIstub shim.ChaincodeStubInterface, queryString string) ([]byte, error) {

	fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString)

	resultsIterator, err := APIstub.GetQueryResult(queryString)
	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	buffer, err := constructQueryResponseFromIterator(resultsIterator)
	if err != nil {
		return nil, err
	}

	fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())

	return buffer.Bytes(), nil
}

=========================================================================================
// getQueryResultForQueryStringWithPagination执行传入的查询字符串,其中包括
// 分页信息。结果集被建立并作为一个字节数组返回,其中包含JSON结果。
=========================================================================================
func getQueryResultForQueryStringWithPagination(APIstub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) ([]byte, error) {

	fmt.Printf("- getQueryResultForQueryStringWithPagination queryString:\n%s\n", queryString)

	resultsIterator, responseMetadata, err := APIstub.GetQueryResultWithPagination(queryString, pageSize, bookmark)
	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	buffer, err := constructQueryResponseFromIterator(resultsIterator)
	if err != nil {
		return nil, err
	}

	bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata)

	fmt.Printf("- getQueryResultForQueryStringWithPagination queryResult:\n%s\n", bufferWithPaginationInfo.String())

	return buffer.Bytes(), nil
}
=========================================================================================
// constructQueryResponseFromIterator构建一个JSON数组,其中包含来自给定结果迭代器的查询结果。
// 一个给定的结果迭代器=========================================================================================
func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) (*bytes.Buffer, error) {
	// buffer is a JSON array containing QueryResults
	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return nil, err
		}
		// Add a comma before array members, suppress it for the first array member
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"Key\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.Key)
		buffer.WriteString("\"")

		buffer.WriteString(", \"Record\":")
		// Record is a JSON object, so we write as-is
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")

	return &buffer, nil
}

=========================================================================================
// addPaginationMetadataToQueryResults 将包含分页的QueryResponseMetadata
// 的信息,添加到构建的查询结果中。
=========================================================================================
func addPaginationMetadataToQueryResults(buffer *bytes.Buffer, responseMetadata *pb.QueryResponseMetadata) *bytes.Buffer {
	buffer.WriteString("[{\"ResponseMetadata\":{\"RecordsCount\":")
	buffer.WriteString("\"")
	buffer.WriteString(fmt.Sprintf("%v", responseMetadata.FetchedRecordsCount))
	buffer.WriteString("\"")
	buffer.WriteString(", \"Bookmark\":")
	buffer.WriteString("\"")
	buffer.WriteString(responseMetadata.Bookmark)
	buffer.WriteString("\"}}]")

	return buffer
}

下面是我写的一个调用分页函数的合约函数(我把可以在SDK中调用的智能合约函数叫做合约函数)示例

func (s *SmartContract) queryCourseWithPagination(APIstub shim.ChaincodeStubInterface, args []string) pb.Response {
	fmt.Println("func queryCourseWithPagination start")
	fmt.Printf("args:%v\n", args)
	if len(args) != 3 {
		return shim.Error("arguments num not enough," + strconv.Itoa(len(args)))
	}
	queryString := args[0]
	//return type of ParseInt is int64
	pageSize, err := strconv.ParseInt(args[1], 10, 32)
	if err != nil {
		return shim.Error(err.Error())
	}
	bookmark := args[2]
    //之前是获取参数,在这一步调用分页富查询函数,查询出结果后转为字节流返回
	queryResults, err := getQueryResultForQueryStringWithPagination(APIstub, queryString, int32(pageSize), bookmark)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(queryResults)
}

chaincode常用包shim链接

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值