Skip to content

Commit 5ef35b2

Browse files
committed
Merge branch 'dev' into sync
2 parents 64c5403 + 850cb92 commit 5ef35b2

File tree

10 files changed

+261
-53
lines changed

10 files changed

+261
-53
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
ObjectBox is a superfast object-oriented database with strong relation support.
77
ObjectBox is embedded into your Android, Linux, macOS, or Windows app.
88

9-
**Latest version: [2.7.0 (2020/07/30)](https://docs.objectbox.io/#objectbox-changelog)**
9+
**Latest version: [2.7.1 (2020/08/19)](https://docs.objectbox.io/#objectbox-changelog)**
1010

1111
Demo code using ObjectBox:
1212

@@ -33,7 +33,7 @@ Add this to your root build.gradle (project level):
3333

3434
```groovy
3535
buildscript {
36-
ext.objectboxVersion = '2.7.0'
36+
ext.objectboxVersion = '2.7.1'
3737
dependencies {
3838
classpath "io.objectbox:objectbox-gradle-plugin:$objectboxVersion"
3939
}

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
buildscript {
22
ext {
33
// Typically, only edit those two:
4-
def objectboxVersionNumber = '2.7.1' // without "-SNAPSHOT", e.g. '2.5.0' or '2.4.0-RC'
4+
def objectboxVersionNumber = '2.7.2' // without "-SNAPSHOT", e.g. '2.5.0' or '2.4.0-RC'
55
def objectboxVersionRelease = false // set to true for releasing to ignore versionPostFix to avoid e.g. "-dev" versions
66

77
// version post fix: '-<value>' or '' if not defined; e.g. used by CI to pass in branch name
@@ -21,7 +21,7 @@ buildscript {
2121

2222
junit_version = '4.13'
2323
mockito_version = '3.3.3'
24-
kotlin_version = '1.3.72'
24+
kotlin_version = '1.4.0'
2525
dokka_version = '0.10.1'
2626

2727
println "version=$ob_version"

objectbox-java/spotbugs-exclude.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<FindBugsFilter>
3+
<!-- Hint: to use regex, prefix with ~. https://spotbugs.readthedocs.io/en/stable/filter.html -->
4+
<!-- Exclude FlatBuffers generated code, compare with /scripts/flatc-idls.sh of core project. -->
35
<Match>
4-
<!-- Exclude FlatBuffers generated code. -->
5-
<Class name="~io\.objectbox\.model\.Model.*" />
6+
<Class name="io.objectbox.DebugFlags" />
7+
</Match>
8+
<Match>
9+
<Package name="io.objectbox.model" />
10+
</Match>
11+
<Match>
12+
<Class name="io.objectbox.query.OrderFlags" />
613
</Match>
714
</FindBugsFilter>

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

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ public class BoxStore implements Closeable {
6767
@Nullable private static Object relinker;
6868

6969
/** Change so ReLinker will update native library when using workaround loading. */
70-
public static final String JNI_VERSION = "2.7.0";
70+
public static final String JNI_VERSION = "2.7.1";
7171

72-
private static final String VERSION = "2.7.0-2020-07-30";
72+
private static final String VERSION = "2.7.2-2020-08-19";
7373
private static BoxStore defaultStore;
7474

7575
/** Currently used DB dirs with values from {@link #getCanonicalPath(File)}. */
@@ -253,47 +253,52 @@ public static boolean isSyncServerAvailable() {
253253
canonicalPath = getCanonicalPath(directory);
254254
verifyNotAlreadyOpen(canonicalPath);
255255

256-
handle = nativeCreateWithFlatOptions(builder.buildFlatStoreOptions(canonicalPath), builder.model);
257-
int debugFlags = builder.debugFlags;
258-
if (debugFlags != 0) {
259-
debugTxRead = (debugFlags & DebugFlags.LOG_TRANSACTIONS_READ) != 0;
260-
debugTxWrite = (debugFlags & DebugFlags.LOG_TRANSACTIONS_WRITE) != 0;
261-
} else {
262-
debugTxRead = debugTxWrite = false;
263-
}
264-
debugRelations = builder.debugRelations;
256+
try {
257+
handle = nativeCreateWithFlatOptions(builder.buildFlatStoreOptions(canonicalPath), builder.model);
258+
int debugFlags = builder.debugFlags;
259+
if (debugFlags != 0) {
260+
debugTxRead = (debugFlags & DebugFlags.LOG_TRANSACTIONS_READ) != 0;
261+
debugTxWrite = (debugFlags & DebugFlags.LOG_TRANSACTIONS_WRITE) != 0;
262+
} else {
263+
debugTxRead = debugTxWrite = false;
264+
}
265+
debugRelations = builder.debugRelations;
265266

266-
for (EntityInfo<?> entityInfo : builder.entityInfoList) {
267-
try {
268-
dbNameByClass.put(entityInfo.getEntityClass(), entityInfo.getDbName());
269-
int entityId = nativeRegisterEntityClass(handle, entityInfo.getDbName(), entityInfo.getEntityClass());
270-
entityTypeIdByClass.put(entityInfo.getEntityClass(), entityId);
271-
classByEntityTypeId.put(entityId, entityInfo.getEntityClass());
272-
propertiesByClass.put(entityInfo.getEntityClass(), entityInfo);
273-
for (Property<?> property : entityInfo.getAllProperties()) {
274-
if (property.customType != null) {
275-
if (property.converterClass == null) {
276-
throw new RuntimeException("No converter class for custom type of " + property);
267+
for (EntityInfo<?> entityInfo : builder.entityInfoList) {
268+
try {
269+
dbNameByClass.put(entityInfo.getEntityClass(), entityInfo.getDbName());
270+
int entityId = nativeRegisterEntityClass(handle, entityInfo.getDbName(), entityInfo.getEntityClass());
271+
entityTypeIdByClass.put(entityInfo.getEntityClass(), entityId);
272+
classByEntityTypeId.put(entityId, entityInfo.getEntityClass());
273+
propertiesByClass.put(entityInfo.getEntityClass(), entityInfo);
274+
for (Property<?> property : entityInfo.getAllProperties()) {
275+
if (property.customType != null) {
276+
if (property.converterClass == null) {
277+
throw new RuntimeException("No converter class for custom type of " + property);
278+
}
279+
nativeRegisterCustomType(handle, entityId, 0, property.dbName, property.converterClass,
280+
property.customType);
277281
}
278-
nativeRegisterCustomType(handle, entityId, 0, property.dbName, property.converterClass,
279-
property.customType);
280282
}
283+
} catch (RuntimeException e) {
284+
throw new RuntimeException("Could not setup up entity " + entityInfo.getEntityClass(), e);
281285
}
282-
} catch (RuntimeException e) {
283-
throw new RuntimeException("Could not setup up entity " + entityInfo.getEntityClass(), e);
284286
}
285-
}
286-
int size = classByEntityTypeId.size();
287-
allEntityTypeIds = new int[size];
288-
long[] entityIdsLong = classByEntityTypeId.keys();
289-
for (int i = 0; i < size; i++) {
290-
allEntityTypeIds[i] = (int) entityIdsLong[i];
291-
}
287+
int size = classByEntityTypeId.size();
288+
allEntityTypeIds = new int[size];
289+
long[] entityIdsLong = classByEntityTypeId.keys();
290+
for (int i = 0; i < size; i++) {
291+
allEntityTypeIds[i] = (int) entityIdsLong[i];
292+
}
292293

293-
objectClassPublisher = new ObjectClassPublisher(this);
294+
objectClassPublisher = new ObjectClassPublisher(this);
294295

295-
failedReadTxAttemptCallback = builder.failedReadTxAttemptCallback;
296-
queryAttempts = Math.max(builder.queryAttempts, 1);
296+
failedReadTxAttemptCallback = builder.failedReadTxAttemptCallback;
297+
queryAttempts = Math.max(builder.queryAttempts, 1);
298+
} catch (RuntimeException runtimeException) {
299+
close(); // Proper clean up, e.g. delete native handle, remove this path from openFiles
300+
throw runtimeException;
301+
}
297302
}
298303

299304
static String getCanonicalPath(File directory) {

objectbox-java/src/main/java/io/objectbox/exception/DbExceptionListener.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018 ObjectBox Ltd. All rights reserved.
2+
* Copyright 2018-2020 ObjectBox Ltd. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,11 +16,30 @@
1616

1717
package io.objectbox.exception;
1818

19+
import io.objectbox.annotation.apihint.Experimental;
20+
1921
/**
2022
* Listener for exceptions occurring during database operations.
2123
* Set via {@link io.objectbox.BoxStore#setDbExceptionListener(DbExceptionListener)}.
2224
*/
2325
public interface DbExceptionListener {
26+
/**
27+
* WARNING/DISCLAIMER: Please avoid this method and handle exceptions "properly" instead.
28+
* By using this method, you "hack" into the exception handling by preventing native core exceptions to be
29+
* raised in Java. This typically results in methods returning zero or null regardless if this breaks any
30+
* non-zero or non-null contract that would be in place otherwise. Additionally, "canceling" exceptions
31+
* may lead to unforeseen follow-up errors that would never occur otherwise. In short, by using this method
32+
* you are accepting undefined behavior.
33+
* <p>
34+
* Also note that it is likely that this method will never graduate from @{@link Experimental} until it is removed.
35+
* <p>
36+
* This method may be only called from {@link #onDbException(Exception)}.
37+
*/
38+
@Experimental
39+
static void cancelCurrentException() {
40+
DbExceptionListenerJni.nativeCancelCurrentException();
41+
}
42+
2443
/**
2544
* Called when an exception is thrown during a database operation.
2645
* Do NOT throw exceptions in this method: throw exceptions are ignored (but logged to stderr).
@@ -29,3 +48,12 @@ public interface DbExceptionListener {
2948
*/
3049
void onDbException(Exception e);
3150
}
51+
52+
/**
53+
* Interface cannot have native methods.
54+
*/
55+
class DbExceptionListenerJni {
56+
native static void nativeCancelCurrentException();
57+
58+
native static void nativeThrowException(long nativeStore, int exNo);
59+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2020 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+
package io.objectbox.exception;
17+
18+
/** Errors were detected in a file related to pages, e.g. illegal values or structural inconsistencies. */
19+
public class PagesCorruptException extends FileCorruptException {
20+
public PagesCorruptException(String message) {
21+
super(message);
22+
}
23+
24+
public PagesCorruptException(String message, int errorCode) {
25+
super(message, errorCode);
26+
}
27+
}

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

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package io.objectbox;
1818

19-
import io.objectbox.exception.FileCorruptException;
19+
import io.objectbox.exception.PagesCorruptException;
2020
import io.objectbox.model.ValidateOnOpenMode;
2121
import org.greenrobot.essentials.io.IoUtils;
2222
import org.junit.Before;
@@ -152,18 +152,13 @@ public void validateOnOpen() {
152152
}
153153

154154

155-
@Test(expected = FileCorruptException.class)
155+
@Test(expected = PagesCorruptException.class)
156156
public void validateOnOpenCorruptFile() throws IOException {
157157
File dir = prepareTempDir("object-store-test-corrupted");
158-
assertTrue(dir.mkdir());
159-
File badDataFile = new File(dir, "data.mdb");
160-
try (InputStream badIn = getClass().getResourceAsStream("corrupt-pageno-in-branch-data.mdb")) {
161-
try (FileOutputStream badOut = new FileOutputStream(badDataFile)) {
162-
IoUtils.copyAllBytes(badIn, badOut);
163-
}
164-
}
158+
File badDataFile = prepareBadDataFile(dir);
159+
165160
builder = BoxStoreBuilder.createDebugWithoutModel().directory(dir);
166-
builder.validateOnOpen(ValidateOnOpenMode.AllBranches);
161+
builder.validateOnOpen(ValidateOnOpenMode.Full);
167162
try {
168163
store = builder.build();
169164
} finally {
@@ -173,4 +168,50 @@ public void validateOnOpenCorruptFile() throws IOException {
173168
assertTrue(delOk); // Try to delete all before asserting
174169
}
175170
}
171+
172+
@Test
173+
public void usePreviousCommitWithCorruptFile() throws IOException {
174+
File dir = prepareTempDir("object-store-test-corrupted");
175+
prepareBadDataFile(dir);
176+
builder = BoxStoreBuilder.createDebugWithoutModel().directory(dir);
177+
builder.validateOnOpen(ValidateOnOpenMode.Full).usePreviousCommit();
178+
store = builder.build();
179+
String diagnoseString = store.diagnose();
180+
assertTrue(diagnoseString.contains("entries=2"));
181+
store.validate(0, true);
182+
store.close();
183+
assertTrue(store.deleteAllFiles());
184+
}
185+
186+
@Test
187+
public void usePreviousCommitAfterFileCorruptException() throws IOException {
188+
File dir = prepareTempDir("object-store-test-corrupted");
189+
prepareBadDataFile(dir);
190+
builder = BoxStoreBuilder.createDebugWithoutModel().directory(dir);
191+
builder.validateOnOpen(ValidateOnOpenMode.Full);
192+
try {
193+
store = builder.build();
194+
fail("Should have thrown");
195+
} catch (PagesCorruptException e) {
196+
builder.usePreviousCommit();
197+
store = builder.build();
198+
}
199+
200+
String diagnoseString = store.diagnose();
201+
assertTrue(diagnoseString.contains("entries=2"));
202+
store.validate(0, true);
203+
store.close();
204+
assertTrue(store.deleteAllFiles());
205+
}
206+
207+
private File prepareBadDataFile(File dir) throws IOException {
208+
assertTrue(dir.mkdir());
209+
File badDataFile = new File(dir, "data.mdb");
210+
try (InputStream badIn = getClass().getResourceAsStream("corrupt-pageno-in-branch-data.mdb")) {
211+
try (FileOutputStream badOut = new FileOutputStream(badDataFile)) {
212+
IoUtils.copyAllBytes(badIn, badOut);
213+
}
214+
}
215+
return badDataFile;
216+
}
176217
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,26 @@ public void testCommitReadTxException_exceptionListener() {
192192
}
193193
}
194194

195+
@Test(expected = IllegalStateException.class)
196+
public void testCancelExceptionOutsideDbExceptionListener() {
197+
DbExceptionListener.cancelCurrentException();
198+
}
199+
200+
@Test
201+
public void testCommitReadTxException_cancelException() {
202+
final Exception[] exs = {null};
203+
DbExceptionListener exceptionListener = e -> {
204+
if (exs[0] != null) throw new RuntimeException("Called more than once");
205+
exs[0] = e;
206+
DbExceptionListener.cancelCurrentException();
207+
};
208+
Transaction tx = store.beginReadTx();
209+
store.setDbExceptionListener(exceptionListener);
210+
tx.commit();
211+
tx.abort();
212+
assertNotNull(exs[0]);
213+
}
214+
195215
/*
196216
@Test
197217
public void testTransactionUsingAfterStoreClosed() {

0 commit comments

Comments
 (0)