From b266c7bb9bb1954f9b374288cdf0d6d85c9a789d Mon Sep 17 00:00:00 2001 From: Balaji Viswanathan Date: Tue, 14 Feb 2017 13:40:37 +0530 Subject: [PATCH] FAB-2228: CouchDB docs to have consistent header Similar to Couch JSON documents, the version information is now stored in header elements of the JSON record. The ReadDoc and SaveDoc methods operate on CouchDoc type which encapsulates the JSON and binary data. Change-Id: I4343161a27fa39a79788063a2b37570046bb782b Signed-off-by: Balaji Viswanathan --- .../statedb/statecouchdb/statecouchdb.go | 115 +++++----- core/ledger/util/couchdb/couchdb.go | 205 ++++++++++-------- core/ledger/util/couchdb/couchdb_test.go | 43 ++-- 3 files changed, 196 insertions(+), 167 deletions(-) mode change 100644 => 100755 core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go diff --git a/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go b/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go old mode 100644 new mode 100755 index fd1ed5db474..9cfde595200 --- a/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go +++ b/core/ledger/kvledger/txmgmt/statedb/statecouchdb/statecouchdb.go @@ -36,7 +36,8 @@ var logger = logging.MustGetLogger("statecouchdb") var compositeKeySep = []byte{0x00} var lastKeyIndicator = byte(0x01) -var savePointKey = []byte{0x00} + +var binaryWrapper = "valueBytes" // VersionedDBProvider implements interface VersionedDBProvider type VersionedDBProvider struct { @@ -119,70 +120,71 @@ func (vdb *VersionedDB) GetState(namespace string, key string) (*statedb.Version compositeKey := constructCompositeKey(namespace, key) - docBytes, _, err := vdb.db.ReadDoc(string(compositeKey)) + couchDoc, _, err := vdb.db.ReadDoc(string(compositeKey)) if err != nil { return nil, err } - if docBytes == nil { + if couchDoc == nil { return nil, nil } // trace the first 200 bytes of value only, in case it is huge - if docBytes != nil && logger.IsEnabledFor(logging.DEBUG) { - if len(docBytes) < 200 { - logger.Debugf("getCommittedValueAndVersion() Read docBytes %s", docBytes) + if couchDoc.JSONValue != nil && logger.IsEnabledFor(logging.DEBUG) { + if len(couchDoc.JSONValue) < 200 { + logger.Debugf("getCommittedValueAndVersion() Read docBytes %s", couchDoc.JSONValue) } else { - logger.Debugf("getCommittedValueAndVersion() Read docBytes %s...", docBytes[0:200]) + logger.Debugf("getCommittedValueAndVersion() Read docBytes %s...", couchDoc.JSONValue[0:200]) } } //remove the data wrapper and return the value and version - returnValue, returnVersion := removeDataWrapper(docBytes) + returnValue, returnVersion := removeDataWrapper(couchDoc.JSONValue, couchDoc.Attachments) return &statedb.VersionedValue{Value: returnValue, Version: &returnVersion}, nil } -func removeDataWrapper(wrappedValue []byte) ([]byte, version.Height) { +func removeDataWrapper(wrappedValue []byte, attachments []couchdb.Attachment) ([]byte, version.Height) { //initialize the return value - returnValue := []byte{} + returnValue := []byte{} // TODO: empty byte or nil //initialize a default return version returnVersion := version.NewHeight(0, 0) - //if this is a JSON, then remove the data wrapper - if couchdb.IsJSON(string(wrappedValue)) { - - //create a generic map for the json - jsonResult := make(map[string]interface{}) + //create a generic map for the json + jsonResult := make(map[string]interface{}) - //unmarshal the selected json into the generic map - json.Unmarshal(wrappedValue, &jsonResult) + //unmarshal the selected json into the generic map + json.Unmarshal(wrappedValue, &jsonResult) + // handle binary or json data + if jsonResult[dataWrapper] == nil && attachments != nil { // binary attachment + // get binary data from attachment + for _, attachment := range attachments { + if attachment.Name == binaryWrapper { + returnValue = attachment.AttachmentBytes + } + } + } else { //place the result json in the data key returnMap := jsonResult[dataWrapper] //marshal the mapped data. this wrappers the result in a key named "data" returnValue, _ = json.Marshal(returnMap) - //create an array containing the blockNum and txNum - versionArray := strings.Split(fmt.Sprintf("%s", jsonResult["version"]), ":") - - //convert the blockNum from String to unsigned int - blockNum, _ := strconv.ParseUint(versionArray[0], 10, 64) + } - //convert the txNum from String to unsigned int - txNum, _ := strconv.ParseUint(versionArray[1], 10, 64) + //create an array containing the blockNum and txNum + versionArray := strings.Split(fmt.Sprintf("%s", jsonResult["version"]), ":") - //create the version based on the blockNum and txNum - returnVersion = version.NewHeight(blockNum, txNum) + //convert the blockNum from String to unsigned int + blockNum, _ := strconv.ParseUint(versionArray[0], 10, 64) - } else { + //convert the txNum from String to unsigned int + txNum, _ := strconv.ParseUint(versionArray[1], 10, 64) - //this is a binary, so decode the value and version from the binary - returnValue, returnVersion = statedb.DecodeValue(wrappedValue) - - } + //create the version based on the blockNum and txNum + returnVersion = version.NewHeight(blockNum, txNum) return returnValue, *returnVersion @@ -262,41 +264,34 @@ func (vdb *VersionedDB) ApplyUpdates(batch *statedb.UpdateBatch, height *version vdb.db.DeleteDoc(string(compositeKey), "") } else { + couchDoc := &couchdb.CouchDoc{} //Check to see if the value is a valid JSON //If this is not a valid JSON, then store as an attachment if couchdb.IsJSON(string(vv.Value)) { - - // SaveDoc using couchdb client and use JSON format - rev, err := vdb.db.SaveDoc(string(compositeKey), "", addVersionAndChainCodeID(vv.Value, ns, vv.Version), nil) - if err != nil { - logger.Errorf("Error during Commit(): %s\n", err.Error()) - return err - } - if rev != "" { - logger.Debugf("Saved document revision number: %s\n", rev) - } - + // Handle it as json + couchDoc.JSONValue = addVersionAndChainCodeID(vv.Value, ns, vv.Version) } else { // if the data is not JSON, save as binary attachment in Couch - //Create an attachment structure and load the bytes attachment := &couchdb.Attachment{} - attachment.AttachmentBytes = statedb.EncodeValue(vv.Value, vv.Version) + attachment.AttachmentBytes = vv.Value attachment.ContentType = "application/octet-stream" - attachment.Name = "valueBytes" + attachment.Name = binaryWrapper attachments := []couchdb.Attachment{} attachments = append(attachments, *attachment) + couchDoc.Attachments = append(couchDoc.Attachments, *attachment) + couchDoc.JSONValue = addVersionAndChainCodeID(nil, ns, vv.Version) + } - // SaveDoc using couchdb client and use attachment to persist the binary data - rev, err := vdb.db.SaveDoc(string(compositeKey), "", addVersionAndChainCodeID(nil, ns, vv.Version), attachments) - if err != nil { - logger.Errorf("Error during Commit(): %s\n", err.Error()) - return err - } - if rev != "" { - logger.Debugf("Saved document revision number: %s\n", rev) - } + // SaveDoc using couchdb client and use attachment to persist the binary data + rev, err := vdb.db.SaveDoc(string(compositeKey), "", couchDoc) + if err != nil { + logger.Errorf("Error during Commit(): %s\n", err.Error()) + return err + } + if rev != "" { + logger.Debugf("Saved document revision number: %s\n", rev) } } } @@ -381,7 +376,7 @@ func (vdb *VersionedDB) recordSavepoint(height *version.Height) error { } // SaveDoc using couchdb client and use JSON format - _, err = vdb.db.SaveDoc(savepointDocID, "", savepointDocJSON, nil) + _, err = vdb.db.SaveDoc(savepointDocID, "", &couchdb.CouchDoc{JSONValue: savepointDocJSON, Attachments: nil}) if err != nil { logger.Errorf("Failed to save the savepoint to DB %s\n", err.Error()) return err @@ -400,19 +395,19 @@ func (vdb *VersionedDB) recordSavepoint(height *version.Height) error { func (vdb *VersionedDB) GetLatestSavePoint() (*version.Height, error) { var err error - savepointJSON, _, err := vdb.db.ReadDoc(savepointDocID) + couchDoc, _, err := vdb.db.ReadDoc(savepointDocID) if err != nil { logger.Errorf("Failed to read savepoint data %s\n", err.Error()) return &version.Height{BlockNum: 0, TxNum: 0}, err } // ReadDoc() not found (404) will result in nil response, in these cases return height 0 - if savepointJSON == nil { + if couchDoc.JSONValue == nil { return &version.Height{BlockNum: 0, TxNum: 0}, nil } savepointDoc := &couchSavepointData{} - err = json.Unmarshal(savepointJSON, &savepointDoc) + err = json.Unmarshal(couchDoc.JSONValue, &savepointDoc) if err != nil { logger.Errorf("Failed to unmarshal savepoint data %s\n", err.Error()) return &version.Height{BlockNum: 0, TxNum: 0}, err @@ -456,7 +451,7 @@ func (scanner *kvScanner) Next() (statedb.QueryResult, error) { _, key := splitCompositeKey([]byte(selectedKV.ID)) //remove the data wrapper and return the value and version - returnValue, returnVersion := removeDataWrapper(selectedKV.Value) + returnValue, returnVersion := removeDataWrapper(selectedKV.Value, selectedKV.Attachments) return &statedb.VersionedKV{ CompositeKey: statedb.CompositeKey{Namespace: scanner.namespace, Key: key}, @@ -489,7 +484,7 @@ func (scanner *queryScanner) Next() (statedb.QueryResult, error) { namespace, key := splitCompositeKey([]byte(selectedResultRecord.ID)) //remove the data wrapper and return the value and version - returnValue, returnVersion := removeDataWrapper(selectedResultRecord.Value) + returnValue, returnVersion := removeDataWrapper(selectedResultRecord.Value, selectedResultRecord.Attachments) return &statedb.VersionedQueryRecord{ Namespace: namespace, diff --git a/core/ledger/util/couchdb/couchdb.go b/core/ledger/util/couchdb/couchdb.go index 3518f14365a..a275458fbe2 100644 --- a/core/ledger/util/couchdb/couchdb.go +++ b/core/ledger/util/couchdb/couchdb.go @@ -103,8 +103,9 @@ type DocID struct { //QueryResult is used for returning query results from CouchDB type QueryResult struct { - ID string - Value []byte + ID string + Value []byte + Attachments []Attachment } //CouchConnectionDef contains parameters @@ -153,6 +154,12 @@ type FileDetails struct { Length int `json:"length"` } +//CouchDoc defines the structure for a JSON document value +type CouchDoc struct { + JSONValue []byte + Attachments []Attachment +} + //CreateConnectionDefinition for a new client connection func CreateConnectionDefinition(couchDBAddress, username, password string) (*CouchConnectionDef, error) { @@ -305,9 +312,14 @@ func (dbclient *CouchDatabase) EnsureFullCommit() (*DBOperationResponse, error) logger.Debugf("Entering EnsureFullCommit()") - url := fmt.Sprintf("%s/%s/_ensure_full_commit", dbclient.couchInstance.conf.URL, dbclient.dbName) + connectURL, err := url.Parse(dbclient.couchInstance.conf.URL) + if err != nil { + logger.Errorf("URL parse error: %s", err.Error()) + return nil, err + } + connectURL.Path = dbclient.dbName + "/_ensure_full_commit" - resp, _, err := dbclient.handleRequest(http.MethodPost, url, nil, "", "") + resp, _, err := dbclient.handleRequest(http.MethodPost, connectURL.String(), nil, "", "") if err != nil { logger.Errorf("Failed to invoke _ensure_full_commit Error: %s\n", err.Error()) return nil, err @@ -333,20 +345,24 @@ func (dbclient *CouchDatabase) EnsureFullCommit() (*DBOperationResponse, error) } //SaveDoc method provides a function to save a document, id and byte array -func (dbclient *CouchDatabase) SaveDoc(id string, rev string, bytesDoc []byte, attachments []Attachment) (string, error) { +func (dbclient *CouchDatabase) SaveDoc(id string, rev string, couchDoc *CouchDoc) (string, error) { + logger.Debugf("Entering SaveDoc()") if !utf8.ValidString(id) { return "", fmt.Errorf("doc id [%x] not a valid utf8 string", id) } + saveURL, err := url.Parse(dbclient.couchInstance.conf.URL) if err != nil { logger.Errorf("URL parse error: %s", err.Error()) return "", err } + saveURL.Path = dbclient.dbName // id can contain a '/', so encode separately saveURL = &url.URL{Opaque: saveURL.String() + "/" + encodePathElement(id)} - logger.Debugf(" id=%s, value=%s", id, string(bytesDoc)) + + logger.Debugf(" id=%s, value=%s, attachments=%d", id, string(couchDoc.JSONValue), len(couchDoc.Attachments)) if rev == "" { @@ -370,20 +386,20 @@ func (dbclient *CouchDatabase) SaveDoc(id string, rev string, bytesDoc []byte, a defaultBoundary := "" //check to see if attachments is nil, if so, then this is a JSON only - if attachments == nil { + if couchDoc.Attachments == nil { //Test to see if this is a valid JSON - if IsJSON(string(bytesDoc)) != true { + if IsJSON(string(couchDoc.JSONValue)) != true { return "", fmt.Errorf("JSON format is not valid") } // if there are no attachments, then use the bytes passed in as the JSON - data.ReadFrom(bytes.NewReader(bytesDoc)) + data.ReadFrom(bytes.NewReader(couchDoc.JSONValue)) } else { // there are attachments //attachments are included, create the multipart definition - multipartData, multipartBoundary, err3 := createAttachmentPart(bytesDoc, attachments, defaultBoundary) + multipartData, multipartBoundary, err3 := createAttachmentPart(couchDoc, defaultBoundary) if err3 != nil { return "", err3 } @@ -415,7 +431,7 @@ func (dbclient *CouchDatabase) SaveDoc(id string, rev string, bytesDoc []byte, a } -func createAttachmentPart(data []byte, attachments []Attachment, defaultBoundary string) (bytes.Buffer, string, error) { +func createAttachmentPart(couchDoc *CouchDoc, defaultBoundary string) (bytes.Buffer, string, error) { //Create a buffer for writing the result writeBuffer := new(bytes.Buffer) @@ -428,7 +444,7 @@ func createAttachmentPart(data []byte, attachments []Attachment, defaultBoundary fileAttachments := map[string]FileDetails{} - for _, attachment := range attachments { + for _, attachment := range couchDoc.Attachments { fileAttachments[attachment.Name] = FileDetails{true, attachment.ContentType, len(attachment.AttachmentBytes)} } @@ -436,12 +452,12 @@ func createAttachmentPart(data []byte, attachments []Attachment, defaultBoundary "_attachments": fileAttachments} //Add any data uploaded with the files - if data != nil { + if couchDoc.JSONValue != nil { //create a generic map genericMap := make(map[string]interface{}) //unmarshal the data into the generic map - json.Unmarshal(data, &genericMap) + json.Unmarshal(couchDoc.JSONValue, &genericMap) //add all key/values to the attachmentJSONMap for jsonKey, jsonValue := range genericMap { @@ -464,7 +480,7 @@ func createAttachmentPart(data []byte, attachments []Attachment, defaultBoundary part.Write(filesForUpload) - for _, attachment := range attachments { + for _, attachment := range couchDoc.Attachments { header := make(textproto.MIMEHeader) part, err2 := writer.CreatePart(header) @@ -499,12 +515,13 @@ func getRevisionHeader(resp *http.Response) (string, error) { } //ReadDoc method provides function to retrieve a document from the database by id -func (dbclient *CouchDatabase) ReadDoc(id string) ([]byte, string, error) { - +func (dbclient *CouchDatabase) ReadDoc(id string) (*CouchDoc, string, error) { + var couchDoc CouchDoc logger.Debugf("Entering ReadDoc() id=%s", id) if !utf8.ValidString(id) { return nil, "", fmt.Errorf("doc id [%x] not a valid utf8 string", id) } + readURL, err := url.Parse(dbclient.couchInstance.conf.URL) if err != nil { logger.Errorf("URL parse error: %s", err.Error()) @@ -513,6 +530,7 @@ func (dbclient *CouchDatabase) ReadDoc(id string) ([]byte, string, error) { readURL.Path = dbclient.dbName // id can contain a '/', so encode separately readURL = &url.URL{Opaque: readURL.String() + "/" + encodePathElement(id)} + query := readURL.Query() query.Add("attachments", "true") @@ -545,81 +563,82 @@ func (dbclient *CouchDatabase) ReadDoc(id string) ([]byte, string, error) { //check to see if the is multipart, handle as attachment if multipart is detected if strings.HasPrefix(mediaType, "multipart/") { - //Set up the multipart reader based on the boundary multipartReader := multipart.NewReader(resp.Body, params["boundary"]) for { - p, err := multipartReader.NextPart() - if err == io.EOF { - return nil, "", err + break // processed all parts } - if err != nil { return nil, "", err } - logger.Debugf("part header=%s", p.Header) - - //See if the part is gzip encoded - switch p.Header.Get("Content-Encoding") { - case "gzip": - - var respBody []byte - - gr, err := gzip.NewReader(p) - if err != nil { - return nil, "", err - } - respBody, err = ioutil.ReadAll(gr) - if err != nil { - return nil, "", err - } - - logger.Debugf("Retrieved attachment data") - - if p.Header.Get("Content-Disposition") == "attachment; filename=\"valueBytes\"" { - - return respBody, revision, nil - - } - - default: + defer p.Close() - //retrieve the data, this is not gzip + logger.Debugf("part header=%s", p.Header) + switch p.Header.Get("Content-Type") { + case "application/json": partdata, err := ioutil.ReadAll(p) if err != nil { return nil, "", err } - logger.Debugf("Retrieved attachment data") - - if p.Header.Get("Content-Disposition") == "attachment; filename=\"valueBytes\"" { - - return partdata, revision, nil - - } - - } - - } - - } else { - - //handle as JSON document - jsonDoc, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, "", err - } - - logger.Debugf("Read document, id=%s, value=%s", id, string(jsonDoc)) - - logger.Debugf("Exiting ReadDoc()") + couchDoc.JSONValue = partdata + default: - return jsonDoc, revision, nil + //Create an attachment structure and load it + attachment := Attachment{} + attachment.ContentType = p.Header.Get("Content-Type") + contentDispositionParts := strings.Split(p.Header.Get("Content-Disposition"), ";") + if strings.TrimSpace(contentDispositionParts[0]) == "attachment" { + switch p.Header.Get("Content-Encoding") { + case "gzip": //See if the part is gzip encoded + + var respBody []byte + + gr, err := gzip.NewReader(p) + if err != nil { + return nil, "", err + } + respBody, err = ioutil.ReadAll(gr) + if err != nil { + return nil, "", err + } + + logger.Debugf("Retrieved attachment data") + attachment.AttachmentBytes = respBody + attachment.Name = p.FileName() + couchDoc.Attachments = append(couchDoc.Attachments, attachment) + + default: + + //retrieve the data, this is not gzip + partdata, err := ioutil.ReadAll(p) + if err != nil { + return nil, "", err + } + logger.Debugf("Retrieved attachment data") + attachment.AttachmentBytes = partdata + attachment.Name = p.FileName() + couchDoc.Attachments = append(couchDoc.Attachments, attachment) + + } // end content-encoding switch + } // end if attachment + } // end content-type switch + } // for all multiparts + + return &couchDoc, revision, nil + } + //handle as JSON document + couchDoc.JSONValue, err = ioutil.ReadAll(resp.Body) + if err != nil { + return nil, "", err } + logger.Debugf("Read document, id=%s, value=%s", id, string(couchDoc.JSONValue)) + logger.Debugf("Exiting ReadDoc()") + return &couchDoc, revision, nil } //ReadDocRange method provides function to a range of documents based on the start and end keys @@ -647,12 +666,13 @@ func (dbclient *CouchDatabase) ReadDocRange(startKey, endKey string, limit, skip queryParms.Add("inclusive_end", "false") // endkey should be exclusive to be consistent with goleveldb //Append the startKey if provided + if startKey != "" { var err error if startKey, err = encodeForJSON(startKey); err != nil { return nil, err } - queryParms.Add("startkey", startKey) + queryParms.Add("startkey", "\""+startKey+"\"") } //Append the endKey if provided @@ -661,7 +681,7 @@ func (dbclient *CouchDatabase) ReadDocRange(startKey, endKey string, limit, skip if endKey, err = encodeForJSON(endKey); err != nil { return nil, err } - queryParms.Add("endkey", endKey) + queryParms.Add("endkey", "\""+endKey+"\"") } rangeURL.RawQuery = queryParms.Encode() @@ -704,23 +724,21 @@ func (dbclient *CouchDatabase) ReadDocRange(startKey, endKey string, limit, skip if jsonDoc.Attachments != nil { - logger.Debugf("Adding binary docment for id: %s", jsonDoc.ID) + logger.Debugf("Adding JSON document and attachments for id: %s", jsonDoc.ID) - binaryDocument, _, err := dbclient.ReadDoc(jsonDoc.ID) + couchDoc, _, err := dbclient.ReadDoc(jsonDoc.ID) if err != nil { return nil, err } - var addDocument = &QueryResult{jsonDoc.ID, binaryDocument} - + var addDocument = &QueryResult{jsonDoc.ID, couchDoc.JSONValue, couchDoc.Attachments} results = append(results, *addDocument) } else { logger.Debugf("Adding json docment for id: %s", jsonDoc.ID) - var addDocument = &QueryResult{jsonDoc.ID, row.Doc} - + var addDocument = &QueryResult{jsonDoc.ID, row.Doc, nil} results = append(results, *addDocument) } @@ -837,20 +855,31 @@ func (dbclient *CouchDatabase) QueryDocuments(query string, limit, skip int) (*[ for _, row := range jsonResponse.Docs { - var jsonDoc = &DocID{} + var jsonDoc = &Doc{} err3 := json.Unmarshal(row, &jsonDoc) if err3 != nil { return nil, err3 } - logger.Debugf("Adding row to resultset: %s", row) + if jsonDoc.Attachments != nil { + + logger.Debugf("Adding JSON docment and attachments for id: %s", jsonDoc.ID) + + couchDoc, _, err := dbclient.ReadDoc(jsonDoc.ID) + if err != nil { + return nil, err + } + var addDocument = &QueryResult{ID: jsonDoc.ID, Value: couchDoc.JSONValue, Attachments: couchDoc.Attachments} + results = append(results, *addDocument) - var addDocument = &QueryResult{jsonDoc.ID, row} + } else { + logger.Debugf("Adding json docment for id: %s", jsonDoc.ID) + var addDocument = &QueryResult{ID: jsonDoc.ID, Value: row, Attachments: nil} - results = append(results, *addDocument) + results = append(results, *addDocument) + } } - logger.Debugf("Exiting QueryDocuments()") return &results, nil @@ -981,5 +1010,7 @@ func encodeForJSON(str string) (string, error) { if err := encoder.Encode(str); err != nil { return "", err } - return buf.String(), nil + // Encode adds double quotes to string and terminates with \n - stripping them as bytes as they are all ascii(0-127) + buffer := buf.Bytes() + return string(buffer[1 : len(buffer)-2]), nil } diff --git a/core/ledger/util/couchdb/couchdb_test.go b/core/ledger/util/couchdb/couchdb_test.go index 1217fc26ffd..5f1d4ebaf2a 100644 --- a/core/ledger/util/couchdb/couchdb_test.go +++ b/core/ledger/util/couchdb/couchdb_test.go @@ -95,7 +95,7 @@ func TestDBCreateSaveWithoutRevision(t *testing.T) { testutil.AssertNoError(t, errdb, fmt.Sprintf("Error when trying to create database")) //Save the test document - _, saveerr := db.SaveDoc("2", "", assetJSON, nil) + _, saveerr := db.SaveDoc("2", "", &CouchDoc{JSONValue: assetJSON, Attachments: nil}) testutil.AssertNoError(t, saveerr, fmt.Sprintf("Error when trying to save a document")) } @@ -115,7 +115,7 @@ func TestDBBadConnection(t *testing.T) { testutil.AssertError(t, errdb, fmt.Sprintf("Error should have been thrown while creating a database with an invalid connecion")) //Save the test document - _, saveerr := db.SaveDoc("3", "", assetJSON, nil) + _, saveerr := db.SaveDoc("3", "", &CouchDoc{JSONValue: assetJSON, Attachments: nil}) testutil.AssertError(t, saveerr, fmt.Sprintf("Error should have been thrown while saving a document with an invalid connecion")) //Retrieve the updated test document @@ -130,7 +130,7 @@ func TestDBCreateDatabaseAndPersist(t *testing.T) { if ledgerconfig.IsCouchDBEnabled() == true { cleanup() - defer cleanup() + //defer cleanup() //create a new instance and database object couchInstance, err := CreateCouchInstance(connectURL, username, password) @@ -147,7 +147,7 @@ func TestDBCreateDatabaseAndPersist(t *testing.T) { testutil.AssertEquals(t, dbResp.DbName, database) //Save the test document - _, saveerr := db.SaveDoc("idWith/slash", "", assetJSON, nil) + _, saveerr := db.SaveDoc("idWith/slash", "", &CouchDoc{JSONValue: assetJSON, Attachments: nil}) testutil.AssertNoError(t, saveerr, fmt.Sprintf("Error when trying to save a document")) //Retrieve the test document @@ -156,13 +156,14 @@ func TestDBCreateDatabaseAndPersist(t *testing.T) { //Unmarshal the document to Asset structure assetResp := &Asset{} - json.Unmarshal(dbGetResp, &assetResp) + geterr = json.Unmarshal(dbGetResp.JSONValue, &assetResp) + testutil.AssertNoError(t, geterr, fmt.Sprintf("Error when trying to retrieve a document")) //Verify the owner retrieved matches testutil.AssertEquals(t, assetResp.Owner, "jerry") //Save the test document - _, saveerr = db.SaveDoc("1", "", assetJSON, nil) + _, saveerr = db.SaveDoc("1", "", &CouchDoc{JSONValue: assetJSON, Attachments: nil}) testutil.AssertNoError(t, saveerr, fmt.Sprintf("Error when trying to save a document")) //Retrieve the test document @@ -171,7 +172,8 @@ func TestDBCreateDatabaseAndPersist(t *testing.T) { //Unmarshal the document to Asset structure assetResp = &Asset{} - json.Unmarshal(dbGetResp, &assetResp) + geterr = json.Unmarshal(dbGetResp.JSONValue, &assetResp) + testutil.AssertNoError(t, geterr, fmt.Sprintf("Error when trying to retrieve a document")) //Verify the owner retrieved matches testutil.AssertEquals(t, assetResp.Owner, "jerry") @@ -183,7 +185,7 @@ func TestDBCreateDatabaseAndPersist(t *testing.T) { assetDocUpdated, _ := json.Marshal(assetResp) //Save the updated test document - _, saveerr = db.SaveDoc("1", "", assetDocUpdated, nil) + _, saveerr = db.SaveDoc("1", "", &CouchDoc{JSONValue: assetDocUpdated, Attachments: nil}) testutil.AssertNoError(t, saveerr, fmt.Sprintf("Error when trying to save the updated document")) //Retrieve the updated test document @@ -192,7 +194,7 @@ func TestDBCreateDatabaseAndPersist(t *testing.T) { //Unmarshal the document to Asset structure assetResp = &Asset{} - json.Unmarshal(dbGetResp, &assetResp) + json.Unmarshal(dbGetResp.JSONValue, &assetResp) //Assert that the update was saved and retrieved testutil.AssertEquals(t, assetResp.Owner, "bob") @@ -233,7 +235,7 @@ func TestDBBadJSON(t *testing.T) { badJSON := []byte(`{"asset_name"}`) //Save the test document - _, saveerr := db.SaveDoc("1", "", badJSON, nil) + _, saveerr := db.SaveDoc("1", "", &CouchDoc{JSONValue: badJSON, Attachments: nil}) testutil.AssertError(t, saveerr, fmt.Sprintf("Error should have been thrown for a bad JSON")) } @@ -266,16 +268,19 @@ func TestPrefixScan(t *testing.T) { id1 := string(0) + string(i) + string(0) id2 := string(0) + string(i) + string(1) id3 := string(0) + string(i) + string(utf8.MaxRune-1) - _, saveerr := db.SaveDoc(id1, "", assetJSON, nil) + _, saveerr := db.SaveDoc(id1, "", &CouchDoc{JSONValue: assetJSON, Attachments: nil}) testutil.AssertNoError(t, saveerr, fmt.Sprintf("Error when trying to save a document")) - _, saveerr = db.SaveDoc(id2, "", assetJSON, nil) + _, saveerr = db.SaveDoc(id2, "", &CouchDoc{JSONValue: assetJSON, Attachments: nil}) testutil.AssertNoError(t, saveerr, fmt.Sprintf("Error when trying to save a document")) - _, saveerr = db.SaveDoc(id3, "", assetJSON, nil) + _, saveerr = db.SaveDoc(id3, "", &CouchDoc{JSONValue: assetJSON, Attachments: nil}) testutil.AssertNoError(t, saveerr, fmt.Sprintf("Error when trying to save a document")) } startKey := string(0) + string(10) endKey := startKey + string(utf8.MaxRune) + _, _, geterr := db.ReadDoc(endKey) + testutil.AssertNoError(t, geterr, fmt.Sprintf("Error when trying to get lastkey")) + resultsPtr, geterr := db.ReadDocRange(startKey, endKey, 1000, 0) testutil.AssertNoError(t, geterr, fmt.Sprintf("Error when trying to perform a range scan")) testutil.AssertNotNil(t, resultsPtr) @@ -320,16 +325,14 @@ func TestDBSaveAttachment(t *testing.T) { testutil.AssertNoError(t, errdb, fmt.Sprintf("Error when trying to create database")) //Save the test document - _, saveerr := db.SaveDoc("10", "", nil, attachments) + _, saveerr := db.SaveDoc("10", "", &CouchDoc{JSONValue: nil, Attachments: attachments}) testutil.AssertNoError(t, saveerr, fmt.Sprintf("Error when trying to save a document")) //Attempt to retrieve the updated test document with attachments - returnDoc, _, geterr2 := db.ReadDoc("10") + couchDoc, _, geterr2 := db.ReadDoc("10") testutil.AssertNoError(t, geterr2, fmt.Sprintf("Error when trying to retrieve a document with attachment")) - - //Test to see that the result from CouchDB matches the initial text - testutil.AssertEquals(t, string(returnDoc), string(byteText)) - + testutil.AssertNotNil(t, couchDoc.Attachments) + testutil.AssertEquals(t, couchDoc.Attachments[0].AttachmentBytes, byteText) } } @@ -350,7 +353,7 @@ func TestDBDeleteDocument(t *testing.T) { testutil.AssertNoError(t, errdb, fmt.Sprintf("Error when trying to create database")) //Save the test document - _, saveerr := db.SaveDoc("2", "", assetJSON, nil) + _, saveerr := db.SaveDoc("2", "", &CouchDoc{JSONValue: assetJSON, Attachments: nil}) testutil.AssertNoError(t, saveerr, fmt.Sprintf("Error when trying to save a document")) //Attempt to retrieve the test document