Skip to content

Commit c31d125

Browse files
committed
Add MDB_MULTIPLE support (fixes #24 and fixes #28)
1 parent 3dacf6e commit c31d125

File tree

3 files changed

+103
-1
lines changed

3 files changed

+103
-1
lines changed

src/main/java/org/lmdbjava/Cursor.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import static org.lmdbjava.Library.LIB;
3030
import static org.lmdbjava.MaskedFlag.isSet;
3131
import static org.lmdbjava.MaskedFlag.mask;
32+
import static org.lmdbjava.PutFlags.MDB_MULTIPLE;
3233
import static org.lmdbjava.PutFlags.MDB_NODUPDATA;
3334
import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE;
3435
import static org.lmdbjava.PutFlags.MDB_RESERVE;
@@ -224,6 +225,42 @@ public boolean put(final T key, final T val, final PutFlags... op) {
224225
return true;
225226
}
226227

228+
/**
229+
* Put multiple values into the database in one <code>MDB_MULTIPLE</code>
230+
* operation.
231+
*
232+
* <p>
233+
* The database must have been opened with {@link DbiFlags#MDB_DUPFIXED}. The
234+
* buffer must contain fixed-sized values to be inserted. The size of each
235+
* element is calculated from the buffer's size divided by the given element
236+
* count. For example, to populate 10 X 4 byte integers at once, present a
237+
* buffer of 40 bytes and specify the element as 10.
238+
*
239+
* @param key key to store in the database (not null)
240+
* @param val value to store in the database (not null)
241+
* @param elements number of elements contained in the passed value buffer
242+
* @param op options for operation (must set <code>MDB_MULTIPLE</code>)
243+
*/
244+
public void putMultiple(final T key, final T val, final int elements,
245+
final PutFlags... op) {
246+
if (SHOULD_CHECK) {
247+
requireNonNull(txn);
248+
requireNonNull(key);
249+
requireNonNull(val);
250+
txn.checkReady();
251+
txn.checkWritesAllowed();
252+
}
253+
final int mask = mask(op);
254+
if (SHOULD_CHECK && !isSet(mask, MDB_MULTIPLE)) {
255+
throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag");
256+
}
257+
txn.kv().keyIn(key);
258+
final Pointer dataPtr = txn.kv().valInMulti(val, elements);
259+
final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(),
260+
dataPtr, mask);
261+
checkRc(rc);
262+
}
263+
227264
/**
228265
* Renew a cursor handle.
229266
*

src/main/java/org/lmdbjava/KeyVal.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import jnr.ffi.Pointer;
2525
import jnr.ffi.provider.MemoryManager;
2626
import static org.lmdbjava.BufferProxy.MDB_VAL_STRUCT_SIZE;
27+
import static org.lmdbjava.BufferProxy.STRUCT_FIELD_OFFSET_SIZE;
2728
import static org.lmdbjava.Library.RUNTIME;
2829

2930
/**
@@ -37,6 +38,7 @@ final class KeyVal<T> implements AutoCloseable {
3738
private boolean closed;
3839
private T k;
3940
private final BufferProxy<T> proxy;
41+
private final Pointer ptrArray;
4042
private final Pointer ptrKey;
4143
private final long ptrKeyAddr;
4244
private final Pointer ptrVal;
@@ -50,7 +52,8 @@ final class KeyVal<T> implements AutoCloseable {
5052
this.v = proxy.allocate();
5153
ptrKey = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false);
5254
ptrKeyAddr = ptrKey.address();
53-
ptrVal = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false);
55+
ptrArray = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE * 2, false);
56+
ptrVal = ptrArray.slice(0, MDB_VAL_STRUCT_SIZE);
5457
ptrValAddr = ptrVal.address();
5558
}
5659

@@ -97,6 +100,35 @@ void valIn(final int size) {
97100
proxy.in(v, size, ptrVal, ptrValAddr);
98101
}
99102

103+
/**
104+
* Prepares an array suitable for presentation as the data argument to a
105+
* <code>MDB_MULTIPLE</code> put.
106+
*
107+
* <p>
108+
* The returned array is equivalent of two <code>MDB_val</code>s as follows:
109+
*
110+
* <ul>
111+
* <li>ptrVal1.data = pointer to the data address of passed buffer</li>
112+
* <li>ptrVal1.size = size of each individual data element</li>
113+
* <li>ptrVal2.data = unused</li>
114+
* <li>ptrVal2.size = number of data elements (as passed to this method)</li>
115+
* </ul>
116+
*
117+
* @param val a user-provided buffer with data elements (required)
118+
* @param elements number of data elements the user has provided
119+
* @return a properly-prepared pointer to an array for the operation
120+
*/
121+
Pointer valInMulti(final T val, final int elements) {
122+
final long ptrVal2SizeOff = MDB_VAL_STRUCT_SIZE + STRUCT_FIELD_OFFSET_SIZE;
123+
ptrArray.putLong(ptrVal2SizeOff, elements); // ptrVal2.size
124+
proxy.in(val, ptrVal, ptrValAddr); // ptrVal1.data
125+
final long totalBufferSize = ptrVal.getLong(STRUCT_FIELD_OFFSET_SIZE);
126+
final long elemSize = totalBufferSize / elements;
127+
ptrVal.putLong(STRUCT_FIELD_OFFSET_SIZE, elemSize); // ptrVal1.size
128+
129+
return ptrArray;
130+
}
131+
100132
T valOut() {
101133
v = proxy.out(v, ptrVal, ptrValAddr);
102134
return v;

src/test/java/org/lmdbjava/CursorTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import static java.lang.Long.BYTES;
2727
import static java.lang.Long.MIN_VALUE;
2828
import java.nio.ByteBuffer;
29+
import static java.nio.ByteBuffer.allocateDirect;
2930
import static org.hamcrest.CoreMatchers.is;
3031
import static org.hamcrest.MatcherAssert.assertThat;
3132
import org.junit.After;
@@ -38,10 +39,12 @@
3839
import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL;
3940
import org.lmdbjava.Cursor.ClosedException;
4041
import static org.lmdbjava.DbiFlags.MDB_CREATE;
42+
import static org.lmdbjava.DbiFlags.MDB_DUPFIXED;
4143
import static org.lmdbjava.DbiFlags.MDB_DUPSORT;
4244
import static org.lmdbjava.Env.create;
4345
import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR;
4446
import static org.lmdbjava.PutFlags.MDB_APPENDDUP;
47+
import static org.lmdbjava.PutFlags.MDB_MULTIPLE;
4548
import static org.lmdbjava.PutFlags.MDB_NODUPDATA;
4649
import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE;
4750
import static org.lmdbjava.SeekOp.MDB_FIRST;
@@ -169,6 +172,36 @@ public void delete() {
169172
}
170173
}
171174

