Search code examples
sortingcouchdbhyperledger-fabricchaincode

Sorting not working in golang chaincode hyperledger fabric


I'm trying to sort the result in golang chaincode, but the result is random, below is my chaincode sample:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "time"

    "github.com/hyperledger/fabric/core/chaincode/shim"
    "github.com/hyperledger/fabric/protos/peer"
)

type itemStruct struct {
    ID        string    `json:"id"`
    Status    string    `json:"status"`
    CreatedAt time.Time `json:"created_at"`
}

func createItem(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    if len(args) != 3 {
        return shim.Error(fmt.Sprintf("Expecting %v arguments {id, status, created_at}, but got %v", 3, len(args)))
    }

    itemID := args[0]
    if len(itemID) == 0 {
        return shim.Error("id field is required")
    }
    status := args[1]
    if len(status) == 0 {
        return shim.Error("status field is required")
    }
    createdAt, err := time.Parse(time.RFC3339, args[2])
    if err != nil {
        return shim.Error("created_at is not a valid datetime string")
    }

    item := itemStruct{
        ID:        itemID,
        CreatedAt: createdAt,
    }

    itemAsJSONBytes, err := json.Marshal(item)
    if err != nil {
        return shim.Error(err.Error())
    }

    return shim.Success(itemAsJSONBytes)
}

func getPendingItems(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    var bookmark string

    if len(args) > 0 && len(args[0]) > 0 {
        bookmark = args[0]
    }

    queryString := `{
        "selector": {
            "status": "pending"
        },
        "sort": [
            {"created_at": "desc"}
        ]
    }`
    result, pagination, err := queryWithPagination(stub, queryString, 20, bookmark)
    if err != nil {
        return shim.Error(err.Error())
    }

    return shim.Success(constructResponse(result, pagination).Bytes())
}

func queryWithPagination(stub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) (map[string]string, string, error) {
    var pagination string
    iterator, meta, err := stub.GetQueryResultWithPagination(queryString, pageSize, bookmark)
    if err != nil {
        return nil, pagination, err
    }
    defer iterator.Close()

    result, err := iterateResult(iterator)
    if err != nil {
        return nil, pagination, err
    }

    pagination = fmt.Sprintf(`{"count": %v, "next_page_token": "%v"}`, meta.FetchedRecordsCount, meta.Bookmark)
    return result, pagination, nil
}

func constructResponse(items map[string]string, pagination string) *bytes.Buffer {
    // buffer is a JSON array containing QueryResults
    var buffer bytes.Buffer

    if len(pagination) > 0 {
        buffer.WriteString(`{"data":`)
    }

    buffer.WriteString(`[`)

    bArrayMemberAlreadyWritten := false
    for _, val := range items {
        // Add a comma before array members, suppress it for the first array member
        if bArrayMemberAlreadyWritten == true {
            buffer.WriteString(",")
        }
        buffer.WriteString(val)
        bArrayMemberAlreadyWritten = true
    }
    buffer.WriteString("]")

    if len(pagination) > 0 {
        buffer.WriteString(`,"pagination":`)
        buffer.WriteString(pagination)
        buffer.WriteString("}")
    }

    return &buffer
}

func iterateResult(iterator shim.StateQueryIteratorInterface) (map[string]string, error) {
    result := map[string]string{}

    for iterator.HasNext() {
        queryResponse, err := iterator.Next()
        if err != nil {
            return nil, err
        }
        result[queryResponse.Key] = string(queryResponse.Value)
    }

    return result, nil
}

// SmartContract : Smart contract struct
type SmartContract struct {
}

// Init : This method is called when chaincode is initialized or updated.
func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) peer.Response {
    return shim.Success(nil)
}

// Invoke : This method is called when any transaction or query fired
func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Retrieve the requested Smart Contract function and arguments
    function, args := stub.GetFunctionAndParameters()

    // Route to the appropriate handler function to interact with the ledger appropriately
    // Transactions
    if function == "createItem" {
        return createItem(stub, args)
    }

    // Queries
    if function == "getItems" {
        return getItems(stub, args)
    }

    return shim.Error("Invalid function")
}

func main() {
    // Create a new Smart Contract
    err := shim.Start(new(SmartContract))

    if err != nil {
        fmt.Printf("Error creating new Smart Contract: %s", err)
    }
}

It creates and asset which can have different status based on what's passed, and I have defined one query function which fetches only pending items.

I have applied sort, but the result is still random, can anyone help me here and guide me where I'm going wrong in this?


Solution

  • the sort field has to be present in the selector !

    something like:

    queryString := `{
        "selector": {
            "created_at": "$gt": null
            "status": "pending"
        },
        "sort": [
            {"created_at": "desc"}
        ]
    }`
    

    or (range)

     queryString := `{
        "selector": {
            "created_at":  {
            "$gt": "2015-01-01T00:00:00Z",
            "$lt": "2019-01-01T00:00:00Z"
            },
            "status": "pending"
        },
        "sort": [
            {"created_at": "desc"}
        ]
    }`
    

    Per the docs - to use sorting, ensure that:

    • At least one of the sort fields is included in the selector.
    • There is an index already defined, with all the sort fields in the same order.
    • Each object in the sort array has a single key.
    • If an object in the sort array does not have a single key, the resulting sort order is implementation specific and might change.

    Also, as an fyi Find does not support multiple fields with different sort orders, so the directions must be either all ascending or all descending.

    See also CouchDB sort doesn't work