Skip to content

Commit bbd388e

Browse files
Merge branch '31-map-error-codes-to-exceptions' into 'main'
Map common OBX_ERROR codes to exceptions Closes #31 See merge request objectbox/objectbox-dart!28
2 parents 5d00347 + 6a715be commit bbd388e

File tree

8 files changed

+162
-26
lines changed

8 files changed

+162
-26
lines changed

objectbox/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
* Allow `analyzer` with major version 5. #487
1111
* Generator not longer warns that it can not find the package source root if the output directory is
1212
the package root directory.
13+
* Add `StorageException` which is a `ObjectBoxException` with an `errorCode` (a `OBX_ERROR` code).
14+
* Throw `DbFullException` instead of `ObjectBoxException` with message `10101 Could not put` (error
15+
code `OBX_ERROR_DB_FULL`).
16+
* Change `Query.findUnique()` to throw `NonUniqueResultException` instead of
17+
`UniqueViolationException` if there is more than one result.
1318

1419
## 1.6.2 (2022-08-24)
1520

objectbox/lib/src/common.dart

+86-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import '../objectbox.dart';
2+
13
/// Wrapper for a semantic version information.
24
class Version {
35
/// Major version number.
@@ -28,13 +30,96 @@ class ObjectBoxException implements Exception {
2830
String toString() => 'ObjectBoxException: $message';
2931
}
3032

33+
/// Thrown if a property query aggregate function (e.g. `sum()`) can not compute
34+
/// a result due to a number type overflowing.
35+
class NumericOverflowException extends ObjectBoxException {
36+
/// See [NumericOverflowException].
37+
NumericOverflowException(String message) : super(message);
38+
}
39+
40+
/// ObjectBox database exception with an OBX_ERROR code.
41+
class StorageException extends ObjectBoxException {
42+
/// OBX_ERROR code as defined in [objectbox.h of the C library](https://github.com/objectbox/objectbox-c/blob/main/include/objectbox.h).
43+
final int errorCode;
44+
45+
/// Create with a message and OBX_ERROR code.
46+
StorageException(String message, this.errorCode) : super(message);
47+
48+
@override
49+
String toString() => 'StorageException: $message (OBX_ERROR code $errorCode)';
50+
}
51+
52+
/// Thrown when applying a transaction (e.g. putting an object) would exceed the
53+
/// `maxDBSizeInKB` configured when calling [Store.new].
54+
class DbFullException extends StorageException {
55+
/// See [DbFullException].
56+
DbFullException(String message, int errorCode) : super(message, errorCode);
57+
}
58+
59+
/// Thrown when the maximum amount of readers (read transactions) was exceeded.
60+
///
61+
/// Verify that your code only uses a reasonable amount of threads.
62+
///
63+
/// If a very high number of threads (>100) needs to be used, consider
64+
/// setting `maxReaders` when calling [Store.new].
65+
class DbMaxReadersExceededException extends StorageException {
66+
/// See [DbMaxReadersExceededException].
67+
DbMaxReadersExceededException(String message, int errorCode)
68+
: super(message, errorCode);
69+
}
70+
71+
/// Thrown when an error occurred that requires the store to be closed.
72+
///
73+
/// This may be an I/O error. Regular operations won't be possible. To handle
74+
/// this exit the app or try to reopen the store.
75+
class DbShutdownException extends StorageException {
76+
/// See [DbShutdownException].
77+
DbShutdownException(String message, int errorCode)
78+
: super(message, errorCode);
79+
}
80+
3181
/// A unique constraint would have been violated by this database operation.
3282
class UniqueViolationException extends ObjectBoxException {
3383
/// Create a new exception.
3484
UniqueViolationException(String message) : super(message);
3585
}
3686

37-
/// Flags to enable debug options when creating a [Store].
87+
/// Thrown when there is an error with the data schema (data model).
88+
///
89+
/// Typically, there is a conflict between the data model defined in your code
90+
/// (using `@Entity` classes) and the data model of the existing database file.
91+
///
92+
/// Read the [meta model docs](https://docs.objectbox.io/advanced/meta-model-ids-and-uids#resolving-meta-model-conflicts)
93+
/// on why this can happen and how to resolve such conflicts.
94+
class SchemaException extends ObjectBoxException {
95+
/// See [SchemaException].
96+
SchemaException(String message) : super(message);
97+
}
98+
99+
/// Errors were detected in a database file, e.g. illegal values or structural
100+
/// inconsistencies.
101+
class DbFileCorruptException extends StorageException {
102+
/// See [DbFileCorruptException].
103+
DbFileCorruptException(String message, int errorCode)
104+
: super(message, errorCode);
105+
}
106+
107+
/// Errors related to pages were detected in a database file, e.g. bad page refs
108+
/// outside of the file.
109+
class DbPagesCorruptException extends DbFileCorruptException {
110+
/// See [DbPagesCorruptException].
111+
DbPagesCorruptException(String message, int errorCode)
112+
: super(message, errorCode);
113+
}
114+
115+
/// Thrown if `Query.findUnique()` is called, but the query matches more than
116+
/// one object.
117+
class NonUniqueResultException extends ObjectBoxException {
118+
/// See [NonUniqueResultException].
119+
NonUniqueResultException(String message) : super(message);
120+
}
121+
122+
/// Passed as `debugFlags` when calling [Store.new] to enable debug options.
38123
class DebugFlags {
39124
/// Log read transactions.
40125
static const int logTransactionsRead = 1;

objectbox/lib/src/native/bindings/helpers.dart

+27-11
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,41 @@ class ObjectBoxNativeError {
5757

5858
ObjectBoxNativeError(this.code, this.message, this.context);
5959

60-
String get fullMessage =>
61-
context == null ? '$code $message' : '$context: $code $message';
60+
String get messageWithContext =>
61+
context == null ? message : '$context: $message';
62+
63+
String get messageWithErrorCode => code == 0
64+
? messageWithContext
65+
: '$messageWithContext (OBX_ERROR code $code)';
6266

6367
Never throwMapped() {
6468
switch (code) {
6569
case OBX_ERROR_ILLEGAL_STATE:
66-
throw StateError(fullMessage);
70+
throw StateError(messageWithErrorCode);
6771
case OBX_ERROR_ILLEGAL_ARGUMENT:
68-
case OBX_ERROR_STD_ILLEGAL_ARGUMENT:
69-
throw ArgumentError(fullMessage);
72+
throw ArgumentError(messageWithErrorCode);
7073
case OBX_ERROR_NUMERIC_OVERFLOW:
71-
case OBX_ERROR_STD_OUT_OF_RANGE:
72-
case OBX_ERROR_STD_RANGE:
73-
case OBX_ERROR_STD_OVERFLOW:
74-
throw RangeError(fullMessage);
74+
throw NumericOverflowException(messageWithContext);
75+
case OBX_ERROR_DB_FULL:
76+
throw DbFullException(messageWithContext, code);
77+
case OBX_ERROR_MAX_READERS_EXCEEDED:
78+
throw DbMaxReadersExceededException(messageWithContext, code);
79+
case OBX_ERROR_STORE_MUST_SHUTDOWN:
80+
throw DbShutdownException(messageWithContext, code);
7581
case OBX_ERROR_UNIQUE_VIOLATED:
76-
throw UniqueViolationException(fullMessage);
82+
throw UniqueViolationException(messageWithContext);
83+
case OBX_ERROR_SCHEMA:
84+
throw SchemaException(messageWithContext);
85+
case OBX_ERROR_FILE_CORRUPT:
86+
throw DbFileCorruptException(messageWithContext, code);
87+
case OBX_ERROR_FILE_PAGES_CORRUPT:
88+
throw DbPagesCorruptException(messageWithContext, code);
7789
default:
78-
throw ObjectBoxException(fullMessage);
90+
if (code == 0) {
91+
throw ObjectBoxException(messageWithContext);
92+
} else {
93+
throw StorageException(messageWithContext, code);
94+
}
7995
}
8096
}
8197
}

objectbox/lib/src/native/query/query.dart

+3-2
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,8 @@ class Query<T> {
775775
}
776776

777777
/// Finds the only object matching the query. Returns null if there are no
778-
/// results or throws if there are multiple objects matching.
778+
/// results or throws [NonUniqueResultException] if there are multiple objects
779+
/// matching.
779780
///
780781
/// Note: [offset] and [limit] are respected, if set. Because [limit] affects
781782
/// the number of matched objects, make sure you leave it at zero or set it
@@ -794,7 +795,7 @@ class Query<T> {
794795
return false;
795796
}
796797
} else {
797-
error = UniqueViolationException(
798+
error = NonUniqueResultException(
798799
'Query findUnique() matched more than one object');
799800
return false;
800801
}

objectbox/lib/src/native/store.dart

+12-9
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,16 @@ class Store {
9898
/// ## Maximum database size
9999
///
100100
/// [maxDBSizeInKB] sets the maximum size the database file can grow to.
101-
/// By default this is 1 GB, which should be sufficient for most applications.
102-
/// The store will throw when trying to insert more data if the maximum size
103-
/// is reached.
101+
/// When applying a transaction (e.g. putting an object) would exceed it a
102+
/// [DbFullException] is thrown.
104103
///
104+
/// By default, this is 1 GB, which should be sufficient for most applications.
105105
/// In general, a maximum size prevents the database from growing indefinitely
106106
/// when something goes wrong (for example data is put in an infinite loop).
107107
///
108+
/// This value can be changed, so increased or also decreased, each time when
109+
/// opening a store.
110+
///
108111
/// ## File mode
109112
///
110113
/// Specify [unix-style file permissions](https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation)
@@ -116,14 +119,14 @@ class Store {
116119
/// ## Maximum number of readers
117120
///
118121
/// [maxReaders] sets the maximum number of concurrent readers. For most
119-
/// applications, the default is fine (~ 126 readers).
122+
/// applications, the default is fine (about 126 readers).
120123
///
121-
/// A "reader" is short for a thread involved in a read transaction.
124+
/// A "reader" is short for a thread involved in a read transaction. If the
125+
/// maximum is exceeded the store throws [DbMaxReadersExceededException]. In
126+
/// this case check that your code only uses a reasonable amount of threads.
122127
///
123-
/// If the store throws OBX_ERROR_MAX_READERS_EXCEEDED, you should first worry
124-
/// about the amount of threads your code is using.
125-
/// For highly concurrent setups (e.g. using ObjectBox on the server side) it
126-
/// may make sense to increase the number.
128+
/// For highly concurrent setups (e.g. you are using ObjectBox on the server
129+
/// side) it may make sense to increase the number.
127130
///
128131
/// Note: Each thread that performed a read transaction and is still alive
129132
/// holds on to a reader slot. These slots only get vacated when the thread

objectbox/test/box_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ void main() {
660660
TxMode.write, () => box.putMany(param));
661661
}, simpleItems()),
662662
throwsA(predicate((StateError e) => e.toString().contains(
663-
'Bad state: failed to create transaction: 10001 Cannot start a write transaction inside a read only transaction'))));
663+
'Bad state: failed to create transaction: Cannot start a write transaction inside a read only transaction (OBX_ERROR code 10001)'))));
664664
}, skip: notAtLeastDart2_15_0());
665665