175+
@Test
176+
public void putMultiple() {
177+
final Dbi<ByteBuffer> db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT,
178+
MDB_DUPFIXED);
179+
final int elemCount = 20;
180+
181+
final ByteBuffer values = allocateDirect(Integer.BYTES * elemCount);
182+
for (int i = 1; i <= elemCount; i++) {
183+
values.putInt(i);
184+
}
185+
values.flip();
186+
187+
final int key = 100;
188+
final ByteBuffer k = bb(key);
189+
try (Txn<ByteBuffer> txn = env.txnWrite()) {
190+
final Cursor<ByteBuffer> c = db.openCursor(txn);
191+
c.putMultiple(k, values, elemCount, MDB_MULTIPLE);
192+
assertThat(c.count(), is((long) elemCount));
193+
}
194+
}
195+
196+
@Test(expected = IllegalArgumentException.class)
197+
public void putMultipleWithoutMdbMultipleFlag() {
198+
final Dbi<ByteBuffer> db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT);
199+
try (Txn<ByteBuffer> txn = env.txnWrite()) {
200+
final Cursor<ByteBuffer> c = db.openCursor(txn);
201+
c.putMultiple(bb(100), bb(1), 1);
202+
}
203+
}
204+
172205
@Test
173206
public void renewTxRo() {
174207
final Dbi<ByteBuffer> db = env.openDbi(DB_1, MDB_CREATE);

0 commit comments

Comments
 (0)