github.com/go-kivik/kivik/v4@v4.3.2/couchdb/db.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 package couchdb 14 15 import ( 16 "bytes" 17 "context" 18 "encoding/json" 19 "errors" 20 "fmt" 21 "io" 22 "mime" 23 "mime/multipart" 24 "net/http" 25 "net/textproto" 26 "net/url" 27 "os" 28 "path/filepath" 29 "reflect" 30 "sort" 31 "strconv" 32 "strings" 33 "sync" 34 "sync/atomic" 35 36 kivik "github.com/go-kivik/kivik/v4" 37 "github.com/go-kivik/kivik/v4/couchdb/chttp" 38 "github.com/go-kivik/kivik/v4/driver" 39 internal "github.com/go-kivik/kivik/v4/int/errors" 40 ) 41 42 type db struct { 43 *client 44 dbName string 45 } 46 47 var ( 48 _ driver.DB = &db{} 49 _ driver.Finder = &db{} 50 _ driver.RevGetter = &db{} 51 _ driver.AttachmentMetaGetter = &db{} 52 _ driver.PartitionedDB = &db{} 53 ) 54 55 func (d *db) path(path string) string { 56 url, err := url.Parse(d.dbName + "/" + strings.TrimPrefix(path, "/")) 57 if err != nil { 58 panic("THIS IS A BUG: d.path failed: " + err.Error()) 59 } 60 return url.String() 61 } 62 63 func optionsToParams(opts ...map[string]interface{}) (url.Values, error) { 64 params := url.Values{} 65 for _, optsSet := range opts { 66 if err := encodeKeys(optsSet); err != nil { 67 return nil, err 68 } 69 for key, i := range optsSet { 70 var values []string 71 switch v := i.(type) { 72 case string: 73 values = []string{v} 74 case []string: 75 values = v 76 case bool: 77 values = []string{fmt.Sprintf("%t", v)} 78 case int, uint, uint8, uint16, uint32, uint64, int8, int16, int32, int64: 79 values = []string{fmt.Sprintf("%d", v)} 80 default: 81 return nil, &internal.Error{Status: http.StatusBadRequest, Err: fmt.Errorf("kivik: invalid type %T for options", i)} 82 } 83 for _, value := range values { 84 params.Add(key, value) 85 } 86 } 87 } 88 return params, nil 89 } 90 91 // rowsQuery performs a query that returns a rows iterator. 92 func (d *db) rowsQuery(ctx context.Context, path string, options driver.Options) (driver.Rows, error) { 93 opts := map[string]interface{}{} 94 options.Apply(opts) 95 payload := make(map[string]interface{}) 96 if keys := opts["keys"]; keys != nil { 97 delete(opts, "keys") 98 payload["keys"] = keys 99 } 100 rowsInit := newRows 101 if queries := opts["queries"]; queries != nil { 102 rowsInit = newMultiQueriesRows 103 delete(opts, "queries") 104 payload["queries"] = queries 105 // Funny that this works even in CouchDB 1.x. It seems 1.x just ignores 106 // extra path elements beyond the view name. So yay for accidental 107 // backward compatibility! 108 path = filepath.Join(path, "queries") 109 } 110 query, err := optionsToParams(opts) 111 if err != nil { 112 return nil, err 113 } 114 chttpOpts := &chttp.Options{Query: query} 115 method := http.MethodGet 116 if len(payload) > 0 { 117 method = http.MethodPost 118 chttpOpts.GetBody = chttp.BodyEncoder(payload) 119 chttpOpts.Header = http.Header{ 120 chttp.HeaderIdempotencyKey: []string{}, 121 } 122 } 123 resp, err := d.Client.DoReq(ctx, method, d.path(path), chttpOpts) 124 if err != nil { 125 return nil, err 126 } 127 if err = chttp.ResponseError(resp); err != nil { 128 return nil, err 129 } 130 return rowsInit(ctx, resp.Body), nil 131 } 132 133 // AllDocs returns all of the documents in the database. 134 func (d *db) AllDocs(ctx context.Context, options driver.Options) (driver.Rows, error) { 135 reqPath := partPath("_all_docs") 136 options.Apply(reqPath) 137 return d.rowsQuery(ctx, reqPath.String(), options) 138 } 139 140 // DesignDocs returns all of the documents in the database. 141 func (d *db) DesignDocs(ctx context.Context, options driver.Options) (driver.Rows, error) { 142 return d.rowsQuery(ctx, "_design_docs", options) 143 } 144 145 // LocalDocs returns all of the documents in the database. 146 func (d *db) LocalDocs(ctx context.Context, options driver.Options) (driver.Rows, error) { 147 return d.rowsQuery(ctx, "_local_docs", options) 148 } 149 150 // Query queries a view. 151 func (d *db) Query(ctx context.Context, ddoc, view string, options driver.Options) (driver.Rows, error) { 152 reqPath := partPath(fmt.Sprintf("_design/%s/_view/%s", chttp.EncodeDocID(ddoc), chttp.EncodeDocID(view))) 153 options.Apply(reqPath) 154 return d.rowsQuery(ctx, reqPath.String(), options) 155 } 156 157 // document represents a single document returned by Get 158 type document struct { 159 id string 160 rev string 161 body io.ReadCloser 162 attachments driver.Attachments 163 164 // read will be non-zero once the document has been read. 165 read int32 166 } 167 168 func (d *document) Next(row *driver.Row) error { 169 if atomic.SwapInt32(&d.read, 1) > 0 { 170 return io.EOF 171 } 172 row.ID = d.id 173 row.Rev = d.rev 174 row.Doc = d.body 175 row.Attachments = d.attachments 176 return nil 177 } 178 179 func (d *document) Close() error { 180 atomic.StoreInt32(&d.read, 1) 181 return d.body.Close() 182 } 183 184 func (*document) UpdateSeq() string { return "" } 185 func (*document) Offset() int64 { return 0 } 186 func (*document) TotalRows() int64 { return 0 } 187 188 // Get fetches the requested document. 189 func (d *db) Get(ctx context.Context, docID string, options driver.Options) (*driver.Document, error) { 190 resp, err := d.get(ctx, http.MethodGet, docID, options) 191 if err != nil { 192 return nil, err 193 } 194 ct, params, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) 195 if err != nil { 196 return nil, &internal.Error{Status: http.StatusBadGateway, Err: err} 197 } 198 199 switch ct { 200 case typeJSON, typeMPRelated: 201 etag, _ := chttp.ETag(resp) 202 doc, err := processDoc(docID, ct, params["boundary"], etag, resp.Body) 203 if err != nil { 204 return nil, err 205 } 206 return &driver.Document{ 207 Rev: doc.rev, 208 Body: doc.body, 209 Attachments: doc.attachments, 210 }, nil 211 default: 212 return nil, &internal.Error{Status: http.StatusBadGateway, Err: fmt.Errorf("kivik: invalid content type in response: %s", ct)} 213 } 214 } 215 216 func openRevs(revs []string) kivik.Option { 217 encoded, _ := json.Marshal(revs) 218 return kivik.Param("open_revs", string(encoded)) 219 } 220 221 // TODO: Flesh this out. 222 func (d *db) OpenRevs(ctx context.Context, docID string, revs []string, options driver.Options) (driver.Rows, error) { 223 opts := multiOptions{ 224 kivik.Option(options), 225 openRevs(revs), 226 } 227 resp, err := d.get(ctx, http.MethodGet, docID, opts) 228 if err != nil { 229 return nil, err 230 } 231 ct, params, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) 232 if err != nil { 233 return nil, &internal.Error{Status: http.StatusBadGateway, Err: err} 234 } 235 236 switch ct { 237 case typeJSON, typeMPRelated: 238 etag, _ := chttp.ETag(resp) 239 return processDoc(docID, ct, params["boundary"], etag, resp.Body) 240 case typeMPMixed: 241 boundary := strings.Trim(params["boundary"], "\"") 242 if boundary == "" { 243 return nil, &internal.Error{Status: http.StatusBadGateway, Err: errors.New("kivik: boundary missing for multipart/related response")} 244 } 245 mpReader := multipart.NewReader(resp.Body, boundary) 246 return &multiDocs{ 247 id: docID, 248 respBody: resp.Body, 249 reader: mpReader, 250 }, nil 251 default: 252 return nil, &internal.Error{Status: http.StatusBadGateway, Err: fmt.Errorf("kivik: invalid content type in response: %s", ct)} 253 } 254 } 255 256 func processDoc(docID, ct, boundary, rev string, body io.ReadCloser) (*document, error) { 257 switch ct { 258 case typeJSON: 259 if rev == "" { 260 var err error 261 body, rev, err = chttp.ExtractRev(body) 262 if err != nil { 263 return nil, err 264 } 265 } 266 267 return &document{ 268 id: docID, 269 rev: rev, 270 body: body, 271 }, nil 272 case typeMPRelated: 273 boundary := strings.Trim(boundary, "\"") 274 if boundary == "" { 275 return nil, &internal.Error{Status: http.StatusBadGateway, Err: errors.New("kivik: boundary missing for multipart/related response")} 276 } 277 mpReader := multipart.NewReader(body, boundary) 278 body, err := mpReader.NextPart() 279 if err != nil { 280 return nil, &internal.Error{Status: http.StatusBadGateway, Err: err} 281 } 282 283 // TODO: Use a TeeReader here, to avoid slurping the entire body into memory at once 284 content, err := io.ReadAll(body) 285 if err != nil { 286 return nil, &internal.Error{Status: http.StatusBadGateway, Err: err} 287 } 288 _, rev, err := chttp.ExtractRev(io.NopCloser(bytes.NewReader(content))) 289 if err != nil { 290 return nil, err 291 } 292 293 var metaDoc struct { 294 Attachments map[string]attMeta `json:"_attachments"` 295 } 296 if err := json.Unmarshal(content, &metaDoc); err != nil { 297 return nil, &internal.Error{Status: http.StatusBadGateway, Err: err} 298 } 299 300 return &document{ 301 id: docID, 302 rev: rev, 303 body: io.NopCloser(bytes.NewBuffer(content)), 304 attachments: &multipartAttachments{ 305 content: body, 306 mpReader: mpReader, 307 meta: metaDoc.Attachments, 308 }, 309 }, nil 310 default: 311 return nil, &internal.Error{Status: http.StatusBadGateway, Err: fmt.Errorf("kivik: invalid content type in response: %s", ct)} 312 } 313 } 314 315 type multiDocs struct { 316 id string 317 respBody io.Closer 318 reader *multipart.Reader 319 readerCloser io.Closer 320 } 321 322 var _ driver.Rows = (*multiDocs)(nil) 323 324 func (d *multiDocs) Next(row *driver.Row) error { 325 if d.readerCloser != nil { 326 if err := d.readerCloser.Close(); err != nil { 327 return err 328 } 329 d.readerCloser = nil 330 } 331 part, err := d.reader.NextPart() 332 if err != nil { 333 return err 334 } 335 ct, params, err := mime.ParseMediaType(part.Header.Get("Content-Type")) 336 if err != nil { 337 return err 338 } 339 340 if _, ok := params["error"]; ok { 341 var body struct { 342 Rev string `json:"missing"` 343 } 344 err := json.NewDecoder(part).Decode(&body) 345 row.ID = d.id 346 row.Error = &internal.Error{Status: http.StatusNotFound, Err: errors.New("missing")} 347 row.Rev = body.Rev 348 return err 349 } 350 351 doc, err := processDoc(d.id, ct, params["boundary"], "", part) 352 if err != nil { 353 return err 354 } 355 356 row.ID = doc.id 357 row.Doc = doc.body 358 row.Rev = doc.rev 359 row.Attachments = doc.attachments 360 361 return nil 362 } 363 364 func (d *multiDocs) Close() error { 365 if d.readerCloser != nil { 366 if err := d.readerCloser.Close(); err != nil { 367 return err 368 } 369 d.readerCloser = nil 370 } 371 return d.respBody.Close() 372 } 373 374 func (*multiDocs) UpdateSeq() string { return "" } 375 func (*multiDocs) Offset() int64 { return 0 } 376 func (*multiDocs) TotalRows() int64 { return 0 } 377 378 type attMeta struct { 379 ContentType string `json:"content_type"` 380 Size *int64 `json:"length"` 381 Follows bool `json:"follows"` 382 } 383 384 type multipartAttachments struct { 385 content io.ReadCloser 386 mpReader *multipart.Reader 387 meta map[string]attMeta 388 } 389 390 var _ driver.Attachments = &multipartAttachments{} 391 392 func (a *multipartAttachments) Next(att *driver.Attachment) error { 393 part, err := a.mpReader.NextPart() 394 switch err { 395 case io.EOF: 396 return err 397 case nil: 398 // fall through 399 default: 400 return &internal.Error{Status: http.StatusBadGateway, Err: err} 401 } 402 403 disp, dispositionParams, err := mime.ParseMediaType(part.Header.Get("Content-Disposition")) 404 if err != nil { 405 return &internal.Error{Status: http.StatusBadGateway, Err: fmt.Errorf("Content-Disposition: %s", err)} 406 } 407 if disp != "attachment" { 408 return &internal.Error{Status: http.StatusBadGateway, Err: fmt.Errorf("Unexpected Content-Disposition: %s", disp)} 409 } 410 filename := dispositionParams["filename"] 411 412 meta := a.meta[filename] 413 if !meta.Follows { 414 return &internal.Error{Status: http.StatusBadGateway, Err: fmt.Errorf("File '%s' not in manifest", filename)} 415 } 416 417 size := int64(-1) 418 if meta.Size != nil { 419 size = *meta.Size 420 } else if cl, e := strconv.ParseInt(part.Header.Get("Content-Length"), 10, 64); e == nil { // nolint:gomnd 421 size = cl 422 } 423 424 var cType string 425 if ctHeader, ok := part.Header["Content-Type"]; ok { 426 cType, _, err = mime.ParseMediaType(ctHeader[0]) 427 if err != nil { 428 return &internal.Error{Status: http.StatusBadGateway, Err: err} 429 } 430 } else { 431 cType = meta.ContentType 432 } 433 434 *att = driver.Attachment{ 435 Filename: filename, 436 Size: size, 437 ContentType: cType, 438 Content: part, 439 ContentEncoding: part.Header.Get("Content-Encoding"), 440 } 441 return nil 442 } 443 444 func (a *multipartAttachments) Close() error { 445 return a.content.Close() 446 } 447 448 // Rev returns the most current rev of the requested document. 449 func (d *db) GetRev(ctx context.Context, docID string, options driver.Options) (string, error) { 450 resp, err := d.get(ctx, http.MethodHead, docID, options) 451 if err != nil { 452 return "", err 453 } 454 _ = resp.Body.Close() 455 rev, err := chttp.GetRev(resp) 456 if err != nil { 457 return "", err 458 } 459 return rev, err 460 } 461 462 type getOptions struct { 463 noMultipartGet bool 464 } 465 466 func (d *db) get(ctx context.Context, method, docID string, options driver.Options) (*http.Response, error) { 467 if docID == "" { 468 return nil, missingArg("docID") 469 } 470 471 var getOpts getOptions 472 options.Apply(&getOpts) 473 474 opts := map[string]interface{}{} 475 options.Apply(opts) 476 477 chttpOpts := chttp.NewOptions(options) 478 479 chttpOpts.Accept = strings.Join([]string{typeMPMixed, typeMPRelated, typeJSON}, ", ") 480 var err error 481 chttpOpts.Query, err = optionsToParams(opts) 482 if err != nil { 483 return nil, err 484 } 485 if getOpts.noMultipartGet { 486 chttpOpts.Accept = typeJSON 487 } 488 resp, err := d.Client.DoReq(ctx, method, d.path(chttp.EncodeDocID(docID)), chttpOpts) 489 if err != nil { 490 return nil, err 491 } 492 err = chttp.ResponseError(resp) 493 return resp, err 494 } 495 496 func (d *db) CreateDoc(ctx context.Context, doc interface{}, options driver.Options) (docID, rev string, err error) { 497 result := struct { 498 ID string `json:"id"` 499 Rev string `json:"rev"` 500 }{} 501 502 chttpOpts := chttp.NewOptions(options) 503 504 opts := map[string]interface{}{} 505 options.Apply(opts) 506 507 path := d.dbName 508 if len(opts) > 0 { 509 params, e := optionsToParams(opts) 510 if e != nil { 511 return "", "", e 512 } 513 path += "?" + params.Encode() 514 } 515 516 chttpOpts.Body = chttp.EncodeBody(doc) 517 518 err = d.Client.DoJSON(ctx, http.MethodPost, path, chttpOpts, &result) 519 return result.ID, result.Rev, err 520 } 521 522 type putOptions struct { 523 NoMultipartPut bool 524 } 525 526 func putOpts(doc interface{}, options driver.Options) (*chttp.Options, error) { 527 chttpOpts := chttp.NewOptions(options) 528 opts := map[string]interface{}{} 529 options.Apply(opts) 530 var err error 531 chttpOpts.Query, err = optionsToParams(opts) 532 if err != nil { 533 return nil, err 534 } 535 var putOpts putOptions 536 options.Apply(&putOpts) 537 if putOpts.NoMultipartPut { 538 if atts, ok := extractAttachments(doc); ok { 539 boundary, size, multipartBody, err := newMultipartAttachments(chttp.EncodeBody(doc), atts) 540 if err != nil { 541 return nil, err 542 } 543 chttpOpts.Body = multipartBody 544 chttpOpts.ContentLength = size 545 chttpOpts.ContentType = fmt.Sprintf(typeMPRelated+"; boundary=%q", boundary) 546 return chttpOpts, nil 547 } 548 } 549 chttpOpts.Body = chttp.EncodeBody(doc) 550 return chttpOpts, nil 551 } 552 553 func (d *db) Put(ctx context.Context, docID string, doc interface{}, options driver.Options) (rev string, err error) { 554 if docID == "" { 555 return "", missingArg("docID") 556 } 557 opts2, err := putOpts(doc, options) 558 if err != nil { 559 return "", err 560 } 561 var result struct { 562 ID string `json:"id"` 563 Rev string `json:"rev"` 564 } 565 err = d.Client.DoJSON(ctx, http.MethodPut, d.path(chttp.EncodeDocID(docID)), opts2, &result) 566 if err != nil { 567 return "", err 568 } 569 return result.Rev, nil 570 } 571 572 const attachmentsKey = "_attachments" 573 574 func extractAttachments(doc interface{}) (*kivik.Attachments, bool) { 575 if doc == nil { 576 return nil, false 577 } 578 v := reflect.ValueOf(doc) 579 if v.Type().Kind() == reflect.Ptr { 580 return extractAttachments(v.Elem().Interface()) 581 } 582 if stdMap, ok := doc.(map[string]interface{}); ok { 583 return interfaceToAttachments(stdMap[attachmentsKey]) 584 } 585 if v.Kind() != reflect.Struct { 586 return nil, false 587 } 588 for i := 0; i < v.NumField(); i++ { 589 if v.Type().Field(i).Tag.Get("json") == attachmentsKey { 590 return interfaceToAttachments(v.Field(i).Interface()) 591 } 592 } 593 return nil, false 594 } 595 596 func interfaceToAttachments(i interface{}) (*kivik.Attachments, bool) { 597 switch t := i.(type) { 598 case kivik.Attachments: 599 atts := make(kivik.Attachments, len(t)) 600 for k, v := range t { 601 atts[k] = v 602 delete(t, k) 603 } 604 return &atts, true 605 case *kivik.Attachments: 606 atts := new(kivik.Attachments) 607 *atts = *t 608 *t = nil 609 return atts, true 610 } 611 return nil, false 612 } 613 614 // newMultipartAttachments reads a json stream on in, and produces a 615 // multipart/related output suitable for a PUT request. 616 func newMultipartAttachments(in io.ReadCloser, att *kivik.Attachments) (boundary string, size int64, content io.ReadCloser, err error) { 617 tmp, err := os.CreateTemp("", "kivik-multipart-*") 618 if err != nil { 619 return "", 0, nil, err 620 } 621 body := multipart.NewWriter(tmp) 622 w := sync.WaitGroup{} 623 w.Add(1) 624 go func() { 625 err = createMultipart(body, in, att) 626 e := in.Close() 627 if err == nil { 628 err = e 629 } 630 w.Done() 631 }() 632 w.Wait() 633 if e := tmp.Sync(); err == nil { 634 err = e 635 } 636 if info, e := tmp.Stat(); e == nil { 637 size = info.Size() 638 } else if err == nil { 639 err = e 640 } 641 if _, e := tmp.Seek(0, 0); e != nil && err == nil { 642 err = e 643 } 644 return body.Boundary(), 645 size, 646 tmp, 647 err 648 } 649 650 func createMultipart(w *multipart.Writer, r io.ReadCloser, atts *kivik.Attachments) error { 651 doc, err := w.CreatePart(textproto.MIMEHeader{ 652 "Content-Type": {typeJSON}, 653 }) 654 if err != nil { 655 return err 656 } 657 attJSON := replaceAttachments(r, atts) 658 if _, e := io.Copy(doc, attJSON); e != nil { 659 return e 660 } 661 662 // Sort the filenames to ensure order consistent with json.Marshal's ordering 663 // of the stubs in the body 664 filenames := make([]string, 0, len(*atts)) 665 for filename := range *atts { 666 filenames = append(filenames, filename) 667 } 668 sort.Strings(filenames) 669 670 for _, filename := range filenames { 671 att := (*atts)[filename] 672 file, err := w.CreatePart(textproto.MIMEHeader{ 673 // "Content-Type": {att.ContentType}, 674 // "Content-Disposition": {fmt.Sprintf(`attachment; filename=%q`, filename)}, 675 // "Content-Length": {strconv.FormatInt(att.Size, 10)}, 676 }) 677 if err != nil { 678 return err 679 } 680 if _, err := io.Copy(file, att.Content); err != nil { 681 return err 682 } 683 _ = att.Content.Close() 684 } 685 686 return w.Close() 687 } 688 689 type lener interface { 690 Len() int 691 } 692 693 type stater interface { 694 Stat() (os.FileInfo, error) 695 } 696 697 // attachmentSize determines the size of the `in` stream, possibly by reading 698 // the entire stream first. If att.Size is already set, this function does 699 // nothing. It attempts the following methods: 700 // 701 // 1. Calls `Len()`, if implemented by `in` (i.e. `*bytes.Buffer`) 702 // 2. Calls `Stat()`, if implemented by `in` (i.e. `*os.File`) then returns 703 // the file's size 704 // 3. If `in` is an io.Seeker, copy the entire contents to io.Discard to 705 // determine size, then reset the reader to the beginning. 706 // 4. Read the entire stream to determine the size, and replace att.Content 707 // to be replayed. 708 func attachmentSize(att *kivik.Attachment) error { 709 if att.Size > 0 { 710 return nil 711 } 712 size, r, err := readerSize(att.Content) 713 if err != nil { 714 return err 715 } 716 rc, ok := r.(io.ReadCloser) 717 if !ok { 718 rc = io.NopCloser(r) 719 } 720 721 att.Content = rc 722 att.Size = size 723 return nil 724 } 725 726 func readerSize(in io.Reader) (int64, io.Reader, error) { 727 if ln, ok := in.(lener); ok { 728 return int64(ln.Len()), in, nil 729 } 730 if st, ok := in.(stater); ok { 731 info, err := st.Stat() 732 if err != nil { 733 return 0, nil, err 734 } 735 return info.Size(), in, nil 736 } 737 if sk, ok := in.(io.Seeker); ok { 738 n, err := io.Copy(io.Discard, in) 739 if err != nil { 740 return 0, nil, err 741 } 742 _, err = sk.Seek(0, io.SeekStart) 743 return n, in, err 744 } 745 content, err := io.ReadAll(in) 746 if err != nil { 747 return 0, nil, err 748 } 749 buf := bytes.NewBuffer(content) 750 return int64(buf.Len()), io.NopCloser(buf), nil 751 } 752 753 // NewAttachment is a convenience function, which sets the size of the attachment 754 // based on content. This is intended for creating attachments to be uploaded 755 // using multipart/related capabilities of [github.com/go-kivik/kivik/v4.DB.Put]. 756 // The attachment size will be set to the first of the following found: 757 // 758 // 1. `size`, if present. Only the first value is considered. 759 // 2. content.Len(), if implemented (i.e. [bytes.Buffer]) 760 // 3. content.Stat().Size(), if implemented (i.e. [os.File]) 761 // 4. Read the entire content into memory, to determine the size. This can 762 // use a lot of memory for large attachments. Please use a file, or 763 // specify the size directly instead. 764 func NewAttachment(filename, contentType string, content io.Reader, size ...int64) (*kivik.Attachment, error) { 765 var filesize int64 766 if len(size) > 0 { 767 filesize = size[0] 768 } else { 769 var err error 770 filesize, content, err = readerSize(content) 771 if err != nil { 772 return nil, err 773 } 774 } 775 rc, ok := content.(io.ReadCloser) 776 if !ok { 777 rc = io.NopCloser(content) 778 } 779 return &kivik.Attachment{ 780 Filename: filename, 781 ContentType: contentType, 782 Content: rc, 783 Size: filesize, 784 }, nil 785 } 786 787 // replaceAttachments reads a JSON stream on in, looking for the _attachments 788 // key, then replaces its value with the marshaled version of att. 789 func replaceAttachments(in io.ReadCloser, atts *kivik.Attachments) io.ReadCloser { 790 r, w := io.Pipe() 791 go func() { 792 stubs, err := attachmentStubs(atts) 793 if err != nil { 794 _ = w.CloseWithError(err) 795 _ = in.Close() 796 return 797 } 798 err = copyWithAttachmentStubs(w, in, stubs) 799 e := in.Close() 800 if err == nil { 801 err = e 802 } 803 _ = w.CloseWithError(err) 804 }() 805 return r 806 } 807 808 type stub struct { 809 ContentType string `json:"content_type"` 810 Size int64 `json:"length"` 811 } 812 813 func (s *stub) MarshalJSON() ([]byte, error) { 814 type attJSON struct { 815 stub 816 Follows bool `json:"follows"` 817 } 818 att := attJSON{ 819 stub: *s, 820 Follows: true, 821 } 822 return json.Marshal(att) 823 } 824 825 func attachmentStubs(atts *kivik.Attachments) (map[string]*stub, error) { 826 if atts == nil { 827 return nil, nil 828 } 829 result := make(map[string]*stub, len(*atts)) 830 for filename, att := range *atts { 831 if err := attachmentSize(att); err != nil { 832 return nil, err 833 } 834 result[filename] = &stub{ 835 ContentType: att.ContentType, 836 Size: att.Size, 837 } 838 } 839 return result, nil 840 } 841 842 // copyWithAttachmentStubs copies r to w, replacing the _attachment value with the 843 // marshaled version of atts. 844 func copyWithAttachmentStubs(w io.Writer, r io.Reader, atts map[string]*stub) error { 845 dec := json.NewDecoder(r) 846 t, err := dec.Token() 847 if err == nil { 848 if t != json.Delim('{') { 849 return &internal.Error{Status: http.StatusBadRequest, Err: fmt.Errorf("expected '{', found '%v'", t)} 850 } 851 } 852 if err != nil { 853 if err != io.EOF { 854 return err 855 } 856 } 857 if _, err := fmt.Fprintf(w, "%v", t); err != nil { 858 return err 859 } 860 first := true 861 for { 862 t, err := dec.Token() 863 if err == io.EOF { 864 break 865 } 866 if err != nil { 867 return &internal.Error{Status: http.StatusBadRequest, Err: err} 868 } 869 switch tp := t.(type) { 870 case string: 871 if !first { 872 if _, e := w.Write([]byte(",")); e != nil { 873 return e 874 } 875 } 876 first = false 877 if _, e := fmt.Fprintf(w, `"%s":`, tp); e != nil { 878 return e 879 } 880 var val json.RawMessage 881 if e := dec.Decode(&val); e != nil { 882 return e 883 } 884 if tp == attachmentsKey { 885 if e := json.NewEncoder(w).Encode(atts); e != nil { 886 return e 887 } 888 // Once we're here, we can just stream the rest of the input 889 // unaltered. 890 if _, e := io.Copy(w, dec.Buffered()); e != nil { 891 return e 892 } 893 _, e := io.Copy(w, r) 894 return e 895 } 896 if _, e := w.Write(val); e != nil { 897 return e 898 } 899 case json.Delim: 900 if tp != json.Delim('}') { 901 return fmt.Errorf("expected '}', found '%v'", t) 902 } 903 if _, err := fmt.Fprintf(w, "%v", t); err != nil { 904 return err 905 } 906 } 907 } 908 return nil 909 } 910 911 func (d *db) Delete(ctx context.Context, docID string, options driver.Options) (string, error) { 912 if docID == "" { 913 return "", missingArg("docID") 914 } 915 opts := map[string]interface{}{} 916 options.Apply(opts) 917 if rev, _ := opts["rev"].(string); rev == "" { 918 return "", missingArg("rev") 919 } 920 921 chttpOpts := chttp.NewOptions(options) 922 923 var err error 924 chttpOpts.Query, err = optionsToParams(opts) 925 if err != nil { 926 return "", err 927 } 928 929 resp, err := d.Client.DoReq(ctx, http.MethodDelete, d.path(chttp.EncodeDocID(docID)), chttpOpts) 930 if err != nil { 931 return "", err 932 } 933 defer chttp.CloseBody(resp.Body) 934 if err := chttp.ResponseError(resp); err != nil { 935 return "", err 936 } 937 return chttp.GetRev(resp) 938 } 939 940 func (d *db) Flush(ctx context.Context) error { 941 opts := &chttp.Options{ 942 Header: http.Header{ 943 chttp.HeaderIdempotencyKey: []string{}, 944 }, 945 } 946 _, err := d.Client.DoError(ctx, http.MethodPost, d.path("/_ensure_full_commit"), opts) 947 return err 948 } 949 950 func (d *db) Compact(ctx context.Context) error { 951 opts := &chttp.Options{ 952 Header: http.Header{ 953 chttp.HeaderIdempotencyKey: []string{}, 954 }, 955 } 956 res, err := d.Client.DoReq(ctx, http.MethodPost, d.path("/_compact"), opts) 957 if err != nil { 958 return err 959 } 960 defer chttp.CloseBody(res.Body) 961 return chttp.ResponseError(res) 962 } 963 964 func (d *db) CompactView(ctx context.Context, ddocID string) error { 965 if ddocID == "" { 966 return missingArg("ddocID") 967 } 968 opts := &chttp.Options{ 969 Header: http.Header{ 970 chttp.HeaderIdempotencyKey: []string{}, 971 }, 972 } 973 res, err := d.Client.DoReq(ctx, http.MethodPost, d.path("/_compact/"+ddocID), opts) 974 if err != nil { 975 return err 976 } 977 defer chttp.CloseBody(res.Body) 978 return chttp.ResponseError(res) 979 } 980 981 func (d *db) ViewCleanup(ctx context.Context) error { 982 opts := &chttp.Options{ 983 Header: http.Header{ 984 chttp.HeaderIdempotencyKey: []string{}, 985 }, 986 } 987 res, err := d.Client.DoReq(ctx, http.MethodPost, d.path("/_view_cleanup"), opts) 988 if err != nil { 989 return err 990 } 991 defer chttp.CloseBody(res.Body) 992 return chttp.ResponseError(res) 993 } 994 995 func (d *db) Security(ctx context.Context) (*driver.Security, error) { 996 var sec *driver.Security 997 err := d.Client.DoJSON(ctx, http.MethodGet, d.path("/_security"), nil, &sec) 998 return sec, err 999 } 1000 1001 func (d *db) SetSecurity(ctx context.Context, security *driver.Security) error { 1002 opts := &chttp.Options{ 1003 GetBody: chttp.BodyEncoder(security), 1004 Header: http.Header{ 1005 chttp.HeaderIdempotencyKey: []string{}, 1006 }, 1007 } 1008 res, err := d.Client.DoReq(ctx, http.MethodPut, d.path("/_security"), opts) 1009 if err != nil { 1010 return err 1011 } 1012 defer chttp.CloseBody(res.Body) 1013 return chttp.ResponseError(res) 1014 } 1015 1016 func (d *db) Copy(ctx context.Context, targetID, sourceID string, options driver.Options) (targetRev string, err error) { 1017 if sourceID == "" { 1018 return "", missingArg("sourceID") 1019 } 1020 if targetID == "" { 1021 return "", missingArg("targetID") 1022 } 1023 chttpOpts := chttp.NewOptions(options) 1024 1025 opts := map[string]interface{}{} 1026 options.Apply(opts) 1027 chttpOpts.Query, err = optionsToParams(opts) 1028 if err != nil { 1029 return "", err 1030 } 1031 chttpOpts.Header = http.Header{ 1032 chttp.HeaderDestination: []string{targetID}, 1033 } 1034 1035 resp, err := d.Client.DoReq(ctx, "COPY", d.path(chttp.EncodeDocID(sourceID)), chttpOpts) 1036 if err != nil { 1037 return "", err 1038 } 1039 defer chttp.CloseBody(resp.Body) 1040 if err := chttp.ResponseError(resp); err != nil { 1041 return "", err 1042 } 1043 return chttp.GetRev(resp) 1044 } 1045 1046 func (d *db) Purge(ctx context.Context, docMap map[string][]string) (*driver.PurgeResult, error) { 1047 result := &driver.PurgeResult{} 1048 options := &chttp.Options{ 1049 GetBody: chttp.BodyEncoder(docMap), 1050 Header: http.Header{ 1051 chttp.HeaderIdempotencyKey: []string{}, 1052 }, 1053 } 1054 err := d.Client.DoJSON(ctx, http.MethodPost, d.path("_purge"), options, &result) 1055 return result, err 1056 } 1057 1058 var _ driver.RevsDiffer = &db{} 1059 1060 func (d *db) RevsDiff(ctx context.Context, revMap interface{}) (driver.Rows, error) { 1061 options := &chttp.Options{ 1062 GetBody: chttp.BodyEncoder(revMap), 1063 Header: http.Header{ 1064 chttp.HeaderIdempotencyKey: []string{}, 1065 }, 1066 } 1067 resp, err := d.Client.DoReq(ctx, http.MethodPost, d.path("_revs_diff"), options) 1068 if err != nil { 1069 return nil, err 1070 } 1071 if err = chttp.ResponseError(resp); err != nil { 1072 return nil, err 1073 } 1074 return newRevsDiffRows(ctx, resp.Body), nil 1075 } 1076 1077 type revsDiffParser struct{} 1078 1079 func (p *revsDiffParser) decodeItem(i interface{}, dec *json.Decoder) error { 1080 t, err := dec.Token() 1081 if err != nil { 1082 return err 1083 } 1084 var value json.RawMessage 1085 if err := dec.Decode(&value); err != nil { 1086 return err 1087 } 1088 row := i.(*driver.Row) 1089 row.ID = t.(string) 1090 row.Value = bytes.NewReader(value) 1091 return nil 1092 } 1093 1094 func newRevsDiffRows(ctx context.Context, in io.ReadCloser) driver.Rows { 1095 iter := newIter(ctx, nil, "", in, &revsDiffParser{}) 1096 iter.objMode = true 1097 return &rows{iter: iter} 1098 } 1099 1100 // Close is a no-op for the CouchDB driver. 1101 func (db) Close() error { return nil }