From dc3b3afa3e94b060011daf918e226057e0040451 Mon Sep 17 00:00:00 2001 From: mario rodriguez Date: Mon, 3 Aug 2020 13:09:16 +0200 Subject: [PATCH 1/5] Add BulkDocs method in db BulkDocs allows to create, update and/or delete multiple documents in a single request. The basic operations are similar to creating or updating a single document, except that they are batched into one request. Ref: https://cloud.ibm.com/docs/Cloudant?topic=Cloudant-documents#bulk-operations --- bulk.go | 20 ++++++++++++++++---- db.go | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/bulk.go b/bulk.go index 3f42122..ba9e36a 100644 --- a/bulk.go +++ b/bulk.go @@ -6,10 +6,14 @@ type bulkID struct { ID string `json:"id"` } -type BulkGet struct { +type BulkGetReq struct { Docs []struct{ ID string } `json:"docs"` } +type BulkDocsReq struct { + Docs []interface{} `json:"docs"` +} + type errorWrapper struct { ID string `json:"id"` Rev string `json:"rev"` @@ -22,11 +26,19 @@ type docWrapper struct { Error *errorWrapper `json:"error"` } -type bulkRes struct { +type bulkGetRes struct { Id string `json:"id"` Docs []docWrapper `json:"docs"` } -type bulkResp struct { - Results []bulkRes +type bulkGetResp struct { + Results []bulkGetRes +} + +type BulkDocsResp struct { + OK bool `json:"ok,omitempty"` + ID string `json:"id"` + Rev string `json:"rev,omitempty"` + Error string `json:"error,omitempty"` + Reason string `json:"reason,omitempty"` } diff --git a/db.go b/db.go index cd8f4b7..d6048f9 100644 --- a/db.go +++ b/db.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "encoding/json" + "net/http" "reflect" "strings" ) @@ -72,7 +73,7 @@ func (db *DB) BulkGet(ids []string, docType interface{}, opts Options) (docs []i return nil, nil, err } - request := &BulkGet{} + request := &BulkGetReq{} for _, id := range ids { request.Docs = append(request.Docs, struct{ ID string }{ID: id}) } @@ -85,7 +86,7 @@ func (db *DB) BulkGet(ids []string, docType interface{}, opts Options) (docs []i return nil, nil, err } - response := bulkResp{} + response := bulkGetResp{} err = readBody(resp, &response) if err != nil { return nil, nil, err @@ -149,6 +150,48 @@ func (db *DB) Put(id string, doc interface{}, rev string) (newrev string, err er return responseRev(db.closedRequest(db.ctx, "PUT", path, b)) } +// BulkDocs allows to create, update and/or delete multiple documents in a single request. +// The basic operations are similar to creating or updating a single document, +// except that they are batched into one request. +// +// BulkDocs accepts an array of documents to be processed. +// Documents may contain _id, _rev and _deleted, +// depending on the wanted operation, +// as well as the corresponding document fields if needed. +// +// It returns a slice of results with the outcome of every operation or an error. +// The only mandatory field is the ID. +// The rest of the structure of the result depends if it was successful or not. +// Note that no error will be returned if an operation or more fail. +// +// Observe that behaviour of two or more operations in a single document is undetermined. +// There are no guarantees that the operations will be processed in any given order. +// +// Reference: https://cloud.ibm.com/docs/Cloudant?topic=Cloudant-documents#bulk-operations +func (db *DB) BulkDocs(docs ...interface{}) (res []BulkDocsResp, err error) { + path := revpath("", db.name, "_bulk_docs") + + var req BulkDocsReq + req.Docs = make([]interface{}, 0, len(docs)) + for _, doc := range docs { + req.Docs = append(req.Docs, doc) + } + + bodyJSON, err := json.Marshal(req) + if err != nil { + return nil, err + } + body := bytes.NewReader(bodyJSON) + httpResp, err := db.request(db.ctx, http.MethodPost, path, body) + + err = readBody(httpResp, &res) + if err != nil { + return nil, err + } + + return res, nil +} + // Delete marks a document revision as deleted. func (db *DB) Delete(id, rev string) (newrev string, err error) { path := revpath(rev, db.name, id) From cb54c3ad25b2614f17a0afe68a445999e1dfb254 Mon Sep 17 00:00:00 2001 From: mario rodriguez Date: Mon, 3 Aug 2020 13:10:23 +0200 Subject: [PATCH 2/5] Add tests for BulkDocs method --- client_test.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/client_test.go b/client_test.go index 03a5227..0b8edef 100644 --- a/client_test.go +++ b/client_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "io/ioutil" "net/http" @@ -232,7 +233,7 @@ func TestGetNonexistingDoc(t *testing.T) { func TestBulkGet(t *testing.T) { c := newTestClient(t) c.Handle("POST /db/_bulk_get", func(resp http.ResponseWriter, req *http.Request) { - reqData := couchdb.BulkGet{} + reqData := couchdb.BulkGetReq{} body, _ := ioutil.ReadAll(req.Body) err := json.Unmarshal(body, &reqData) check(t, "reqData.Docs[0].ID", "foo", reqData.Docs[0].ID) @@ -318,6 +319,71 @@ func TestPut(t *testing.T) { check(t, "returned rev", "1-619db7ba8551c0de3f3a178775509611", rev) } +func TestBulkDocs(t *testing.T) { + c := newTestClient(t) + c.Handle("POST /db/_bulk_docs", func(rw http.ResponseWriter, req *http.Request) { + body, _ := ioutil.ReadAll(req.Body) + fmt.Println(string(body)) + reqData := couchdb.BulkDocsReq{} + err := json.Unmarshal(body, &reqData) + check(t, "json.Unmarshal", err, nil) + + check(t, "request body", "Barney", reqData.Docs[0].(map[string]interface{})["_id"]) + check(t, "request body", "Fred Flintstone", reqData.Docs[1].(map[string]interface{})["name"]) + check(t, "request body", "Pebbles", reqData.Docs[2].(map[string]interface{})["_id"]) + check(t, "request body", "Dino", reqData.Docs[3].(map[string]interface{})["name"]) + + rw.WriteHeader(http.StatusOK) + _, err = io.WriteString(rw, `[{"ok":true,"id":"Barney","rev":"1"}, + {"ok":true,"id":"Fred","rev":"1"}, + {"ok":true,"id":"Pebbles","rev":"2"}, + {"id":"Dino","error":"conflict","reason":"Document update conflict"}]`) + check(t, "io.WriteString", err, nil) + + }) + + type createDoc struct { + Name string `json:"name"` + ID string `json:"_id"` + Rev string `json:"_rev"` + } + type updateDoc struct { + Name string `json:"name"` + Age int32 `json:"age"` + } + type delDoc struct { + ID string `json:"_id"` + Rev string `json:"_rev"` + Deleted bool `json:"_deleted"` + } + + docCreate := &createDoc{"Barney Rubble", "Barney", "1"} + docUpdate := &updateDoc{"Fred Flintstone", 41} + docDel := &delDoc{"Pebbles", "2", true} + docFailUpdate := &updateDoc{Name: "Dino", Age: 5} + + res, err := c.DB("db").BulkDocs(docCreate, docUpdate, docDel, docFailUpdate) + check(t, "BulkDocs", err, nil) + + createRes := res[0] + check(t, "createRes.OK", true, createRes.OK) + check(t, "createRes.ID", "Barney", createRes.ID) + check(t, "createRes.Rev", "1", createRes.Rev) + + updateRes := res[1] + check(t, "updateRes.OK", true, updateRes.OK) + + delRes := res[2] + check(t, "delRes.OK", true, delRes.OK) + check(t, "delRes.ID", "Pebbles", delRes.ID) + check(t, "delRes.Rev", "2", delRes.Rev) + + updateFailuteRes := res[3] + check(t, "updateFailuteRes.OK", false, updateFailuteRes.OK) + check(t, "updateFailuteRes.ID", "Dino", updateFailuteRes.ID) + check(t, "updateFailuteRes.Error", "conflict", updateFailuteRes.Error) +} + func TestPutWithRev(t *testing.T) { c := newTestClient(t) c.Handle("PUT /db/doc", func(resp http.ResponseWriter, req *http.Request) { From fd1be05b3f211f999309c55c5371b49756aa68cf Mon Sep 17 00:00:00 2001 From: mario rodriguez Date: Mon, 3 Aug 2020 13:10:42 +0200 Subject: [PATCH 3/5] Update CHANGELOG with BulkDocs changes --- CHANGELOG.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cac17b0..a39205d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,37 @@ # Cabify CouchDB Library ChangeLog -## 0.3.1 +## [Unreleased] +### Added +- BulkDocs method + +### Changed +- Nothing + +### Deprecated +- Nothing + +### Removed +- Nothing + +### Fixed +- Nothing + +### Security +- Nothing + +## [0.3.1] - 2020-02-13 ### Fixed - PostView func needs body and query parameters -## 0.3.0 +## [0.3.0] - 2020-02-12 ### Added - PostView method to query views with POST method -## 0.2.0 +## [0.2.0] - 2020-07-09 ### Added - BulkGet method to query multiple documents per id at once - go.mod file -## 0.1.0 +## [0.1.0] - 2020-06-19 +### Added - First release to be compatible with go modules \ No newline at end of file From 2e963b67fcd2ab0772a618c0eb817720323460e0 Mon Sep 17 00:00:00 2001 From: mario rodriguez Date: Mon, 3 Aug 2020 13:13:35 +0200 Subject: [PATCH 4/5] goimport project --- attachments_test.go | 3 ++- auth_test.go | 3 ++- couchapp/couchapp.go | 3 ++- x_test.go | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/attachments_test.go b/attachments_test.go index c8c6952..32ebefc 100644 --- a/attachments_test.go +++ b/attachments_test.go @@ -4,11 +4,12 @@ import ( "bytes" "encoding/base64" "encoding/json" - "github.com/cabify/go-couchdb" "io" "io/ioutil" . "net/http" "testing" + + "github.com/cabify/go-couchdb" ) var ( diff --git a/auth_test.go b/auth_test.go index 2413551..2c59954 100644 --- a/auth_test.go +++ b/auth_test.go @@ -1,9 +1,10 @@ package couchdb_test import ( - "github.com/cabify/go-couchdb" "net/http" "testing" + + "github.com/cabify/go-couchdb" ) func TestBasicAuth(t *testing.T) { diff --git a/couchapp/couchapp.go b/couchapp/couchapp.go index 9eea266..e94f244 100644 --- a/couchapp/couchapp.go +++ b/couchapp/couchapp.go @@ -11,12 +11,13 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/cabify/go-couchdb" "io/ioutil" "mime" "os" "path" "strings" + + "github.com/cabify/go-couchdb" ) // DefaultIgnorePatterns contains the default list of glob patterns diff --git a/x_test.go b/x_test.go index 2b16038..bd5486c 100644 --- a/x_test.go +++ b/x_test.go @@ -4,12 +4,13 @@ package couchdb_test import ( "bytes" - "github.com/cabify/go-couchdb" "io/ioutil" "net/http" "net/http/httptest" "reflect" "testing" + + "github.com/cabify/go-couchdb" ) // testClient is a very special couchdb.Client that also implements From eb002c8b0e86d58682421acd4f89df430c6f76b7 Mon Sep 17 00:00:00 2001 From: mario rodriguez Date: Mon, 3 Aug 2020 13:21:34 +0200 Subject: [PATCH 5/5] Undo breaking changes --- bulk.go | 2 +- client_test.go | 2 +- db.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bulk.go b/bulk.go index ba9e36a..be06638 100644 --- a/bulk.go +++ b/bulk.go @@ -6,7 +6,7 @@ type bulkID struct { ID string `json:"id"` } -type BulkGetReq struct { +type BulkGet struct { Docs []struct{ ID string } `json:"docs"` } diff --git a/client_test.go b/client_test.go index 0b8edef..5fa0f2f 100644 --- a/client_test.go +++ b/client_test.go @@ -233,7 +233,7 @@ func TestGetNonexistingDoc(t *testing.T) { func TestBulkGet(t *testing.T) { c := newTestClient(t) c.Handle("POST /db/_bulk_get", func(resp http.ResponseWriter, req *http.Request) { - reqData := couchdb.BulkGetReq{} + reqData := couchdb.BulkGet{} body, _ := ioutil.ReadAll(req.Body) err := json.Unmarshal(body, &reqData) check(t, "reqData.Docs[0].ID", "foo", reqData.Docs[0].ID) diff --git a/db.go b/db.go index d6048f9..f83e32b 100644 --- a/db.go +++ b/db.go @@ -73,7 +73,7 @@ func (db *DB) BulkGet(ids []string, docType interface{}, opts Options) (docs []i return nil, nil, err } - request := &BulkGetReq{} + request := &BulkGet{} for _, id := range ids { request.Docs = append(request.Docs, struct{ ID string }{ID: id}) }