github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/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 // Init initializes chaincode 108 // =========================== 109 func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { 110 return shim.Success(nil) 111 } 112 113 // Invoke - Our entry point for Invocations 114 // ======================================== 115 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { 116 function, args := stub.GetFunctionAndParameters() 117 fmt.Println("invoke is running " + function) 118 119 // Handle different functions 120 if function == "initMarble" { //create a new marble 121 return t.initMarble(stub, args) 122 } else if function == "transferMarble" { //change owner of a specific marble 123 return t.transferMarble(stub, args) 124 } else if function == "transferMarblesBasedOnColor" { //transfer all marbles of a certain color 125 return t.transferMarblesBasedOnColor(stub, args) 126 } else if function == "delete" { //delete a marble 127 return t.delete(stub, args) 128 } else if function == "readMarble" { //read a marble 129 return t.readMarble(stub, args) 130 } else if function == "queryMarblesByOwner" { //find marbles for owner X using rich query 131 return t.queryMarblesByOwner(stub, args) 132 } else if function == "queryMarbles" { //find marbles based on an ad hoc rich query 133 return t.queryMarbles(stub, args) 134 } else if function == "getHistoryForMarble" { //get history of values for a marble 135 return t.getHistoryForMarble(stub, args) 136 } else if function == "getMarblesByRange" { //get marbles based on range query 137 return t.getMarblesByRange(stub, args) 138 } else if function == "getMarblesByRangeWithPagination" { 139 return t.getMarblesByRangeWithPagination(stub, args) 140 } else if function == "queryMarblesWithPagination" { 141 return t.queryMarblesWithPagination(stub, args) 142 } 143 144 fmt.Println("invoke did not find func: " + function) //error 145 return shim.Error("Received unknown function invocation") 146 } 147 148 // ============================================================ 149 // initMarble - create a new marble, store into chaincode state 150 // ============================================================ 151 func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 152 var err error 153 154 // 0 1 2 3 155 // "asdf", "blue", "35", "bob" 156 if len(args) != 4 { 157 return shim.Error("Incorrect number of arguments. Expecting 4") 158 } 159 160 // ==== Input sanitation ==== 161 fmt.Println("- start init marble") 162 if len(args[0]) <= 0 { 163 return shim.Error("1st argument must be a non-empty string") 164 } 165 if len(args[1]) <= 0 { 166 return shim.Error("2nd argument must be a non-empty string") 167 } 168 if len(args[2]) <= 0 { 169 return shim.Error("3rd argument must be a non-empty string") 170 } 171 if len(args[3]) <= 0 { 172 return shim.Error("4th argument must be a non-empty string") 173 } 174 marbleName := args[0] 175 color := strings.ToLower(args[1]) 176 owner := strings.ToLower(args[3]) 177 size, err := strconv.Atoi(args[2]) 178 if err != nil { 179 return shim.Error("3rd argument must be a numeric string") 180 } 181 182 // ==== Check if marble already exists ==== 183 marbleAsBytes, err := stub.GetState(marbleName) 184 if err != nil { 185 return shim.Error("Failed to get marble: " + err.Error()) 186 } else if marbleAsBytes != nil { 187 fmt.Println("This marble already exists: " + marbleName) 188 return shim.Error("This marble already exists: " + marbleName) 189 } 190 191 // ==== Create marble object and marshal to JSON ==== 192 objectType := "marble" 193 marble := &marble{objectType, marbleName, color, size, owner} 194 marbleJSONasBytes, err := json.Marshal(marble) 195 if err != nil { 196 return shim.Error(err.Error()) 197 } 198 //Alternatively, build the marble json string manually if you don't want to use struct marshalling 199 //marbleJSONasString := `{"docType":"Marble", "name": "` + marbleName + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": "` + owner + `"}` 200 //marbleJSONasBytes := []byte(str) 201 202 // === Save marble to state === 203 err = stub.PutState(marbleName, marbleJSONasBytes) 204 if err != nil { 205 return shim.Error(err.Error()) 206 } 207 208 // ==== Index the marble to enable color-based range queries, e.g. return all blue marbles ==== 209 // An 'index' is a normal key/value entry in state. 210 // The key is a composite key, with the elements that you want to range query on listed first. 211 // In our case, the composite key is based on indexName~color~name. 212 // This will enable very efficient state range queries based on composite keys matching indexName~color~* 213 indexName := "color~name" 214 colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name}) 215 if err != nil { 216 return shim.Error(err.Error()) 217 } 218 // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. 219 // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value 220 value := []byte{0x00} 221 err = stub.PutState(colorNameIndexKey, value) 222 if err != nil { 223 return shim.Error(err.Error()) 224 } 225 226 // ==== Marble saved and indexed. Return success ==== 227 fmt.Println("- end init marble") 228 return shim.Success(nil) 229 } 230 231 // =============================================== 232 // readMarble - read a marble from chaincode state 233 // =============================================== 234 func (t *SimpleChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 235 var name, jsonResp string 236 var err error 237 238 if len(args) != 1 { 239 return shim.Error("Incorrect number of arguments. Expecting name of the marble to query") 240 } 241 242 name = args[0] 243 valAsbytes, err := stub.GetState(name) //get the marble from chaincode state 244 if err != nil { 245 jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}" 246 return shim.Error(jsonResp) 247 } else if valAsbytes == nil { 248 jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}" 249 return shim.Error(jsonResp) 250 } 251 252 return shim.Success(valAsbytes) 253 } 254 255 // ================================================== 256 // delete - remove a marble key/value pair from state 257 // ================================================== 258 func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { 259 var jsonResp string 260 var marbleJSON marble 261 if len(args) != 1 { 262 return shim.Error("Incorrect number of arguments. Expecting 1") 263 } 264 marbleName := args[0] 265 266 // to maintain the color~name index, we need to read the marble first and get its color 267 valAsbytes, err := stub.GetState(marbleName) //get the marble from chaincode state 268 if err != nil { 269 jsonResp = "{\"Error\":\"Failed to get state for " + marbleName + "\"}" 270 return shim.Error(jsonResp) 271 } else if valAsbytes == nil { 272 jsonResp = "{\"Error\":\"Marble does not exist: " + marbleName + "\"}" 273 return shim.Error(jsonResp) 274 } 275 276 err = json.Unmarshal([]byte(valAsbytes), &marbleJSON) 277 if err != nil { 278 jsonResp = "{\"Error\":\"Failed to decode JSON of: " + marbleName + "\"}" 279 return shim.Error(jsonResp) 280 } 281 282 err = stub.DelState(marbleName) //remove the marble from chaincode state 283 if err != nil { 284 return shim.Error("Failed to delete state:" + err.Error()) 285 } 286 287 // maintain the index 288 indexName := "color~name" 289 colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleJSON.Color, marbleJSON.Name}) 290 if err != nil { 291 return shim.Error(err.Error()) 292 } 293 294 // Delete index entry to state. 295 err = stub.DelState(colorNameIndexKey) 296 if err != nil { 297 return shim.Error("Failed to delete state:" + err.Error()) 298 } 299 return shim.Success(nil) 300 } 301 302 // =========================================================== 303 // transfer a marble by setting a new owner name on the marble 304 // =========================================================== 305 func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 306 307 // 0 1 308 // "name", "bob" 309 if len(args) < 2 { 310 return shim.Error("Incorrect number of arguments. Expecting 2") 311 } 312 313 marbleName := args[0] 314 newOwner := strings.ToLower(args[1]) 315 fmt.Println("- start transferMarble ", marbleName, newOwner) 316 317 marbleAsBytes, err := stub.GetState(marbleName) 318 if err != nil { 319 return shim.Error("Failed to get marble:" + err.Error()) 320 } else if marbleAsBytes == nil { 321 return shim.Error("Marble does not exist") 322 } 323 324 marbleToTransfer := marble{} 325 err = json.Unmarshal(marbleAsBytes, &marbleToTransfer) //unmarshal it aka JSON.parse() 326 if err != nil { 327 return shim.Error(err.Error()) 328 } 329 marbleToTransfer.Owner = newOwner //change the owner 330 331 marbleJSONasBytes, _ := json.Marshal(marbleToTransfer) 332 err = stub.PutState(marbleName, marbleJSONasBytes) //rewrite the marble 333 if err != nil { 334 return shim.Error(err.Error()) 335 } 336 337 fmt.Println("- end transferMarble (success)") 338 return shim.Success(nil) 339 } 340 341 // =========================================================================================== 342 // constructQueryResponseFromIterator constructs a JSON array containing query results from 343 // a given result iterator 344 // =========================================================================================== 345 func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) (*bytes.Buffer, error) { 346 // buffer is a JSON array containing QueryResults 347 var buffer bytes.Buffer 348 buffer.WriteString("[") 349 350 bArrayMemberAlreadyWritten := false 351 for resultsIterator.HasNext() { 352 queryResponse, err := resultsIterator.Next() 353 if err != nil { 354 return nil, err 355 } 356 // Add a comma before array members, suppress it for the first array member 357 if bArrayMemberAlreadyWritten { 358 buffer.WriteString(",") 359 } 360 buffer.WriteString("{\"Key\":") 361 buffer.WriteString("\"") 362 buffer.WriteString(queryResponse.Key) 363 buffer.WriteString("\"") 364 365 buffer.WriteString(", \"Record\":") 366 // Record is a JSON object, so we write as-is 367 buffer.WriteString(string(queryResponse.Value)) 368 buffer.WriteString("}") 369 bArrayMemberAlreadyWritten = true 370 } 371 buffer.WriteString("]") 372 373 return &buffer, nil 374 } 375 376 // =========================================================================================== 377 // addPaginationMetadataToQueryResults adds QueryResponseMetadata, which contains pagination 378 // info, to the constructed query results 379 // =========================================================================================== 380 func addPaginationMetadataToQueryResults(buffer *bytes.Buffer, responseMetadata *pb.QueryResponseMetadata) *bytes.Buffer { 381 382 buffer.WriteString("[{\"ResponseMetadata\":{\"RecordsCount\":") 383 buffer.WriteString("\"") 384 buffer.WriteString(fmt.Sprintf("%v", responseMetadata.FetchedRecordsCount)) 385 buffer.WriteString("\"") 386 buffer.WriteString(", \"Bookmark\":") 387 buffer.WriteString("\"") 388 buffer.WriteString(responseMetadata.Bookmark) 389 buffer.WriteString("\"}}]") 390 391 return buffer 392 } 393 394 // =========================================================================================== 395 // getMarblesByRange performs a range query based on the start and end keys provided. 396 397 // Read-only function results are not typically submitted to ordering. If the read-only 398 // results are submitted to ordering, or if the query is used in an update transaction 399 // and submitted to ordering, then the committing peers will re-execute to guarantee that 400 // result sets are stable between endorsement time and commit time. The transaction is 401 // invalidated by the committing peers if the result set has changed between endorsement 402 // time and commit time. 403 // Therefore, range queries are a safe option for performing update transactions based on query results. 404 // =========================================================================================== 405 func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { 406 407 if len(args) < 2 { 408 return shim.Error("Incorrect number of arguments. Expecting 2") 409 } 410 411 startKey := args[0] 412 endKey := args[1] 413 414 resultsIterator, err := stub.GetStateByRange(startKey, endKey) 415 if err != nil { 416 return shim.Error(err.Error()) 417 } 418 defer resultsIterator.Close() 419 420 buffer, err := constructQueryResponseFromIterator(resultsIterator) 421 if err != nil { 422 return shim.Error(err.Error()) 423 } 424 425 fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) 426 427 return shim.Success(buffer.Bytes()) 428 } 429 430 // ==== Example: GetStateByPartialCompositeKey/RangeQuery ========================================= 431 // transferMarblesBasedOnColor will transfer marbles of a given color to a certain new owner. 432 // Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. 433 // Committing peers will re-execute range queries to guarantee that result sets are stable 434 // between endorsement time and commit time. The transaction is invalidated by the 435 // committing peers if the result set has changed between endorsement time and commit time. 436 // Therefore, range queries are a safe option for performing update transactions based on query results. 437 // =========================================================================================== 438 func (t *SimpleChaincode) transferMarblesBasedOnColor(stub shim.ChaincodeStubInterface, args []string) pb.Response { 439 440 // 0 1 441 // "color", "bob" 442 if len(args) < 2 { 443 return shim.Error("Incorrect number of arguments. Expecting 2") 444 } 445 446 color := args[0] 447 newOwner := strings.ToLower(args[1]) 448 fmt.Println("- start transferMarblesBasedOnColor ", color, newOwner) 449 450 // Query the color~name index by color 451 // This will execute a key range query on all keys starting with 'color' 452 coloredMarbleResultsIterator, err := stub.GetStateByPartialCompositeKey("color~name", []string{color}) 453 if err != nil { 454 return shim.Error(err.Error()) 455 } 456 defer coloredMarbleResultsIterator.Close() 457 458 // Iterate through result set and for each marble found, transfer to newOwner 459 var i int 460 for i = 0; coloredMarbleResultsIterator.HasNext(); i++ { 461 // Note that we don't get the value (2nd return variable), we'll just get the marble name from the composite key 462 responseRange, err := coloredMarbleResultsIterator.Next() 463 if err != nil { 464 return shim.Error(err.Error()) 465 } 466 467 // get the color and name from color~name composite key 468 objectType, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key) 469 if err != nil { 470 return shim.Error(err.Error()) 471 } 472 returnedColor := compositeKeyParts[0] 473 returnedMarbleName := compositeKeyParts[1] 474 fmt.Printf("- found a marble from index:%s color:%s name:%s\n", objectType, returnedColor, returnedMarbleName) 475 476 // Now call the transfer function for the found marble. 477 // Re-use the same function that is used to transfer individual marbles 478 response := t.transferMarble(stub, []string{returnedMarbleName, newOwner}) 479 // if the transfer failed break out of loop and return error 480 if response.Status != shim.OK { 481 return shim.Error("Transfer failed: " + response.Message) 482 } 483 } 484 485 responsePayload := fmt.Sprintf("Transferred %d %s marbles to %s", i, color, newOwner) 486 fmt.Println("- end transferMarblesBasedOnColor: " + responsePayload) 487 return shim.Success([]byte(responsePayload)) 488 } 489 490 // =======Rich queries ========================================================================= 491 // Two examples of rich queries are provided below (parameterized query and ad hoc query). 492 // Rich queries pass a query string to the state database. 493 // Rich queries are only supported by state database implementations 494 // that support rich query (e.g. CouchDB). 495 // The query string is in the syntax of the underlying state database. 496 // With rich queries there is no guarantee that the result set hasn't changed between 497 // endorsement time and commit time, aka 'phantom reads'. 498 // Therefore, rich queries should not be used in update transactions, unless the 499 // application handles the possibility of result set changes between endorsement and commit time. 500 // Rich queries can be used for point-in-time queries against a peer. 501 // ============================================================================================ 502 503 // ===== Example: Parameterized rich query ================================================= 504 // queryMarblesByOwner queries for marbles based on a passed in owner. 505 // This is an example of a parameterized query where the query logic is baked into the chaincode, 506 // and accepting a single query parameter (owner). 507 // Only available on state databases that support rich query (e.g. CouchDB) 508 // ========================================================================================= 509 func (t *SimpleChaincode) queryMarblesByOwner(stub shim.ChaincodeStubInterface, args []string) pb.Response { 510 511 // 0 512 // "bob" 513 if len(args) < 1 { 514 return shim.Error("Incorrect number of arguments. Expecting 1") 515 } 516 517 owner := strings.ToLower(args[0]) 518 519 queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"marble\",\"owner\":\"%s\"}}", owner) 520 521 queryResults, err := getQueryResultForQueryString(stub, queryString) 522 if err != nil { 523 return shim.Error(err.Error()) 524 } 525 return shim.Success(queryResults) 526 } 527 528 // ===== Example: Ad hoc rich query ======================================================== 529 // queryMarbles uses a query string to perform a query for marbles. 530 // Query string matching state database syntax is passed in and executed as is. 531 // Supports ad hoc queries that can be defined at runtime by the client. 532 // If this is not desired, follow the queryMarblesForOwner example for parameterized queries. 533 // Only available on state databases that support rich query (e.g. CouchDB) 534 // ========================================================================================= 535 func (t *SimpleChaincode) queryMarbles(stub shim.ChaincodeStubInterface, args []string) pb.Response { 536 537 // 0 538 // "queryString" 539 if len(args) < 1 { 540 return shim.Error("Incorrect number of arguments. Expecting 1") 541 } 542 543 queryString := args[0] 544 545 queryResults, err := getQueryResultForQueryString(stub, queryString) 546 if err != nil { 547 return shim.Error(err.Error()) 548 } 549 return shim.Success(queryResults) 550 } 551 552 // ========================================================================================= 553 // getQueryResultForQueryString executes the passed in query string. 554 // Result set is built and returned as a byte array containing the JSON results. 555 // ========================================================================================= 556 func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { 557 558 fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) 559 560 resultsIterator, err := stub.GetQueryResult(queryString) 561 if err != nil { 562 return nil, err 563 } 564 defer resultsIterator.Close() 565 566 buffer, err := constructQueryResponseFromIterator(resultsIterator) 567 if err != nil { 568 return nil, err 569 } 570 571 fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) 572 573 return buffer.Bytes(), nil 574 } 575 576 // ====== Pagination ========================================================================= 577 // Pagination provides a method to retrieve records with a defined pagesize and 578 // start point (bookmark). An empty string bookmark defines the first "page" of a query 579 // result. Paginated queries return a bookmark that can be used in 580 // the next query to retrieve the next page of results. Paginated queries extend 581 // rich queries and range queries to include a pagesize and bookmark. 582 // 583 // Two examples are provided in this example. The first is getMarblesByRangeWithPagination 584 // which executes a paginated range query. 585 // The second example is a paginated query for rich ad-hoc queries. 586 // ========================================================================================= 587 588 // ====== Example: Pagination with Range Query =============================================== 589 // getMarblesByRangeWithPagination performs a range query based on the start & end key, 590 // page size and a bookmark. 591 592 // The number of fetched records will be equal to or lesser than the page size. 593 // Paginated range queries are only valid for read only transactions. 594 // =========================================================================================== 595 func (t *SimpleChaincode) getMarblesByRangeWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { 596 597 if len(args) < 4 { 598 return shim.Error("Incorrect number of arguments. Expecting 4") 599 } 600 601 startKey := args[0] 602 endKey := args[1] 603 //return type of ParseInt is int64 604 pageSize, err := strconv.ParseInt(args[2], 10, 32) 605 if err != nil { 606 return shim.Error(err.Error()) 607 } 608 bookmark := args[3] 609 610 resultsIterator, responseMetadata, err := stub.GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark) 611 if err != nil { 612 return shim.Error(err.Error()) 613 } 614 defer resultsIterator.Close() 615 616 buffer, err := constructQueryResponseFromIterator(resultsIterator) 617 if err != nil { 618 return shim.Error(err.Error()) 619 } 620 621 bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) 622 623 fmt.Printf("- getMarblesByRange queryResult:\n%s\n", bufferWithPaginationInfo.String()) 624 625 return shim.Success(buffer.Bytes()) 626 } 627 628 // ===== Example: Pagination with Ad hoc Rich Query ======================================================== 629 // queryMarblesWithPagination uses a query string, page size and a bookmark to perform a query 630 // for marbles. Query string matching state database syntax is passed in and executed as is. 631 // The number of fetched records would be equal to or lesser than the specified page size. 632 // Supports ad hoc queries that can be defined at runtime by the client. 633 // If this is not desired, follow the queryMarblesForOwner example for parameterized queries. 634 // Only available on state databases that support rich query (e.g. CouchDB) 635 // Paginated queries are only valid for read only transactions. 636 // ========================================================================================= 637 func (t *SimpleChaincode) queryMarblesWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { 638 639 // 0 640 // "queryString" 641 if len(args) < 3 { 642 return shim.Error("Incorrect number of arguments. Expecting 3") 643 } 644 645 queryString := args[0] 646 //return type of ParseInt is int64 647 pageSize, err := strconv.ParseInt(args[1], 10, 32) 648 if err != nil { 649 return shim.Error(err.Error()) 650 } 651 bookmark := args[2] 652 653 queryResults, err := getQueryResultForQueryStringWithPagination(stub, queryString, int32(pageSize), bookmark) 654 if err != nil { 655 return shim.Error(err.Error()) 656 } 657 return shim.Success(queryResults) 658 } 659 660 // ========================================================================================= 661 // getQueryResultForQueryStringWithPagination executes the passed in query string with 662 // pagination info. Result set is built and returned as a byte array containing the JSON results. 663 // ========================================================================================= 664 func getQueryResultForQueryStringWithPagination(stub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) ([]byte, error) { 665 666 fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) 667 668 resultsIterator, responseMetadata, err := stub.GetQueryResultWithPagination(queryString, pageSize, bookmark) 669 if err != nil { 670 return nil, err 671 } 672 defer resultsIterator.Close() 673 674 buffer, err := constructQueryResponseFromIterator(resultsIterator) 675 if err != nil { 676 return nil, err 677 } 678 679 bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) 680 681 fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", bufferWithPaginationInfo.String()) 682 683 return buffer.Bytes(), nil 684 } 685 686 func (t *SimpleChaincode) getHistoryForMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { 687 688 if len(args) < 1 { 689 return shim.Error("Incorrect number of arguments. Expecting 1") 690 } 691 692 marbleName := args[0] 693 694 fmt.Printf("- start getHistoryForMarble: %s\n", marbleName) 695 696 resultsIterator, err := stub.GetHistoryForKey(marbleName) 697 if err != nil { 698 return shim.Error(err.Error()) 699 } 700 defer resultsIterator.Close() 701 702 // buffer is a JSON array containing historic values for the marble 703 var buffer bytes.Buffer 704 buffer.WriteString("[") 705 706 bArrayMemberAlreadyWritten := false 707 for resultsIterator.HasNext() { 708 response, err := resultsIterator.Next() 709 if err != nil { 710 return shim.Error(err.Error()) 711 } 712 // Add a comma before array members, suppress it for the first array member 713 if bArrayMemberAlreadyWritten { 714 buffer.WriteString(",") 715 } 716 buffer.WriteString("{\"TxId\":") 717 buffer.WriteString("\"") 718 buffer.WriteString(response.TxId) 719 buffer.WriteString("\"") 720 721 buffer.WriteString(", \"Value\":") 722 // if it was a delete operation on given key, then we need to set the 723 //corresponding value null. Else, we will write the response.Value 724 //as-is (as the Value itself a JSON marble) 725 if response.IsDelete { 726 buffer.WriteString("null") 727 } else { 728 buffer.WriteString(string(response.Value)) 729 } 730 731 buffer.WriteString(", \"Timestamp\":") 732 buffer.WriteString("\"") 733 buffer.WriteString(time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).String()) 734 buffer.WriteString("\"") 735 736 buffer.WriteString(", \"IsDelete\":") 737 buffer.WriteString("\"") 738 buffer.WriteString(strconv.FormatBool(response.IsDelete)) 739 buffer.WriteString("\"") 740 741 buffer.WriteString("}") 742 bArrayMemberAlreadyWritten = true 743 } 744 buffer.WriteString("]") 745 746 fmt.Printf("- getHistoryForMarble returning:\n%s\n", buffer.String()) 747 748 return shim.Success(buffer.Bytes()) 749 }