@@ -25,12 +25,14 @@ import ( //nolint:gci // False positive, seemingly caused by the CouchDB driver
2525const (
2626 couchDBUsersTable = "_users"
2727 revIDFieldKey = "_rev"
28+ deletedFieldKey = "_deleted"
2829
2930 designDocumentName = "AriesStorageDesignDocument"
3031 payloadFieldKey = "payload"
3132
3233 // Hardcoded strings returned from Kivik/CouchDB that we check for.
3334 docNotFoundErrMsgFromKivik = "Not Found: missing"
35+ bulkGetDocNotFoundErrMsgFromKivik = "not_found: missing"
3436 docDeletedErrMsgFromKivik = "Not Found: deleted"
3537 databaseNotFoundErrMsgFromKivik = "Not Found: Database does not exist."
3638 documentUpdateConflictErrMsgFromKivik = "Conflict: Document update conflict."
@@ -61,6 +63,7 @@ type db interface {
6163 Put (ctx context.Context , docID string , doc interface {}, options ... kivik.Options ) (rev string , err error )
6264 Find (ctx context.Context , query interface {}, options ... kivik.Options ) (* kivik.Rows , error )
6365 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 )
6467 Close (ctx context.Context ) error
6568}
6669
@@ -339,7 +342,7 @@ func (s *Store) Get(k string) ([]byte, error) {
339342 return nil , fmt .Errorf (failureWhileScanningRow , err )
340343 }
341344
342- storedValue , err := getValueFromRawDoc (rawDoc , payloadFieldKey )
345+ storedValue , err := getStringValueFromRawDoc (rawDoc , payloadFieldKey )
343346 if err != nil {
344347 return nil , fmt .Errorf ("failed to get payload from raw document: %w" , err )
345348 }
@@ -377,7 +380,21 @@ func (s *Store) GetTags(k string) ([]newstorage.Tag, error) {
377380// GetBulk fetches the values associated with the given keys.
378381// If a key doesn't exist, then a nil []byte is returned for that value. It is not considered an error.
379382func (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
381398}
382399
383400// Query returns all data that satisfies the expression. Expression format: TagName:TagValue.
@@ -535,14 +552,39 @@ func (s *Store) getRevID(k string) (string, error) {
535552 return "" , err
536553 }
537554
538- revID , err := getValueFromRawDoc (rawDoc , revIDFieldKey )
555+ revID , err := getStringValueFromRawDoc (rawDoc , revIDFieldKey )
539556 if err != nil {
540557 return "" , fmt .Errorf ("failed to get revision ID from the raw document: %w" , err )
541558 }
542559
543560 return revID , nil
544561}
545562
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+
546588type couchDBResultsIterator struct {
547589 store * Store
548590 resultRows rows
@@ -733,15 +775,12 @@ func createIndexes(db *kivik.DB, tagNamesNeedIndexCreation []string) error {
733775
734776func getQueryOptions (options []newstorage.QueryOption ) newstorage.QueryOptions {
735777 var queryOptions newstorage.QueryOptions
778+ queryOptions .PageSize = 25
736779
737780 for _ , option := range options {
738781 option (& queryOptions )
739782 }
740783
741- if queryOptions .PageSize == 0 {
742- queryOptions .PageSize = 25
743- }
744-
745784 return queryOptions
746785}
747786
@@ -753,7 +792,7 @@ func getValueFromRows(rows rows, rawDocKey string) (string, error) {
753792 return "" , fmt .Errorf (failWhileScanResultRows , err )
754793 }
755794
756- value , err := getValueFromRawDoc (rawDoc , rawDocKey )
795+ value , err := getStringValueFromRawDoc (rawDoc , rawDocKey )
757796 if err != nil {
758797 return "" , fmt .Errorf (`failure while getting the value associated with the "%s" key` +
759798 `from the raw document` , rawDocKey )
@@ -762,7 +801,7 @@ func getValueFromRows(rows rows, rawDocKey string) (string, error) {
762801 return value , nil
763802}
764803
765- func getValueFromRawDoc (rawDoc map [string ]interface {}, rawDocKey string ) (string , error ) {
804+ func getStringValueFromRawDoc (rawDoc map [string ]interface {}, rawDocKey string ) (string , error ) {
766805 value , ok := rawDoc [rawDocKey ]
767806 if ! ok {
768807 return "" , fmt .Errorf (`"%s" is missing from the raw document` , rawDocKey )
@@ -778,6 +817,70 @@ func getValueFromRawDoc(rawDoc map[string]interface{}, rawDocKey string) (string
778817 return valueString , nil
779818}
780819
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+
781884func getTagsFromRawDoc (rawDoc map [string ]interface {}) ([]newstorage.Tag , error ) {
782885 var tags []newstorage.Tag
783886
0 commit comments