Skip to content

Commit 32021fb

Browse files
Store: add experimental maxDataSizeInKByte (#149)
1 parent 29747e2 commit 32021fb

File tree

3 files changed

+131
-8
lines changed

3 files changed

+131
-8
lines changed

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

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ public class BoxStoreBuilder {
7777
/** Defaults to {@link #DEFAULT_MAX_DB_SIZE_KBYTE}. */
7878
long maxSizeInKByte = DEFAULT_MAX_DB_SIZE_KBYTE;
7979

80+
long maxDataSizeInKByte;
81+
8082
/** On Android used for native library loading. */
8183
@Nullable Object context;
8284
@Nullable Object relinker;
@@ -339,10 +341,34 @@ BoxStoreBuilder modelUpdate(ModelUpdate modelUpdate) {
339341
* (for example you insert data in an infinite loop).
340342
*/
341343
public BoxStoreBuilder maxSizeInKByte(long maxSizeInKByte) {
344+
if (maxSizeInKByte <= maxDataSizeInKByte) {
345+
throw new IllegalArgumentException("maxSizeInKByte must be larger than maxDataSizeInKByte.");
346+
}
342347
this.maxSizeInKByte = maxSizeInKByte;
343348
return this;
344349
}
345350

351+
/**
352+
* This API is experimental and may change or be removed in future releases.
353+
* <p>
354+
* Sets the maximum size the data stored in the database can grow to. Must be below {@link #maxSizeInKByte(long)}.
355+
* <p>
356+
* Different from {@link #maxSizeInKByte(long)} this only counts bytes stored in objects, excluding system and
357+
* metadata. However, it is more involved than database size tracking, e.g. it stores an internal counter.
358+
* Only use this if a stricter, more accurate limit is required.
359+
* <p>
360+
* When the data limit is reached data can be removed to get below the limit again (assuming the database size limit
361+
* is not also reached).
362+
*/
363+
@Experimental
364+
public BoxStoreBuilder maxDataSizeInKByte(long maxDataSizeInKByte) {
365+
if (maxDataSizeInKByte >= maxSizeInKByte) {
366+
throw new IllegalArgumentException("maxDataSizeInKByte must be smaller than maxSizeInKByte.");
367+
}
368+
this.maxDataSizeInKByte = maxDataSizeInKByte;
369+
return this;
370+
}
371+
346372
/**
347373
* Open the store in read-only mode: no schema update, no write transactions are allowed (would throw).
348374
*/
@@ -491,13 +517,12 @@ byte[] buildFlatStoreOptions(String canonicalPath) {
491517
FlatStoreOptions.addValidateOnOpenPageLimit(fbb, validateOnOpenPageLimit);
492518
}
493519
}
494-
if(skipReadSchema) FlatStoreOptions.addSkipReadSchema(fbb, skipReadSchema);
495-
if(usePreviousCommit) FlatStoreOptions.addUsePreviousCommit(fbb, usePreviousCommit);
496-
if(readOnly) FlatStoreOptions.addReadOnly(fbb, readOnly);
497-
if(noReaderThreadLocals) FlatStoreOptions.addNoReaderThreadLocals(fbb, noReaderThreadLocals);
498-
if (debugFlags != 0) {
499-
FlatStoreOptions.addDebugFlags(fbb, debugFlags);
500-
}
520+
if (skipReadSchema) FlatStoreOptions.addSkipReadSchema(fbb, true);
521+
if (usePreviousCommit) FlatStoreOptions.addUsePreviousCommit(fbb, true);
522+
if (readOnly) FlatStoreOptions.addReadOnly(fbb, true);
523+
if (noReaderThreadLocals) FlatStoreOptions.addNoReaderThreadLocals(fbb, true);
524+
if (debugFlags != 0) FlatStoreOptions.addDebugFlags(fbb, debugFlags);
525+
if (maxDataSizeInKByte > 0) FlatStoreOptions.addMaxDataSizeInKbyte(fbb, maxDataSizeInKByte);
501526

502527
int offset = FlatStoreOptions.endFlatStoreOptions(fbb);
503528
fbb.finish(offset);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2022 ObjectBox Ltd. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.objectbox.exception;
18+
19+
/**
20+
* Thrown when applying a transaction would exceed the {@link io.objectbox.BoxStoreBuilder#maxDataSizeInKByte(long) maxDataSizeInKByte}
21+
* configured for the store.
22+
*/
23+
public class DbMaxDataSizeExceededException extends DbException {
24+
public DbMaxDataSizeExceededException(String message) {
25+
super(message);
26+
}
27+
}

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

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package io.objectbox;
1818

19+
import io.objectbox.exception.DbFullException;
20+
import io.objectbox.exception.DbMaxDataSizeExceededException;
1921
import io.objectbox.exception.PagesCorruptException;
2022
import io.objectbox.model.ValidateOnOpenMode;
2123
import org.greenrobot.essentials.io.IoUtils;
@@ -28,7 +30,6 @@
2830
import java.io.InputStream;
2931
import java.nio.file.Files;
3032
import java.nio.file.Path;
31-
import java.util.Collections;
3233
import java.util.HashSet;
3334
import java.util.List;
3435
import java.util.Set;
@@ -38,13 +39,16 @@
3839
import static org.junit.Assert.assertEquals;
3940
import static org.junit.Assert.assertNotNull;
4041
import static org.junit.Assert.assertSame;
42+
import static org.junit.Assert.assertThrows;
4143
import static org.junit.Assert.assertTrue;
4244
import static org.junit.Assert.fail;
4345

4446
public class BoxStoreBuilderTest extends AbstractObjectBoxTest {
4547

4648
private BoxStoreBuilder builder;
4749

50+
private static final String LONG_STRING = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
51+
4852
@Override
4953
protected BoxStore createBoxStore() {
5054
// Standard setup of store not required
@@ -167,6 +171,73 @@ public void readOnly() {
167171
assertTrue(store.isReadOnly());
168172
}
169173

174+
@Test
175+
public void maxSize_invalidValues_throw() {
176+
// Max data larger than max database size throws.
177+
builder.maxSizeInKByte(10);
178+
IllegalArgumentException exSmaller = assertThrows(
179+
IllegalArgumentException.class,
180+
() -> builder.maxDataSizeInKByte(11)
181+
);
182+
assertEquals("maxDataSizeInKByte must be smaller than maxSizeInKByte.", exSmaller.getMessage());
183+
184+
// Max database size smaller than max data size throws.
185+
builder.maxDataSizeInKByte(9);
186+
IllegalArgumentException exLarger = assertThrows(
187+
IllegalArgumentException.class,
188+
() -> builder.maxSizeInKByte(8)
189+
);
190+
assertEquals("maxSizeInKByte must be larger than maxDataSizeInKByte.", exLarger.getMessage());
191+
}
192+
193+
@Test
194+
public void maxFileSize() {
195+
builder = createBoxStoreBuilder(null);
196+
builder.maxSizeInKByte(30); // Empty file is around 12 KB, object below adds about 8 KB each.
197+
store = builder.build();
198+
putTestEntity(LONG_STRING, 1);
199+
TestEntity testEntity2 = createTestEntity(LONG_STRING, 2);
200+
DbFullException dbFullException = assertThrows(
201+
DbFullException.class,
202+
() -> getTestEntityBox().put(testEntity2)
203+
);
204+
assertEquals("Could not commit tx", dbFullException.getMessage());
205+
206+
// Re-open with larger size.
207+
store.close();
208+
builder.maxSizeInKByte(40);
209+
store = builder.build();
210+
testEntity2.setId(0); // Clear ID of object that failed to put.
211+
getTestEntityBox().put(testEntity2);
212+
}
213+
214+
@Test
215+
public void maxDataSize() {
216+
// Put until max data size is reached, but still below max database size.
217+
builder = createBoxStoreBuilder(null);
218+
builder.maxSizeInKByte(50); // Empty file is around 12 KB, each put adds about 8 KB.
219+
builder.maxDataSizeInKByte(1);
220+
store = builder.build();
221+
222+
TestEntity testEntity1 = putTestEntity(LONG_STRING, 1);
223+
TestEntity testEntity2 = createTestEntity(LONG_STRING, 2);
224+
DbMaxDataSizeExceededException maxDataExc = assertThrows(
225+
DbMaxDataSizeExceededException.class,
226+
() -> getTestEntityBox().put(testEntity2)
227+
);
228+
assertEquals("Exceeded user-set maximum by [bytes]: 64", maxDataExc.getMessage());
229+
230+
// Remove to get below max data size, then put again.
231+
getTestEntityBox().remove(testEntity1);
232+
getTestEntityBox().put(testEntity2);
233+
234+
// Alternatively, re-open with larger max data size.
235+
store.close();
236+
builder.maxDataSizeInKByte(2);
237+
store = builder.build();
238+
putTestEntity(LONG_STRING, 3);
239+
}
240+
170241
@Test
171242
public void validateOnOpen() {
172243
// Create a database first; we must create the model only once (ID/UID sequences would be different 2nd time)

0 commit comments

Comments
 (0)