github.com/lzy4123/fabric@v2.1.1+incompatible/integration/chaincode/marbles/marbles_chaincode.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 SPDX-License-Identifier: Apache-2.0 4 */ 5 6 // ====CHAINCODE EXECUTION SAMPLES (CLI) ================== 7 8 // ==== Invoke marbles ==== 9 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble1","blue","35","tom"]}' 10 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble2","red","50","tom"]}' 11 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble3","blue","70","tom"]}' 12 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarble","marble2","jerry"]}' 13 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarblesBasedOnColor","blue","jerry"]}' 14 // peer chaincode invoke -C myc1 -n marbles -c '{"Args":["delete","marble1"]}' 15 16 // ==== Query marbles ==== 17 // peer chaincode query -C myc1 -n marbles -c '{"Args":["readMarble","marble1"]}' 18 // peer chaincode query -C myc1 -n marbles -c '{"Args":["getMarblesByRange","marble1","marble3"]}' 19 // peer chaincode query -C myc1 -n marbles -c '{"Args":["getHistoryForMarble","marble1"]}' 20 21 // Rich Query (Only supported if CouchDB is used as state database): 22 // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' 23 // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' 24 25 // Rich Query with Pagination (Only supported if CouchDB is used as state database): 26 // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesWithPagination","{\"selector\":{\"owner\":\"tom\"}}","3",""]}' 27 28 // INDEXES TO SUPPORT COUCHDB RICH QUERIES 29 // 30 // Indexes in CouchDB are required in order to make JSON queries efficient and are required for 31 // any JSON query with a sort. As of Hyperledger Fabric 1.1, indexes may be packaged alongside 32 // chaincode in a META-INF/statedb/couchdb/indexes directory. Each index must be defined in its own 33 // text file with extension *.json with the index definition formatted in JSON following the 34 // CouchDB index JSON syntax as documented at: 35 // http://docs.couchdb.org/en/2.1.1/api/database/find.html#db-index 36 // 37 // This marbles02 example chaincode demonstrates a packaged 38 // index which you can find in META-INF/statedb/couchdb/indexes/indexOwner.json. 39 // For deployment of chaincode to production environments, it is recommended 40 // to define any indexes alongside chaincode so that the chaincode and supporting indexes 41 // are deployed automatically as a unit, once the chaincode has been installed on a peer and 42 // instantiated on a channel. See Hyperledger Fabric documentation for more details. 43 // 44 // If you have access to the your peer's CouchDB state database in a development environment, 45 // you may want to iteratively test various indexes in support of your chaincode queries. You 46 // can use the CouchDB Fauxton interface or a command line curl utility to create and update 47 // indexes. Then once you finalize an index, include the index definition alongside your 48 // chaincode in the META-INF/statedb/couchdb/indexes directory, for packaging and deployment 49 // to managed environments. 50 // 51 // In the examples below you can find index definitions that support marbles02 52 // chaincode queries, along with the syntax that you can use in development environments 53 // to create the indexes in the CouchDB Fauxton interface or a curl command line utility. 54 // 55 56 //Example hostname:port configurations to access CouchDB. 57 // 58 //To access CouchDB docker container from within another docker container or from vagrant environments: 59 // http://couchdb:5984/ 60 // 61 //Inside couchdb docker container 62 // http://127.0.0.1:5984/ 63 64 // Index for docType, owner. 65 // 66 // Example curl command line to define index in the CouchDB channel_chaincode database 67 // curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"docType\",\"owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index 68 // 69 70 // Index for docType, owner, size (descending order). 71 // 72 // Example curl command line to define index in the CouchDB channel_chaincode database 73 // curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"size\":\"desc\"},{\"docType\":\"desc\"},{\"owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index 74 75 // Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database): 76 // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":\"marble\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}' 77 78 // Rich Query with index design doc specified only (Only supported if CouchDB is used as state database): 79 // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":{\"$eq\":\"marble\"},\"owner\":{\"$eq\":\"tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}' 80 81 package marbles 82 83 import ( 84 "bytes" 85 "encoding/json" 86 "fmt" 87 "strconv" 88 "strings" 89 "time" 90 91 "github.com/hyperledger/fabric-chaincode-go/shim" 92 pb "github.com/hyperledger/fabric-protos-go/peer" 93 ) 94 95 // SimpleChaincode example simple Chaincode implementation 96 type SimpleChaincode struct { 97 } 98 99 type marble struct { 100 ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database 101 Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around 102 Color string `json:"color"` 103 Size int `json:"size"` 104 Owner string `json:"owner"` 105 } 106 107 // =================================================================================== 108 // Main 109 // =================================================================================== 110 func main() { 111 err := shim.Start(new(SimpleChaincode)) 112 if err != nil { 113 fmt.Printf("Error starting Simple chaincode: %s", err) 114 } 115 } 116 117 // Init initializes chaincode 118 // =========================== 119 func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { 120 return shim.Success(nil) 121 } 122 123 // Invoke - Our entry point for Invocations 124 // ======================================== 125 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { 126 function, args := stub.GetFunctionAndParameters() 127 fmt.Println("invoke is running " + function) 128 129 // Handle different functions 130 if function == "initMarble" { //create a new marble 131 return t.initMarble(stub, args) 132 } else if function == "transferMarble" { //change owner of a specific marble 133 return t.transferMarble(stub, args) 134 } else if function == "transferMarblesBasedOnColor" { //transfer all marbles of a certain color 135 return t.transferMarblesBasedOnColor(stub, args) 136 } else if function == "delete" { //delete a marble 137 return t.delete(stub, args) 138 } else if function == "readMarble" { //read a marble 139 return t.readMarble(stub, args) 140 } else if function == "queryMarblesByOwner" { //find marbles for owner X using rich query 141 return t.queryMarblesByOwner(stub, args) 142 } else if function == "queryMarbles" { //find marbles based on an ad hoc rich query 143 return t.queryMarbles(stub, args) 144 } else if function == "getHistoryForMarble" { //get history of values for a marble 145 return t.getHistoryForMarble(stub, args) 146 } else if function == "getMarblesByRange" { //get marbles based on range query 147 return t.getMarblesByRange(stub, args) 148 } else if function == "getMarblesByRangeWithPagination" { 149 return t.getMarblesByRangeWithPagination(stub, args) 150 } else if function == "queryMarblesWithPagination" { 151 return t.queryMarblesWithPagination(stub, args) 152 } 153 154 fmt.Println("invoke did not find func: " + function) //error 155 return shim.Error("Received unknown function invocation") 156 } 157 158 // ============================================================ 159 // initMarble - create a new marble, store into chaincode state 160 // ============================================================ 161 func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 162 var err error 163 164 // 0 1 2 3 165 // "asdf", "blue", "35", "bob" 166 if len(args) != 4 { 167 return shim.Error("Incorrect number of arguments. Expecting 4") 168 } 169 170 // ==== Input sanitation ==== 171 fmt.Println("- start init marble") 172 if len(args[0]) <= 0 { 173 return shim.Error("1st argument must be a non-empty string") 174 } 175 if len(args[1]) <= 0 { 176 return shim.Error("2nd argument must be a non-empty string") 177 } 178 if len(args[2]) <= 0 { 179 return shim.Error("3rd argument must be a non-empty string") 180 } 181 if len(args[3]) <= 0 { 182 return shim.Error("4th argument must be a non-empty string") 183 } 184 marbleName := args[0] 185 color := strings.ToLower(args[1]) 186 owner := strings.ToLower(args[3]) 187 size, err := strconv.Atoi(args[2]) 188 if err != nil { 189 return shim.Error("3rd argument must be a numeric string") 190 } 191 192 // ==== Check if marble already exists ==== 193 marbleAsBytes, err := stub.GetState(marbleName) 194 if err != nil { 195 return shim.Error("Failed to get marble: " + err.Error()) 196 } else if marbleAsBytes != nil { 197 fmt.Println("This marble already exists: " + marbleName) 198 return shim.Error("This marble already exists: " + marbleName) 199 } 200 201 // ==== Create marble object and marshal to JSON ==== 202 objectType := "marble" 203 marble := &marble{objectType, marbleName, color, size, owner} 204 marbleJSONasBytes, err := json.Marshal(marble) 205 if err != nil { 206 return shim.Error(err.Error()) 207 } 208 //Alternatively, build the marble json string manually if you don't want to use struct marshalling 209 //marbleJSONasString := `{"docType":"Marble", "name": "` + marbleName + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": "` + owner + `"}` 210 //marbleJSONasBytes := []byte(str) 211 212 // === Save marble to state === 213 err = stub.PutState(marbleName, marbleJSONasBytes) 214 if err != nil { 215 return shim.Error(err.Error()) 216 } 217 218 // ==== Index the marble to enable color-based range queries, e.g. return all blue marbles ==== 219 // An 'index' is a normal key/value entry in state. 220 // The key is a composite key, with the elements that you want to range query on listed first. 221 // In our case, the composite key is based on indexName~color~name. 222 // This will enable very efficient state range queries based on composite keys matching indexName~color~* 223 indexName := "color~name" 224 colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name}) 225 if err != nil { 226 return shim.Error(err.Error()) 227 } 228 // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. 229 // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value 230 value := []byte{0x00} 231 stub.PutState(colorNameIndexKey, value) 232 233 // ==== Marble saved and indexed. Return success ==== 234 fmt.Println("- end init marble") 235 return shim.Success(nil) 236 } 237 238 // =============================================== 239 // readMarble - read a marble from chaincode state 240 // =============================================== 241 func (t *SimpleChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 242 var name, jsonResp string 243 var err error 244 245 if len(args) != 1 { 246 return shim.Error("Incorrect number of arguments. Expecting name of the marble to query") 247 } 248 249 name = args[0] 250 valAsbytes, err := stub.GetState(name) //get the marble from chaincode state 251 if err != nil { 252 jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}" 253 return shim.Error(jsonResp) 254 } else if valAsbytes == nil { 255 jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}" 256 return shim.Error(jsonResp) 257 } 258 259 return shim.Success(valAsbytes) 260 } 261 262 // ================================================== 263 // delete - remove a marble key/value pair from state 264 // ================================================== 265 func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { 266 var jsonResp string 267 var marbleJSON marble 268 if len(args) != 1 { 269 return shim.Error("Incorrect number of arguments. Expecting 1") 270 } 271 marbleName := args[0] 272 273 // to maintain the color~name index, we need to read the marble first and get its color 274 valAsbytes, err := stub.GetState(marbleName) //get the marble from chaincode state 275 if err != nil { 276 jsonResp = "{\"Error\":\"Failed to get state for " + marbleName + "\"}" 277 return shim.Error(jsonResp) 278 } else if valAsbytes == nil { 279 jsonResp = "{\"Error\":\"Marble does not exist: " + marbleName + "\"}" 280 return shim.Error(jsonResp) 281 } 282 283 err = json.Unmarshal([]byte(valAsbytes), &marbleJSON) 284 if err != nil { 285 jsonResp = "{\"Error\":\"Failed to decode JSON of: " + marbleName + "\"}" 286 return shim.Error(jsonResp) 287 } 288 289 err = stub.DelState(marbleName) //remove the marble from chaincode state 290 if err != nil { 291 return shim.Error("Failed to delete state:" + err.Error()) 292 } 293 294 // maintain the index 295 indexName := "color~name" 296 colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleJSON.Color, marbleJSON.Name}) 297 if err != nil { 298 return shim.Error(err.Error()) 299 } 300 301 // Delete index entry to state. 302 err = stub.DelState(colorNameIndexKey) 303 if err != nil { 304 return shim.Error("Failed to delete state:" + err.Error()) 305 } 306 return shim.Success(nil) 307 } 308 309 // =========================================================== 310 // transfer a marble by setting a new owner name on the marble 311 // =========================================================== 312 func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 313 314 // 0 1 315 // "name", "bob" 316 if len(args) < 2 { 317 return shim.Error("Incorrect number of arguments. Expecting 2") 318 } 319 320 marbleName := args[0] 321 newOwner := strings.ToLower(args[1]) 322 fmt.Println("- start transferMarble ", marbleName, newOwner) 323 324 marbleAsBytes, err := stub.GetState(marbleName) 325 if err != nil { 326 return shim.Error("Failed to get marble:" + err.Error()) 327 } else if marbleAsBytes == nil { 328 return shim.Error("Marble does not exist") 329 } 330 331 marbleToTransfer := marble{} 332 err = json.Unmarshal(marbleAsBytes, &marbleToTransfer) //unmarshal it aka JSON.parse() 333 if err != nil { 334 return shim.Error(err.Error()) 335 } 336 marbleToTransfer.Owner = newOwner //change the owner 337 338 marbleJSONasBytes, _ := json.Marshal(marbleToTransfer) 339 err = stub.PutState(marbleName, marbleJSONasBytes) //rewrite the marble 340 if err != nil { 341 return shim.Error(err.Error()) 342 } 343 344 fmt.Println("- end transferMarble (success)") 345 return shim.Success(nil) 346 } 347 348 // =========================================================================================== 349 // constructQueryResponseFromIterator constructs a JSON array containing query results from 350 // a given result iterator 351 // =========================================================================================== 352 func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) (*bytes.Buffer, error) { 353 // buffer is a JSON array containing QueryResults 354 var buffer bytes.Buffer 355 buffer.WriteString("[") 356 357 bArrayMemberAlreadyWritten := false 358 for resultsIterator.HasNext() { 359 queryResponse, err := resultsIterator.Next() 360 if err != nil { 361 return nil, err 362 } 363 // Add a comma before array members, suppress it for the first array member 364 if bArrayMemberAlreadyWritten == true { 365 buffer.WriteString(",") 366 } 367 buffer.WriteString("{\"Key\":") 368 buffer.WriteString("\"") 369 buffer.WriteString(queryResponse.Key) 370 buffer.WriteString("\"") 371 372 buffer.WriteString(", \"Record\":") 373 // Record is a JSON object, so we write as-is 374 buffer.WriteString(string(queryResponse.Value)) 375 buffer.WriteString("}") 376 bArrayMemberAlreadyWritten = true 377 } 378 buffer.WriteString("]") 379 380 return &buffer, nil 381 } 382 383 // =========================================================================================== 384 // addPaginationMetadataToQueryResults adds QueryResponseMetadata, which contains pagination 385 // info, to the constructed query results 386 // =========================================================================================== 387 func addPaginationMetadataToQueryResults(buffer *bytes.Buffer, responseMetadata *pb.QueryResponseMetadata) *bytes.Buffer { 388 389 buffer.WriteString("[{\"ResponseMetadata\":{\"RecordsCount\":") 390 buffer.WriteString("\"") 391 buffer.WriteString(fmt.Sprintf("%v", responseMetadata.FetchedRecordsCount)) 392 buffer.WriteString("\"") 393 buffer.WriteString(", \"Bookmark\":") 394 buffer.WriteString("\"") 395 buffer.WriteString(responseMetadata.Bookmark) 396 buffer.WriteString("\"}}]") 397 398 return buffer 399 } 400 401 // =========================================================================================== 402 // getMarblesByRange performs a range query based on the start and end keys provided. 403 404 // Read-only function results are not typically submitted to ordering. If the read-only 405 // results are submitted to ordering, or if the query is used in an update transaction 406 // and submitted to ordering, then the committing peers will re-execute to guarantee that 407 // result sets are stable between endorsement time and commit time. The transaction is 408 // invalidated by the committing peers if the result set has changed between endorsement 409 // time and commit time. 410 // Therefore, range queries are a safe option for performing update transactions based on query results. 411 // =========================================================================================== 412 func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { 413 414 if len(args) < 2 { 415 return shim.Error("Incorrect number of arguments. Expecting 2") 416 } 417 418 startKey := args[0] 419 endKey := args[1] 420 421 resultsIterator, err := stub.GetStateByRange(startKey, endKey) 422 if err != nil { 423 return shim.Error(err.Error()) 424 } 425 defer resultsIterator.Close() 426 427 buffer, err := constructQueryResponseFromIterator(resultsIterator) 428 if err != nil { 429 return shim.Error(err.Error()) 430 } 431 432 fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) 433 434 return shim.Success(buffer.Bytes()) 435 } 436 437 // ==== Example: GetStateByPartialCompositeKey/RangeQuery ========================================= 438 // transferMarblesBasedOnColor will transfer marbles of a given color to a certain new owner. 439 // Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. 440 // Committing peers will re-execute range queries to guarantee that result sets are stable 441 // between endorsement time and commit time. The transaction is invalidated by the 442 // committing peers if the result set has changed between endorsement time and commit time. 443 // Therefore, range queries are a safe option for performing update transactions based on query results. 444 // =========================================================================================== 445 func (t *SimpleChaincode) transferMarblesBasedOnColor(stub shim.ChaincodeStubInterface, args []string) pb.Response { 446 447 // 0 1 448 // "color", "bob" 449 if len(args) < 2 { 450 return shim.Error("Incorrect number of arguments. Expecting 2") 451 } 452 453 color := args[0] 454 newOwner := strings.ToLower(args[1]) 455 fmt.Println("- start transferMarblesBasedOnColor ", color, newOwner) 456 457 // Query the color~name index by color 458 // This will execute a key range query on all keys starting with 'color' 459 coloredMarbleResultsIterator, err := stub.GetStateByPartialCompositeKey("color~name", []string{color}) 460 if err != nil { 461 return shim.Error(err.Error()) 462 } 463 defer coloredMarbleResultsIterator.Close() 464 465 // Iterate through result set and for each marble found, transfer to newOwner 466 var i int 467 for i = 0; coloredMarbleResultsIterator.HasNext(); i++ { 468 // Note that we don't get the value (2nd return variable), we'll just get the marble name from the composite key 469 responseRange, err := coloredMarbleResultsIterator.Next() 470 if err != nil { 471 return shim.Error(err.Error()) 472 } 473 474 // get the color and name from color~name composite key 475 objectType, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key) 476 if err != nil { 477 return shim.Error(err.Error()) 478 } 479 returnedColor := compositeKeyParts[0] 480 returnedMarbleName := compositeKeyParts[1] 481 fmt.Printf("- found a marble from index:%s color:%s name:%s\n", objectType, returnedColor, returnedMarbleName) 482 483 // Now call the transfer function for the found marble. 484 // Re-use the same function that is used to transfer individual marbles 485 response := t.transferMarble(stub, []string{returnedMarbleName, newOwner}) 486 // if the transfer failed break out of loop and return error 487 if response.Status != shim.OK { 488 return shim.Error("Transfer failed: " + response.Message) 489 } 490 } 491 492 responsePayload := fmt.Sprintf("Transferred %d %s marbles to %s", i, color, newOwner) 493 fmt.Println("- end transferMarblesBasedOnColor: " + responsePayload) 494 return shim.Success([]byte(responsePayload)) 495 } 496 497 // =======Rich queries ========================================================================= 498 // Two examples of rich queries are provided below (parameterized query and ad hoc query). 499 // Rich queries pass a query string to the state database. 500 // Rich queries are only supported by state database implementations 501 // that support rich query (e.g. CouchDB). 502 // The query string is in the syntax of the underlying state database. 503 // With rich queries there is no guarantee that the result set hasn't changed between 504 // endorsement time and commit time, aka 'phantom reads'. 505 // Therefore, rich queries should not be used in update transactions, unless the 506 // application handles the possibility of result set changes between endorsement and commit time. 507 // Rich queries can be used for point-in-time queries against a peer. 508 // ============================================================================================ 509 510 // ===== Example: Parameterized rich query ================================================= 511 // queryMarblesByOwner queries for marbles based on a passed in owner. 512 // This is an example of a parameterized query where the query logic is baked into the chaincode, 513 // and accepting a single query parameter (owner). 514 // Only available on state databases that support rich query (e.g. CouchDB) 515 // ========================================================================================= 516 func (t *SimpleChaincode) queryMarblesByOwner(stub shim.ChaincodeStubInterface, args []string) pb.Response { 517 518 // 0 519 // "bob" 520 if len(args) < 1 { 521 return shim.Error("Incorrect number of arguments. Expecting 1") 522 } 523 524 owner := strings.ToLower(args[0]) 525 526 queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"marble\",\"owner\":\"%s\"}}", owner) 527 528 queryResults, err := getQueryResultForQueryString(stub, queryString) 529 if err != nil { 530 return shim.Error(err.Error()) 531 } 532 return shim.Success(queryResults) 533 } 534 535 // ===== Example: Ad hoc rich query ======================================================== 536 // queryMarbles uses a query string to perform a query for marbles. 537 // Query string matching state database syntax is passed in and executed as is. 538 // Supports ad hoc queries that can be defined at runtime by the client. 539 // If this is not desired, follow the queryMarblesForOwner example for parameterized queries. 540 // Only available on state databases that support rich query (e.g. CouchDB) 541 // ========================================================================================= 542 func (t *SimpleChaincode) queryMarbles(stub shim.ChaincodeStubInterface, args []string) pb.Response { 543 544 // 0 545 // "queryString" 546 if len(args) < 1 { 547 return shim.Error("Incorrect number of arguments. Expecting 1") 548 } 549 550 queryString := args[0] 551 552 queryResults, err := getQueryResultForQueryString(stub, queryString) 553 if err != nil { 554 return shim.Error(err.Error()) 555 } 556 return shim.Success(queryResults) 557 } 558 559 // ========================================================================================= 560 // getQueryResultForQueryString executes the passed in query string. 561 // Result set is built and returned as a byte array containing the JSON results. 562 // ========================================================================================= 563 func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { 564 565 fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) 566 567 resultsIterator, err := stub.GetQueryResult(queryString) 568 if err != nil { 569 return nil, err 570 } 571 defer resultsIterator.Close() 572 573 buffer, err := constructQueryResponseFromIterator(resultsIterator) 574 if err != nil { 575 return nil, err 576 } 577 578 fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) 579 580 return buffer.Bytes(), nil 581 } 582 583 // ====== Pagination ========================================================================= 584 // Pagination provides a method to retrieve records with a defined pagesize and 585 // start point (bookmark). An empty string bookmark defines the first "page" of a query 586 // result. Paginated queries return a bookmark that can be used in 587 // the next query to retrieve the next page of results. Paginated queries extend 588 // rich queries and range queries to include a pagesize and bookmark. 589 // 590 // Two examples are provided in this example. The first is getMarblesByRangeWithPagination 591 // which executes a paginated range query. 592 // The second example is a paginated query for rich ad-hoc queries. 593 // ========================================================================================= 594 595 // ====== Example: Pagination with Range Query =============================================== 596 // getMarblesByRangeWithPagination performs a range query based on the start & end key, 597 // page size and a bookmark. 598 599 // The number of fetched records will be equal to or lesser than the page size. 600 // Paginated range queries are only valid for read only transactions. 601 // =========================================================================================== 602 func (t *SimpleChaincode) getMarblesByRangeWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { 603 604 if len(args) < 4 { 605 return shim.Error("Incorrect number of arguments. Expecting 4") 606 } 607 608 startKey := args[0] 609 endKey := args[1] 610 //return type of ParseInt is int64 611 pageSize, err := strconv.ParseInt(args[2], 10, 32) 612 if err != nil { 613 return shim.Error(err.Error()) 614 } 615 bookmark := args[3] 616 617 resultsIterator, responseMetadata, err := stub.GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark) 618 if err != nil { 619 return shim.Error(err.Error()) 620 } 621 defer resultsIterator.Close() 622 623 buffer, err := constructQueryResponseFromIterator(resultsIterator) 624 if err != nil { 625 return shim.Error(err.Error()) 626 } 627 628 bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) 629 630 fmt.Printf("- getMarblesByRange queryResult:\n%s\n", bufferWithPaginationInfo.String()) 631 632 return shim.Success(buffer.Bytes()) 633 } 634 635 // ===== Example: Pagination with Ad hoc Rich Query ======================================================== 636 // queryMarblesWithPagination uses a query string, page size and a bookmark to perform a query 637 // for marbles. Query string matching state database syntax is passed in and executed as is. 638 // The number of fetched records would be equal to or lesser than the specified page size. 639 // Supports ad hoc queries that can be defined at runtime by the client. 640 // If this is not desired, follow the queryMarblesForOwner example for parameterized queries. 641 // Only available on state databases that support rich query (e.g. CouchDB) 642 // Paginated queries are only valid for read only transactions. 643 // ========================================================================================= 644 func (t *SimpleChaincode) queryMarblesWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { 645 646 // 0 647 // "queryString" 648 if len(args) < 3 { 649 return shim.Error("Incorrect number of arguments. Expecting 3") 650 } 651 652 queryString := args[0] 653 //return type of ParseInt is int64 654 pageSize, err := strconv.ParseInt(args[1], 10, 32) 655 if err != nil { 656 return shim.Error(err.Error()) 657 } 658 bookmark := args[2] 659 660 queryResults, err := getQueryResultForQueryStringWithPagination(stub, queryString, int32(pageSize), bookmark) 661 if err != nil { 662 return shim.Error(err.Error()) 663 } 664 return shim.Success(queryResults) 665 } 666 667 // ========================================================================================= 668 // getQueryResultForQueryStringWithPagination executes the passed in query string with 669 // pagination info. Result set is built and returned as a byte array containing the JSON results. 670 // ========================================================================================= 671 func getQueryResultForQueryStringWithPagination(stub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) ([]byte, error) { 672 673 fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) 674 675 resultsIterator, responseMetadata, err := stub.GetQueryResultWithPagination(queryString, pageSize, bookmark) 676 if err != nil { 677 return nil, err 678 } 679 defer resultsIterator.Close() 680 681 buffer, err := constructQueryResponseFromIterator(resultsIterator) 682 if err != nil { 683 return nil, err 684 } 685 686 bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) 687 688 fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", bufferWithPaginationInfo.String()) 689 690 return buffer.Bytes(), nil 691 } 692 693 func (t *SimpleChaincode) getHistoryForMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 694 695 if len(args) < 1 { 696 return shim.Error("Incorrect number of arguments. Expecting 1") 697 } 698 699 marbleName := args[0] 700 701 fmt.Printf("- start getHistoryForMarble: %s\n", marbleName) 702 703 resultsIterator, err := stub.GetHistoryForKey(marbleName) 704 if err != nil { 705 return shim.Error(err.Error()) 706 } 707 defer resultsIterator.Close() 708 709 // buffer is a JSON array containing historic values for the marble 710 var buffer bytes.Buffer 711 buffer.WriteString("[") 712 713 bArrayMemberAlreadyWritten := false 714 for resultsIterator.HasNext() { 715 response, err := resultsIterator.Next() 716 if err != nil { 717 return shim.Error(err.Error()) 718 } 719 // Add a comma before array members, suppress it for the first array member 720 if bArrayMemberAlreadyWritten == true { 721 buffer.WriteString(",") 722 } 723 buffer.WriteString("{\"TxId\":") 724 buffer.WriteString("\"") 725 buffer.WriteString(response.TxId) 726 buffer.WriteString("\"") 727 728 buffer.WriteString(", \"Value\":") 729 // if it was a delete operation on given key, then we need to set the 730 //corresponding value null. Else, we will write the response.Value 731 //as-is (as the Value itself a JSON marble) 732 if response.IsDelete { 733 buffer.WriteString("null") 734 } else { 735 buffer.WriteString(string(response.Value)) 736 } 737 738 buffer.WriteString(", \"Timestamp\":") 739 buffer.WriteString("\"") 740 buffer.WriteString(time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).String()) 741 buffer.WriteString("\"") 742 743 buffer.WriteString(", \"IsDelete\":") 744 buffer.WriteString("\"") 745 buffer.WriteString(strconv.FormatBool(response.IsDelete)) 746 buffer.WriteString("\"") 747 748 buffer.WriteString("}") 749 bArrayMemberAlreadyWritten = true 750 } 751 buffer.WriteString("]") 752 753 fmt.Printf("- getHistoryForMarble returning:\n%s\n", buffer.String()) 754 755 return shim.Success(buffer.Bytes()) 756 }