666666
test('failing in recursive txn', () {

objectbox/test/query_test.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,8 @@ void main() {
365365

366366
expect(
367367
() => query.findUnique(),
368-
throwsA(predicate((UniqueViolationException e) =>
369-
e.toString().contains('more than one'))));
368+
throwsA(predicate((NonUniqueResultException e) =>
369+
e.message == 'Query findUnique() matched more than one object')));
370370

371371
query.param(TestEntity_.tString).value = 't2';
372372
expect(query.findUnique()!.tString, 't2');

objectbox/test/store_test.dart

+26
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,32 @@ void main() {
234234
Directory('store').deleteSync(recursive: true);
235235
});
236236

237+
test('store maxDBSizeInKB', () {
238+
final testDir = Directory('db-size-test');
239+
if (testDir.existsSync()) testDir.deleteSync(recursive: true);
240+
241+
// Empty file is around 24 KB, object below adds about 12 KB each.
242+
var store =
243+
Store(getObjectBoxModel(), directory: testDir.path, maxDBSizeInKB: 40);
244+
var box = store.box<TestEntity>();
245+
box.put(TestEntity.filled(id: 0));
246+
247+
final testEntity2 = TestEntity.filled(id: 0);
248+
expect(
249+
() => box.put(testEntity2),
250+
throwsA(predicate((e) =>
251+
e is DbFullException &&
252+
e.errorCode == OBX_ERROR_DB_FULL &&
253+
e.message == 'object put failed: Could not put')));
254+
255+
// Re-open with larger size.
256+
store.close();
257+
store =
258+
Store(getObjectBoxModel(), directory: testDir.path, maxDBSizeInKB: 60);
259+
testEntity2.id = 0; // Clear ID of object that failed to put.
260+
store.box<TestEntity>().put(testEntity2);
261+
});
262+
237263
test('store open in unicode symbol path', () async {
238264
final parentDir = Directory('unicode-test');
239265
await parentDir.create();

0 commit comments

Comments
 (0)