diff --git a/docs/api-status.json b/docs/api-status.json index 1610f74..461258a 100644 --- a/docs/api-status.json +++ b/docs/api-status.json @@ -976,6 +976,18 @@ "comment": "Set an xattr.\n PREVIEW\n\nImplements:\n void rados_write_op_setxattr(rados_write_op_t write_op,\n const char * name,\n const char * value,\n size_t value_len)\n", "added_in_version": "v0.14.0", "expected_stable_version": "v0.16.0" + }, + { + "name": "ReadOpOmapGetValsByKeysStep.Next", + "comment": "Next gets the next omap key/value pair referenced by\nReadOpOmapGetValsByKeysStep's internal iterator.\nIf there are no more elements to retrieve, (nil, nil) is returned.\nMay be called only after Operate() finished.\n PREVIEW\n", + "added_in_version": "v0.14.0", + "expected_stable_version": "v0.16.0" + }, + { + "name": "ReadOp.GetOmapValuesByKeys", + "comment": "GetOmapValuesByKeys starts iterating over specific key/value pairs.\n PREVIEW\n\nImplements:\n void rados_read_op_omap_get_vals_by_keys(rados_read_op_t read_op,\n char const * const * keys,\n size_t keys_len,\n rados_omap_iter_t * iter,\n int * prval)\n", + "added_in_version": "v0.14.0", + "expected_stable_version": "v0.16.0" } ] }, @@ -1788,4 +1800,4 @@ } ] } -} +} \ No newline at end of file diff --git a/docs/api-status.md b/docs/api-status.md index b2b6bcc..e99eb19 100644 --- a/docs/api-status.md +++ b/docs/api-status.md @@ -25,6 +25,8 @@ WriteOp.Remove | v0.14.0 | v0.16.0 | ReadOp.AssertVersion | v0.14.0 | v0.16.0 | WriteOp.AssertVersion | v0.14.0 | v0.16.0 | WriteOp.SetXattr | v0.14.0 | v0.16.0 | +ReadOpOmapGetValsByKeysStep.Next | v0.14.0 | v0.16.0 | +ReadOp.GetOmapValuesByKeys | v0.14.0 | v0.16.0 | ## Package: rbd diff --git a/rados/read_op_omap_get_vals_by_keys.go b/rados/read_op_omap_get_vals_by_keys.go new file mode 100644 index 0000000..c328907 --- /dev/null +++ b/rados/read_op_omap_get_vals_by_keys.go @@ -0,0 +1,120 @@ +//go:build ceph_preview +// +build ceph_preview + +package rados + +// #cgo LDFLAGS: -lrados +// #include +// #include +// +import "C" + +import ( + "unsafe" +) + +// ReadOpOmapGetValsByKeysStep holds the result of the +// GetOmapValuesByKeys read operation. +// Result is valid only after Operate() was called. +type ReadOpOmapGetValsByKeysStep struct { + // C arguments + + iter C.rados_omap_iter_t + prval *C.int + + // Internal state + + // canIterate is only set after the operation is performed and is + // intended to prevent premature fetching of data. + canIterate bool +} + +func newReadOpOmapGetValsByKeysStep() *ReadOpOmapGetValsByKeysStep { + s := &ReadOpOmapGetValsByKeysStep{ + prval: (*C.int)(C.malloc(C.sizeof_int)), + } + + return s +} + +func (s *ReadOpOmapGetValsByKeysStep) free() { + s.canIterate = false + C.rados_omap_get_end(s.iter) + + C.free(unsafe.Pointer(s.prval)) + s.prval = nil +} + +func (s *ReadOpOmapGetValsByKeysStep) update() error { + err := getError(*s.prval) + s.canIterate = (err == nil) + + return err +} + +// Next gets the next omap key/value pair referenced by +// ReadOpOmapGetValsByKeysStep's internal iterator. +// If there are no more elements to retrieve, (nil, nil) is returned. +// May be called only after Operate() finished. +// PREVIEW +func (s *ReadOpOmapGetValsByKeysStep) Next() (*OmapKeyValue, error) { + if !s.canIterate { + return nil, ErrOperationIncomplete + } + + var ( + cKey *C.char + cVal *C.char + cValLen C.size_t + ) + + ret := C.rados_omap_get_next(s.iter, &cKey, &cVal, &cValLen) + if ret != 0 { + return nil, getError(ret) + } + + if cKey == nil { + // Iterator has reached the end of the list. + return nil, nil + } + + return &OmapKeyValue{ + Key: C.GoString(cKey), + Value: C.GoBytes(unsafe.Pointer(cVal), C.int(cValLen)), + }, nil +} + +// GetOmapValuesByKeys starts iterating over specific key/value pairs. +// PREVIEW +// +// Implements: +// void rados_read_op_omap_get_vals_by_keys(rados_read_op_t read_op, +// char const * const * keys, +// size_t keys_len, +// rados_omap_iter_t * iter, +// int * prval) +func (r *ReadOp) GetOmapValuesByKeys(keys []string) *ReadOpOmapGetValsByKeysStep { + s := newReadOpOmapGetValsByKeysStep() + r.steps = append(r.steps, s) + + cKeys := make([]*C.char, len(keys)) + defer func() { + for _, cKeyPtr := range cKeys { + C.free(unsafe.Pointer(cKeyPtr)) + } + }() + + for i, key := range keys { + cKeys[i] = C.CString(key) + } + + C.rados_read_op_omap_get_vals_by_keys( + r.op, + &cKeys[0], + C.size_t(len(keys)), + &s.iter, + s.prval, + ) + + return s +} diff --git a/rados/read_op_omap_get_vals_by_keys_test.go b/rados/read_op_omap_get_vals_by_keys_test.go new file mode 100644 index 0000000..8cfc3b8 --- /dev/null +++ b/rados/read_op_omap_get_vals_by_keys_test.go @@ -0,0 +1,62 @@ +//go:build ceph_preview +// +build ceph_preview + +package rados + +import ( + "github.com/stretchr/testify/assert" +) + +func (suite *RadosTestSuite) TestReadOpOmapGetValsByKeys() { + suite.SetupConnection() + ta := assert.New(suite.T()) + + var ( + oid = "TestReadOpOmapGetValsByKeys" + kvs = map[string][]byte{ + "k1": []byte("v1"), + "k2": []byte("v2"), + } + err error + ) + + // Create an object and populate it with data. + + op1 := CreateWriteOp() + defer op1.Release() + op1.Create(CreateIdempotent) + op1.SetOmap(kvs) + err = op1.Operate(suite.ioctx, oid, OperationNoFlag) + ta.NoError(err) + + // Retrieve objects omap key-value pairs by ks keys, but add + // a few non-existingones which are expected to be silently skipped. + + var ( + actualKVs = make(map[string][]byte) + ks = []string{"k1", "k2", "xx", "yy"} + ) + + op2 := CreateReadOp() + defer op2.Release() + kvStep := op2.GetOmapValuesByKeys(ks) + + _, err = kvStep.Next() + ta.Equal(ErrOperationIncomplete, err) + + err = op2.Operate(suite.ioctx, oid, OperationNoFlag) + ta.NoError(err) + + for { + kv, err := kvStep.Next() + ta.NoError(err) + + if kv == nil { + break + } + + actualKVs[kv.Key] = kv.Value + } + + ta.Equal(kvs, actualKVs) +}