@@ -25,12 +25,14 @@ import ( //nolint:gci // False positive, seemingly caused by the CouchDB driver
25
25
const (
26
26
couchDBUsersTable = "_users"
27
27
revIDFieldKey = "_rev"
28
+ deletedFieldKey = "_deleted"
28
29
29
30
designDocumentName = "AriesStorageDesignDocument"
30
31
payloadFieldKey = "payload"
31
32
32
33
// Hardcoded strings returned from Kivik/CouchDB that we check for.
33
34
docNotFoundErrMsgFromKivik = "Not Found: missing"
35
+ bulkGetDocNotFoundErrMsgFromKivik = "not_found: missing"
34
36
docDeletedErrMsgFromKivik = "Not Found: deleted"
35
37
databaseNotFoundErrMsgFromKivik = "Not Found: Database does not exist."
36
38
documentUpdateConflictErrMsgFromKivik = "Conflict: Document update conflict."
@@ -61,6 +63,7 @@ type db interface {
61
63
Put (ctx context.Context , docID string , doc interface {}, options ... kivik.Options ) (rev string , err error )
62
64
Find (ctx context.Context , query interface {}, options ... kivik.Options ) (* kivik.Rows , error )
63
65
Delete (ctx context.Context , docID , rev string , options ... kivik.Options ) (newRev string , err error )
66
+ BulkGet (ctx context.Context , docs []kivik.BulkGetReference , options ... kivik.Options ) (* kivik.Rows , error )
64
67
Close (ctx context.Context ) error
65
68
}
66
69
@@ -339,7 +342,7 @@ func (s *Store) Get(k string) ([]byte, error) {
339
342
return nil , fmt .Errorf (failureWhileScanningRow , err )
340
343
}
341
344
342
- storedValue , err := getValueFromRawDoc (rawDoc , payloadFieldKey )
345
+ storedValue , err := getStringValueFromRawDoc (rawDoc , payloadFieldKey )
343
346
if err != nil {
344
347
return nil , fmt .Errorf ("failed to get payload from raw document: %w" , err )
345
348
}
@@ -377,7 +380,21 @@ func (s *Store) GetTags(k string) ([]newstorage.Tag, error) {
377
380
// GetBulk fetches the values associated with the given keys.
378
381
// If a key doesn't exist, then a nil []byte is returned for that value. It is not considered an error.
379
382
func (s * Store ) GetBulk (keys ... string ) ([][]byte , error ) {
380
- return nil , errors .New ("not implemented" )
383
+ if keys == nil {
384
+ return nil , errors .New ("keys string slice cannot be nil" )
385
+ }
386
+
387
+ rawDocs , err := s .getRawDocs (keys )
388
+ if err != nil {
389
+ return nil , fmt .Errorf ("failure while getting raw CouchDB documents: %w" , err )
390
+ }
391
+
392
+ values , err := getPayloadsFromRawDocs (rawDocs )
393
+ if err != nil {
394
+ return nil , fmt .Errorf ("failure while getting stored values from raw docs: %w" , err )
395
+ }
396
+
397
+ return values , nil
381
398
}
382
399
383
400
// Query returns all data that satisfies the expression. Expression format: TagName:TagValue.
@@ -535,14 +552,39 @@ func (s *Store) getRevID(k string) (string, error) {
535
552
return "" , err
536
553
}
537
554
538
- revID , err := getValueFromRawDoc (rawDoc , revIDFieldKey )
555
+ revID , err := getStringValueFromRawDoc (rawDoc , revIDFieldKey )
539
556
if err != nil {
540
557
return "" , fmt .Errorf ("failed to get revision ID from the raw document: %w" , err )
541
558
}
542
559
543
560
return revID , nil
544
561
}
545
562
563
+ // getRawDocs returns the raw documents from CouchDB using a bulk REST call.
564
+ // If a document is not found, then the raw document will be nil. It is not considered an error.
565
+ func (s * Store ) getRawDocs (keys []string ) ([]map [string ]interface {}, error ) {
566
+ bulkGetReferences := make ([]kivik.BulkGetReference , len (keys ))
567
+ for i , key := range keys {
568
+ bulkGetReferences [i ].ID = key
569
+ }
570
+
571
+ rows , err := s .db .BulkGet (context .Background (), bulkGetReferences )
572
+ if err != nil {
573
+ return nil , fmt .Errorf ("failure while sending request to CouchDB bulk docs endpoint: %w" , err )
574
+ }
575
+
576
+ rawDocs , err := getRawDocsFromRows (rows )
577
+ if err != nil {
578
+ return nil , fmt .Errorf ("failed to get raw documents from rows: %w" , err )
579
+ }
580
+
581
+ if len (rawDocs ) != len (keys ) {
582
+ return nil , fmt .Errorf ("received %d raw documents, but %d were expected" , len (rawDocs ), len (keys ))
583
+ }
584
+
585
+ return rawDocs , nil
586
+ }
587
+
546
588
type couchDBResultsIterator struct {
547
589
store * Store
548
590
resultRows rows
@@ -733,15 +775,12 @@ func createIndexes(db *kivik.DB, tagNamesNeedIndexCreation []string) error {
733
775
734
776
func getQueryOptions (options []newstorage.QueryOption ) newstorage.QueryOptions {
735
777
var queryOptions newstorage.QueryOptions
778
+ queryOptions .PageSize = 25
736
779
737
780
for _ , option := range options {
738
781
option (& queryOptions )
739
782
}
740
783
741
- if queryOptions .PageSize == 0 {
742
- queryOptions .PageSize = 25
743
- }
744
-
745
784
return queryOptions
746
785
}
747
786
@@ -753,7 +792,7 @@ func getValueFromRows(rows rows, rawDocKey string) (string, error) {
753
792
return "" , fmt .Errorf (failWhileScanResultRows , err )
754
793
}
755
794
756
- value , err := getValueFromRawDoc (rawDoc , rawDocKey )
795
+ value , err := getStringValueFromRawDoc (rawDoc , rawDocKey )
757
796
if err != nil {
758
797
return "" , fmt .Errorf (`failure while getting the value associated with the "%s" key` +
759
798
`from the raw document` , rawDocKey )
@@ -762,7 +801,7 @@ func getValueFromRows(rows rows, rawDocKey string) (string, error) {
762
801
return value , nil
763
802
}
764
803
765
- func getValueFromRawDoc (rawDoc map [string ]interface {}, rawDocKey string ) (string , error ) {
804
+ func getStringValueFromRawDoc (rawDoc map [string ]interface {}, rawDocKey string ) (string , error ) {
766
805
value , ok := rawDoc [rawDocKey ]
767
806
if ! ok {
768
807
return "" , fmt .Errorf (`"%s" is missing from the raw document` , rawDocKey )
@@ -778,6 +817,70 @@ func getValueFromRawDoc(rawDoc map[string]interface{}, rawDocKey string) (string
778
817
return valueString , nil
779
818
}
780
819
820
+ func getPayloadsFromRawDocs (rawDocs []map [string ]interface {}) ([][]byte , error ) {
821
+ storedValues := make ([][]byte , len (rawDocs ))
822
+
823
+ for i , rawDoc := range rawDocs {
824
+ // If the rawDoc is nil, this means that the value could not be found.
825
+ // It is not considered an error.
826
+ if rawDoc == nil {
827
+ storedValues [i ] = nil
828
+
829
+ continue
830
+ }
831
+
832
+ // CouchDB still returns a raw document if the key has been deleted, so if this is a "deleted" raw document
833
+ // then we need to return nil to indicate that the value could not be found
834
+ isDeleted , containsIsDeleted := rawDoc [deletedFieldKey ]
835
+ if containsIsDeleted {
836
+ isDeletedBool , ok := isDeleted .(bool )
837
+ if ! ok {
838
+ return nil , errors .New ("failed to assert the retrieved deleted field value as a bool" )
839
+ }
840
+
841
+ if isDeletedBool {
842
+ storedValues [i ] = nil
843
+
844
+ continue
845
+ }
846
+ }
847
+
848
+ storedValue , err := getStringValueFromRawDoc (rawDoc , payloadFieldKey )
849
+ if err != nil {
850
+ return nil , fmt .Errorf (`failed to get the payload from the raw document: %w` , err )
851
+ }
852
+
853
+ storedValues [i ] = []byte (storedValue )
854
+ }
855
+
856
+ return storedValues , nil
857
+ }
858
+
859
+ func getRawDocsFromRows (rows rows ) ([]map [string ]interface {}, error ) {
860
+ moreDocumentsToRead := rows .Next ()
861
+
862
+ var rawDocs []map [string ]interface {}
863
+
864
+ for moreDocumentsToRead {
865
+ var rawDoc map [string ]interface {}
866
+ err := rows .ScanDoc (& rawDoc )
867
+ // For the regular Get method, Kivik actually returns a different error message if a document was deleted.
868
+ // When doing a bulk get, however, Kivik doesn't return an error message, and we have to check the "_deleted"
869
+ // field in the raw doc later. This is done in the getPayloadsFromRawDocs method.
870
+ // If the document wasn't found, we allow the nil raw doc to be appended since we don't consider it to be
871
+ // an error.
872
+ if err != nil && ! strings .Contains (err .Error (), bulkGetDocNotFoundErrMsgFromKivik ) {
873
+ return nil , fmt .Errorf (failWhileScanResultRows , err )
874
+ }
875
+
876
+ rawDocs = append (rawDocs , rawDoc )
877
+
878
+ moreDocumentsToRead = rows .Next ()
879
+ }
880
+
881
+ return rawDocs , nil
882
+ }
883
+
781
884
func getTagsFromRawDoc (rawDoc map [string ]interface {}) ([]newstorage.Tag , error ) {
782
885
var tags []newstorage.Tag
783
886
0 commit comments