Skip to content

Commit 927ec9a

Browse files
committed
Dbi read-only support (fixes lmdbjava#8)
1 parent 6dff2c5 commit 927ec9a

8 files changed

Lines changed: 106 additions & 41 deletions

File tree

src/main/java/org/lmdbjava/Dbi.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ public final class Dbi<T> {
4747
Dbi(final Env<T> env, final Txn<T> txn, final String name,
4848
final DbiFlags... flags) throws CommittedException, LmdbNativeException,
4949
ReadWriteRequiredException {
50-
requireNonNull(env);
51-
requireNonNull(txn);
52-
txn.checkNotCommitted();
53-
txn.checkWritesAllowed();
5450
this.env = env;
5551
this.name = name;
5652
final int flagsMask = mask(flags);

src/main/java/org/lmdbjava/Env.java

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
import jnr.ffi.byref.PointerByReference;
2424
import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL;
2525
import static org.lmdbjava.ByteUnit.MEBIBYTES;
26+
import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV;
2627
import static org.lmdbjava.Library.LIB;
2728
import org.lmdbjava.Library.MDB_envinfo;
2829
import org.lmdbjava.Library.MDB_stat;
2930
import static org.lmdbjava.Library.RUNTIME;
31+
import static org.lmdbjava.MaskedFlag.isSet;
3032
import static org.lmdbjava.MaskedFlag.mask;
3133
import static org.lmdbjava.ResultCodeMapper.checkRc;
32-
import static org.lmdbjava.TxnFlags.MDB_RDONLY;
34+
import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN;
3335

3436
/**
3537
* LMDB environment.
@@ -57,8 +59,7 @@ public final class Env<T> implements AutoCloseable {
5759
* @return the environment (never null)
5860
*/
5961
public static Builder<ByteBuffer> create() {
60-
Env<ByteBuffer> env = new Env<>(PROXY_OPTIMAL);
61-
return new Builder<>(env);
62+
return new Builder<>(PROXY_OPTIMAL);
6263
}
6364

6465
/**
@@ -69,7 +70,7 @@ public static Builder<ByteBuffer> create() {
6970
* @return the environment (never null)
7071
*/
7172
public static <T> Builder<T> create(final BufferProxy<T> proxy) {
72-
return new Builder<>(new Env<>(proxy));
73+
return new Builder<>(proxy);
7374
}
7475

7576
/**
@@ -82,23 +83,21 @@ public static <T> Builder<T> create(final BufferProxy<T> proxy) {
8283
* @return env the environment (never null)
8384
*/
8485
public static Env<ByteBuffer> open(File path, int size, EnvFlags... flags) {
85-
Env<ByteBuffer> env = new Env<>(PROXY_OPTIMAL);
86-
return new Builder<>(env)
87-
.setMaxDbs(1)
86+
return new Builder<>(PROXY_OPTIMAL)
8887
.setMapSize(size, MEBIBYTES)
8988
.open(path, flags);
9089
}
9190

9291
private boolean closed = false;
9392
private final BufferProxy<T> proxy;
9493
final Pointer ptr;
94+
final boolean readOnly;
9595

96-
private Env(final BufferProxy<T> proxy) {
97-
requireNonNull(proxy);
96+
private Env(final BufferProxy<T> proxy, final Pointer ptr,
97+
final boolean readOnly) {
9898
this.proxy = proxy;
99-
final PointerByReference envPtr = new PointerByReference();
100-
checkRc(LIB.mdb_env_create(envPtr));
101-
ptr = envPtr.getValue();
99+
this.readOnly = readOnly;
100+
this.ptr = ptr;
102101
}
103102

104103
/**
@@ -188,9 +187,9 @@ public boolean isClosed() {
188187
* @return a database that is ready to use
189188
*/
190189
public Dbi<T> openDbi(final String name, final DbiFlags... flags) {
191-
try (Txn<T> txn = txnWrite()) {
190+
try (Txn<T> txn = readOnly ? txnRead() : txnWrite()) {
192191
final Dbi<T> dbi = new Dbi<>(this, txn, name, flags);
193-
txn.commit();
192+
txn.commit(); // even RO Txns require a commit to retain Dbi in Env
194193
return dbi;
195194
}
196195
}
@@ -247,7 +246,7 @@ public Txn<T> txn(final Txn<T> parent, final TxnFlags... flags) {
247246
* @return a read-only transaction
248247
*/
249248
public Txn<T> txnRead() {
250-
return new Txn<>(this, null, proxy, MDB_RDONLY);
249+
return new Txn<>(this, null, proxy, MDB_RDONLY_TXN);
251250
}
252251

253252
/**
@@ -292,15 +291,19 @@ public AlreadyOpenException() {
292291
/**
293292
* Builder for configuring and opening Env.
294293
*
295-
* @param <T> buffer type
294+
* @param <T>
296295
*/
297296
public static final class Builder<T> {
298297

299-
private final Env<T> env;
298+
private long mapSize = MEBIBYTES.toBytes(1);
299+
private int maxDbs = 1;
300+
private int maxReaders = 1;
300301
private boolean opened = false;
302+
private final BufferProxy<T> proxy;
301303

302-
private Builder(Env<T> env) {
303-
this.env = env;
304+
private Builder(final BufferProxy<T> proxy) {
305+
requireNonNull(proxy);
306+
this.proxy = proxy;
304307
}
305308

306309
/**
@@ -317,10 +320,17 @@ public Env<T> open(final File path, final int mode,
317320
if (opened) {
318321
throw new AlreadyOpenException();
319322
}
320-
final int flagsMask = mask(flags);
321-
checkRc(LIB.mdb_env_open(env.ptr, path.getAbsolutePath(), flagsMask, mode));
322323
opened = true;
323-
return this.env;
324+
final PointerByReference envPtr = new PointerByReference();
325+
checkRc(LIB.mdb_env_create(envPtr));
326+
final Pointer ptr = envPtr.getValue();
327+
checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize));
328+
checkRc(LIB.mdb_env_set_maxdbs(ptr, maxDbs));
329+
checkRc(LIB.mdb_env_set_maxreaders(ptr, maxReaders));
330+
final int flagsMask = mask(flags);
331+
final boolean readOnly = isSet(flagsMask, MDB_RDONLY_ENV);
332+
checkRc(LIB.mdb_env_open(ptr, path.getAbsolutePath(), flagsMask, mode));
333+
return new Env<>(proxy, ptr, readOnly);
324334
}
325335

326336
/**
@@ -344,7 +354,7 @@ public Builder<T> setMapSize(final long mapSize) {
344354
if (opened) {
345355
throw new AlreadyOpenException();
346356
}
347-
checkRc(LIB.mdb_env_set_mapsize(env.ptr, mapSize));
357+
this.mapSize = mapSize;
348358
return this;
349359
}
350360

@@ -369,7 +379,7 @@ public Builder<T> setMaxDbs(final int dbs) {
369379
if (opened) {
370380
throw new AlreadyOpenException();
371381
}
372-
checkRc(LIB.mdb_env_set_maxdbs(env.ptr, dbs));
382+
this.maxDbs = dbs;
373383
return this;
374384
}
375385

@@ -383,7 +393,7 @@ public Builder<T> setMaxReaders(final int readers) {
383393
if (opened) {
384394
throw new AlreadyOpenException();
385395
}
386-
checkRc(LIB.mdb_env_set_maxreaders(env.ptr, readers));
396+
this.maxReaders = readers;
387397
return this;
388398
}
389399
}

src/main/java/org/lmdbjava/EnvFlags.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
*/
2121
public enum EnvFlags implements MaskedFlag {
2222

23+
/**
24+
* mmap at a fixed address (experimental)
25+
*/
2326
/**
2427
* mmap at a fixed address (experimental)
2528
*/
@@ -35,7 +38,7 @@ public enum EnvFlags implements MaskedFlag {
3538
/**
3639
* read only
3740
*/
38-
MDB_RDONLY(0x2_0000),
41+
MDB_RDONLY_ENV(0x2_0000),
3942
/**
4043
* don't fsync metapage after commit
4144
*/

src/main/java/org/lmdbjava/Txn.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import static org.lmdbjava.MaskedFlag.isSet;
2828
import static org.lmdbjava.MaskedFlag.mask;
2929
import static org.lmdbjava.ResultCodeMapper.checkRc;
30-
import static org.lmdbjava.TxnFlags.MDB_RDONLY;
30+
import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN;
3131

3232
/**
3333
* LMDB transaction.
@@ -61,7 +61,10 @@ public final class Txn<T> implements AutoCloseable {
6161
this.env = env;
6262
this.proxy = proxy;
6363
final int flagsMask = mask(flags);
64-
this.readOnly = isSet(flagsMask, MDB_RDONLY);
64+
this.readOnly = isSet(flagsMask, MDB_RDONLY_TXN);
65+
if (env.readOnly && !this.readOnly) {
66+
throw new EnvIsReadOnly();
67+
}
6568
this.parent = parent;
6669
if (parent != null) {
6770
if ((parent.readOnly && !this.readOnly)
@@ -296,6 +299,21 @@ public CommittedException() {
296299
}
297300
}
298301

302+
/**
303+
* The proposed R-W transaction is incompatible with a R-O Env.
304+
*/
305+
public static class EnvIsReadOnly extends LmdbException {
306+
307+
private static final long serialVersionUID = 1L;
308+
309+
/**
310+
* Creates a new instance.
311+
*/
312+
public EnvIsReadOnly() {
313+
super("Read-write Txn incompatible with read-only Env");
314+
}
315+
}
316+
299317
/**
300318
* The proposed transaction is incompatible with its parent transaction.
301319
*/

src/main/java/org/lmdbjava/TxnFlags.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public enum TxnFlags implements MaskedFlag {
2222
/**
2323
* Read only
2424
*/
25-
MDB_RDONLY(0x2_0000);
25+
MDB_RDONLY_TXN(0x2_0000);
2626

2727
private final int mask;
2828

src/test/java/org/lmdbjava/EnvTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
import static org.lmdbjava.Env.create;
3636
import static org.lmdbjava.Env.open;
3737
import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR;
38+
import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV;
39+
import static org.lmdbjava.TestUtils.DB_1;
40+
import static org.lmdbjava.TestUtils.bb;
3841

3942
public class EnvTest {
4043

@@ -193,6 +196,20 @@ public void info() throws IOException {
193196
assertThat(info.numReaders, is(0));
194197
}
195198

199+
@Test
200+
public void readOnlySupported() throws IOException {
201+
final File path = tmp.newFolder();
202+
try (final Env<ByteBuffer> rwEnv = create().open(path)) {
203+
final Dbi<ByteBuffer> rwDb = rwEnv.openDbi(DB_1, MDB_CREATE);
204+
rwDb.put(bb(1), bb(42));
205+
}
206+
final Env<ByteBuffer> roEnv = create().open(path, MDB_RDONLY_ENV);
207+
final Dbi<ByteBuffer> roDb = roEnv.openDbi(DB_1);
208+
try (final Txn<ByteBuffer> roTxn = roEnv.txnRead()) {
209+
assertThat(roDb.get(roTxn, bb(1)), notNullValue());
210+
}
211+
}
212+
196213
@Test
197214
public void stats() throws IOException {
198215
final File path = tmp.newFile();

src/test/java/org/lmdbjava/MaskedFlagTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import org.junit.Test;
2222
import static org.lmdbjava.EnvFlags.MDB_FIXEDMAP;
2323
import static org.lmdbjava.EnvFlags.MDB_NOSYNC;
24-
import static org.lmdbjava.EnvFlags.MDB_RDONLY;
24+
import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV;
2525
import static org.lmdbjava.MaskedFlag.isSet;
2626
import static org.lmdbjava.MaskedFlag.mask;
2727

@@ -31,20 +31,20 @@ public class MaskedFlagTest {
3131
public void isSetOperates() {
3232
assertThat(isSet(0, MDB_NOSYNC), is(false));
3333
assertThat(isSet(0, MDB_FIXEDMAP), is(false));
34-
assertThat(isSet(0, MDB_RDONLY), is(false));
34+
assertThat(isSet(0, MDB_RDONLY_ENV), is(false));
3535

3636
assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_NOSYNC), is(false));
3737
assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_FIXEDMAP), is(true));
38-
assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_RDONLY), is(false));
38+
assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_RDONLY_ENV), is(false));
3939

4040
assertThat(isSet(MDB_NOSYNC.getMask(), MDB_NOSYNC), is(true));
4141
assertThat(isSet(MDB_NOSYNC.getMask(), MDB_FIXEDMAP), is(false));
42-
assertThat(isSet(MDB_NOSYNC.getMask(), MDB_RDONLY), is(false));
42+
assertThat(isSet(MDB_NOSYNC.getMask(), MDB_RDONLY_ENV), is(false));
4343

4444
final int syncFixed = mask(MDB_NOSYNC, MDB_FIXEDMAP);
4545
assertThat(isSet(syncFixed, MDB_NOSYNC), is(true));
4646
assertThat(isSet(syncFixed, MDB_FIXEDMAP), is(true));
47-
assertThat(isSet(syncFixed, MDB_RDONLY), is(false));
47+
assertThat(isSet(syncFixed, MDB_RDONLY_ENV), is(false));
4848
}
4949

5050
@Test

src/test/java/org/lmdbjava/TxnTest.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.concurrent.atomic.AtomicLong;
2222
import static org.hamcrest.CoreMatchers.is;
2323
import static org.hamcrest.CoreMatchers.not;
24+
import static org.hamcrest.CoreMatchers.notNullValue;
2425
import static org.hamcrest.CoreMatchers.nullValue;
2526
import static org.hamcrest.MatcherAssert.assertThat;
2627
import org.junit.Before;
@@ -32,33 +33,53 @@
3233
import org.lmdbjava.Env.AlreadyClosedException;
3334
import static org.lmdbjava.Env.create;
3435
import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR;
36+
import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV;
3537
import static org.lmdbjava.TestUtils.DB_1;
3638
import static org.lmdbjava.TestUtils.POSIX_MODE;
3739
import static org.lmdbjava.TestUtils.bb;
3840
import org.lmdbjava.Txn.CommittedException;
41+
import org.lmdbjava.Txn.EnvIsReadOnly;
3942
import org.lmdbjava.Txn.IncompatibleParent;
4043
import org.lmdbjava.Txn.NotResetException;
4144
import org.lmdbjava.Txn.ReadOnlyRequiredException;
4245
import org.lmdbjava.Txn.ReadWriteRequiredException;
4346
import org.lmdbjava.Txn.ResetException;
44-
import static org.lmdbjava.TxnFlags.MDB_RDONLY;
47+
import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN;
4548

4649
public class TxnTest {
4750

4851
@Rule
4952
public final TemporaryFolder tmp = new TemporaryFolder();
5053
private Env<ByteBuffer> env;
54+
private File path;
5155

5256
@Before
5357
public void before() throws IOException {
54-
final File path = tmp.newFile();
58+
path = tmp.newFile();
5559
env = create()
5660
.setMapSize(100, KIBIBYTES)
5761
.setMaxReaders(1)
5862
.setMaxDbs(2)
5963
.open(path, POSIX_MODE, MDB_NOSUBDIR);
6064
}
6165

66+
@Test
67+
public void readOnlyTxnAllowedInReadOnlyEnv() throws IOException {
68+
env.openDbi(DB_1, MDB_CREATE);
69+
final Env<ByteBuffer> roEnv = create().open(path, MDB_NOSUBDIR,
70+
MDB_RDONLY_ENV);
71+
assertThat(roEnv.txnRead(), is(notNullValue()));
72+
}
73+
74+
@Test(expected = EnvIsReadOnly.class)
75+
public void readWriteTxnDeniedInReadOnlyEnv() throws IOException {
76+
env.openDbi(DB_1, MDB_CREATE);
77+
env.close();
78+
final Env<ByteBuffer> roEnv = create().open(path, MDB_NOSUBDIR,
79+
MDB_RDONLY_ENV);
80+
roEnv.txnWrite(); // error
81+
}
82+
6283
@Test(expected = CommittedException.class)
6384
public void testCheckNotCommitted() {
6485
final Txn<ByteBuffer> txn = env.txnRead();
@@ -148,7 +169,7 @@ public void txParentROChildRWIncompatible() {
148169
@Test(expected = IncompatibleParent.class)
149170
public void txParentRWChildROIncompatible() {
150171
final Txn<ByteBuffer> txRoot = env.txnWrite();
151-
env.txn(txRoot, MDB_RDONLY); // error
172+
env.txn(txRoot, MDB_RDONLY_TXN); // error
152173
}
153174

154175
@Test

0 commit comments

Comments
 (0)