Skip to content

Commit f2cd2cc

Browse files
Add read-only, previous commit, validate on open options.
1 parent 93ba538 commit f2cd2cc

File tree

3 files changed

+121
-9
lines changed

3 files changed

+121
-9
lines changed

objectbox-java/src/main/java/io/objectbox/BoxStore.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,6 @@ public static String getVersionNative() {
151151
*/
152152
static native long nativeCreateWithFlatOptions(byte[] options, byte[] model);
153153

154-
/**
155-
* Returns whether the store was created using read-only mode.
156-
* If true the schema is not updated and write transactions are not possible.
157-
*/
158154
static native boolean nativeIsReadOnly(long store);
159155

160156
static native void nativeDelete(long store);
@@ -286,6 +282,8 @@ public static boolean isObjectBrowserAvailable() {
286282

287283
private byte[] buildFlatStoreOptions(BoxStoreBuilder builder, String canonicalPath) {
288284
FlatBufferBuilder fbb = new FlatBufferBuilder();
285+
// TODO Is forceDefaults required by JNI or not?
286+
fbb.forceDefaults(true);
289287

290288
// Add non-integer values first...
291289
int directoryPathOffset = fbb.createString(canonicalPath);
@@ -294,11 +292,19 @@ private byte[] buildFlatStoreOptions(BoxStoreBuilder builder, String canonicalPa
294292

295293
// ...then build options.
296294
FlatStoreOptions.addDirectoryPath(fbb, directoryPathOffset);
297-
// FlatStoreOptions.addModelBytes(fbb, modelBytesOffset); // TODO Use this instead of model param on JNI method?
298295
FlatStoreOptions.addMaxDbSizeInKByte(fbb, builder.maxSizeInKByte);
299296
FlatStoreOptions.addMaxReaders(fbb, builder.maxReaders);
300297
// FlatStoreOptions.addDebugFlags(fbb, builder.debugFlags); // TODO Use this instead of nativeSetDebugFlags?
301298
// TODO Add new values.
299+
FlatStoreOptions.addReadOnly(fbb, builder.readOnly);
300+
FlatStoreOptions.addUsePreviousCommit(fbb, builder.usePreviousCommit);
301+
FlatStoreOptions.addUsePreviousCommitOnValidationFailure(fbb, builder.usePreviousCommitOnValidationFailure);
302+
if (builder.validateOnOpenMode != 0) {
303+
FlatStoreOptions.addValidateOnOpen(fbb, builder.validateOnOpenMode);
304+
if (builder.validateOnOpenPageLimit != 0) {
305+
FlatStoreOptions.addValidateOnOpenPageLimit(fbb, builder.validateOnOpenPageLimit);
306+
}
307+
}
302308

303309
int offset = FlatStoreOptions.endFlatStoreOptions(fbb);
304310
fbb.finish(offset);
@@ -483,6 +489,14 @@ public boolean isClosed() {
483489
return closed;
484490
}
485491

492+
/**
493+
* Whether the store was created using read-only mode.
494+
* If true the schema is not updated and write transactions are not possible.
495+
*/
496+
public boolean isReadOnly() {
497+
return nativeIsReadOnly(handle);
498+
}
499+
486500
/**
487501
* Closes the BoxStore and frees associated resources.
488502
* This method is useful for unit tests;

objectbox-java/src/main/java/io/objectbox/BoxStoreBuilder.java

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import io.objectbox.annotation.apihint.Internal;
3838
import io.objectbox.exception.DbException;
3939
import io.objectbox.ideasonly.ModelUpdate;
40+
import io.objectbox.model.ValidateOnOpenMode;
4041

4142
/**
4243
* Builds a {@link BoxStore} with optional configurations. The class is not initiated directly; use
@@ -91,6 +92,13 @@ public class BoxStoreBuilder {
9192

9293
int queryAttempts;
9394

95+
boolean readOnly;
96+
boolean usePreviousCommit;
97+
boolean usePreviousCommitOnValidationFailure;
98+
99+
int validateOnOpenMode;
100+
long validateOnOpenPageLimit;
101+
94102
TxCallback<?> failedReadTxAttemptCallback;
95103

96104
final List<EntityInfo<?>> entityInfoList = new ArrayList<>();
@@ -299,6 +307,85 @@ public BoxStoreBuilder maxSizeInKByte(long maxSizeInKByte) {
299307
return this;
300308
}
301309

310+
/**
311+
* Open the store in read-only mode: no schema update, no write transactions.
312+
* <p>
313+
* It is recommended to use this with {@link #usePreviousCommit()} to ensure no data is lost.
314+
*/
315+
public BoxStoreBuilder readOnly() {
316+
this.readOnly = true;
317+
return this;
318+
}
319+
320+
/**
321+
* Ignores the latest data snapshot (committed transaction state) and uses the previous snapshot instead.
322+
* When used with care (e.g. backup the DB files first), this option may also recover data removed by the latest
323+
* transaction.
324+
* <p>
325+
* It is recommended to use this with {@link #readOnly()} to ensure no data is lost.
326+
*/
327+
public BoxStoreBuilder usePreviousCommit() {
328+
if (usePreviousCommitOnValidationFailure) {
329+
throw new IllegalStateException("Can not use together with usePreviousCommitOnValidationFailure()");
330+
}
331+
this.usePreviousCommit = true;
332+
return this;
333+
}
334+
335+
/**
336+
* If consistency checks fail during opening the DB, ObjectBox
337+
* automatically switches to the previous commit. This way, this constitutes
338+
* an auto-recover mode from severe failures.
339+
* <p>
340+
* However, keep in mind that any consistency failure
341+
* is an indication that something is very wrong with OS/hardware and thus you may possibly alert your users.
342+
*
343+
* @see #usePreviousCommit()
344+
* @see #validateOnOpen(int)
345+
*/
346+
public BoxStoreBuilder usePreviousCommitOnValidationFailure() {
347+
if (usePreviousCommit) {
348+
throw new IllegalStateException("Can not use together with usePreviousCommit()");
349+
}
350+
this.usePreviousCommitOnValidationFailure = true;
351+
return this;
352+
}
353+
354+
/**
355+
* When a database is opened, ObjectBox can perform additional consistency checks on its database structure.
356+
* Reliable file systems already guarantee consistency, so this is primarily meant to deal with unreliable
357+
* OSes, file systems, or hardware.
358+
* <p>
359+
* Note: ObjectBox builds upon ACID storage, which already has strong consistency mechanisms in place.
360+
*
361+
* @param validateOnOpenMode One of {@link ValidateOnOpenMode}.
362+
*/
363+
public BoxStoreBuilder validateOnOpen(int validateOnOpenMode) {
364+
if (validateOnOpenMode < ValidateOnOpenMode.None || validateOnOpenMode > ValidateOnOpenMode.Full) {
365+
throw new IllegalArgumentException("Must be one of ValidateOnOpenMode");
366+
}
367+
this.validateOnOpenMode = validateOnOpenMode;
368+
return this;
369+
}
370+
371+
/**
372+
* To fine-tune {@link #validateOnOpen(int)}, you can specify a limit on how much data is looked at.
373+
* This is measured in "pages" with a page typically holding 4000.
374+
* Usually a low number (e.g. 1-20) is sufficient and does not impact startup performance significantly.
375+
* <p>
376+
* This can only be used with {@link ValidateOnOpenMode#Regular} and {@link ValidateOnOpenMode#WithLeaves}.
377+
*/
378+
public BoxStoreBuilder validateOnOpenPageLimit(long limit) {
379+
if (validateOnOpenMode != ValidateOnOpenMode.Regular && validateOnOpenMode != ValidateOnOpenMode.WithLeaves) {
380+
throw new IllegalStateException("Must call validateOnOpen(mode) with mode Regular or WithLeaves first");
381+
}
382+
if (limit < 1) {
383+
throw new IllegalArgumentException("limit must be positive");
384+
}
385+
this.validateOnOpenPageLimit = limit;
386+
return this;
387+
}
388+
302389
/**
303390
* @deprecated Use {@link #debugFlags} instead.
304391
*/

tests/objectbox-java-test/src/test/java/io/objectbox/BoxStoreBuilderTest.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,9 @@
1919
import org.junit.Before;
2020
import org.junit.Test;
2121

22-
import io.objectbox.exception.DbMaxReadersExceededException;
2322

24-
25-
import static org.junit.Assert.assertEquals;
26-
import static org.junit.Assert.assertNotNull;
2723
import static org.junit.Assert.assertSame;
24+
import static org.junit.Assert.assertTrue;
2825
import static org.junit.Assert.fail;
2926

3027
public class BoxStoreBuilderTest extends AbstractObjectBoxTest {
@@ -107,4 +104,18 @@ public void testMaxReaders() {
107104
// assertEquals(DbMaxReadersExceededException.class, exHolder[0].getClass());
108105
}
109106

107+
@Test
108+
public void readOnly() {
109+
// Create a database first.
110+
builder = createBoxStoreBuilder(false);
111+
store = builder.build();
112+
store.close();
113+
114+
// Then open existing database as read-only.
115+
builder = createBoxStoreBuilder(false);
116+
builder.readOnly();
117+
store = builder.build();
118+
119+
assertTrue(store.isReadOnly());
120+
}
110121
}

0 commit comments

Comments
 (0)