-
Notifications
You must be signed in to change notification settings - Fork 37
/
objectbox.hpp
3625 lines (2992 loc) · 162 KB
/
objectbox.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2018-2023 ObjectBox Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Usage note: Put "#define OBX_CPP_FILE" before including this file in (exactly) one of your .cpp/.cc files.
#pragma once
#include <algorithm>
#include <atomic>
#include <cstring>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "objectbox.h"
#ifndef OBX_DISABLE_FLATBUFFERS // FlatBuffers is required to put data; you can disable it until have the include file.
#include "flatbuffers/flatbuffers.h"
#endif
#ifdef __cpp_lib_optional
#include <optional>
#endif
static_assert(OBX_VERSION_MAJOR == 4 && OBX_VERSION_MINOR == 0 && OBX_VERSION_PATCH == 3, // NOLINT
"Versions of objectbox.h and objectbox.hpp files do not match, please update");
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas" // So we can have the next pragma with strict compiler settings
#pragma clang diagnostic ignored "-Wunused-function" // It's an API, so it's normal to use parts only
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection" // It's an API, so it's normal to use parts only
#pragma ide diagnostic ignored "readability-else-after-return" // They way it's used here improves readability
#endif
namespace obx {
/**
* @defgroup cpp ObjectBox C++ API
* @{
*/
/// \brief Base class for ObjectBox related exceptions.
/// Note that there are currently 3 main sub types:
/// IllegalArgumentException, IllegalStateException, and, for all other error types, DbException.
class Exception : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
/// The error code as defined in objectbox.h via the OBX_ERROR_* constants
virtual int code() const = 0;
};
/// Thrown when the passed arguments are illegal
class IllegalArgumentException : public Exception {
public:
using Exception::Exception;
/// Always OBX_ERROR_ILLEGAL_ARGUMENT
int code() const override { return OBX_ERROR_ILLEGAL_ARGUMENT; }
};
/// Thrown when a request does not make sense in the current state. For example, doing actions on a closed object.
class IllegalStateException : public Exception {
public:
using Exception::Exception;
/// Always OBX_ERROR_ILLEGAL_STATE
int code() const override { return OBX_ERROR_ILLEGAL_STATE; }
};
/// Thrown when a transaction is about to commit but it would exceed the user-defined data size limit.
/// See obx_opt_max_data_size_in_kb() for details.
class MaxDataSizeExceededException : public Exception {
public:
using Exception::Exception;
/// Always OBX_ERROR_MAX_DATA_SIZE_EXCEEDED
int code() const override { return OBX_ERROR_MAX_DATA_SIZE_EXCEEDED; }
};
/// The operation on a resource (typically a Store) failed because the resources is in process of being shut down or
/// already has shutdown. For example, calling methods on the Store will throw this exception after Store::close().
class ShuttingDownException : public IllegalStateException {
public:
using IllegalStateException::IllegalStateException;
/// Always OBX_ERROR_SHUTTING_DOWN
int code() const override { return OBX_ERROR_SHUTTING_DOWN; }
};
#define OBX_VERIFY_ARGUMENT(c) \
((c) ? (void) (0) : obx::internal::throwIllegalArgumentException("Argument validation failed: ", #c))
#define OBX_VERIFY_STATE(c) \
((c) ? (void) (0) : obx::internal::throwIllegalStateException("State condition failed: ", #c))
/// Database related exception, containing a error code to differentiate between various errors.
/// Note: what() typically contains a specific text about the error condition (sometimes helpful to resolve the issue).
class DbException : public Exception {
const int code_;
public:
explicit DbException(const std::string& text, int code = 0) : Exception(text), code_(code) {}
explicit DbException(const char* text, int code = 0) : Exception(text), code_(code) {}
/// The error code as defined in objectbox.h via the OBX_ERROR_* constants
int code() const override { return code_; }
};
/// A functionality was invoked that is not part in this edition of ObjectBox.
class FeatureNotAvailableException : public Exception {
public:
using Exception::Exception;
/// Always OBX_ERROR_FEATURE_NOT_AVAILABLE
int code() const override { return OBX_ERROR_FEATURE_NOT_AVAILABLE; }
};
namespace internal {
[[noreturn]] void throwIllegalArgumentException(const char* text1, const char* text2);
[[noreturn]] void throwIllegalStateException(const char* text1, const char* text2);
/// @throws Exception using the given error (defaults to obx_last_error_code())
[[noreturn]] void throwLastError(obx_err err = obx_last_error_code(), const char* contextPrefix = nullptr);
void appendLastErrorText(obx_err err, std::string& outMessage);
/// @throws Exception or subclass depending on the given err, with the given message
[[noreturn]] void throwError(obx_err err, const std::string& message);
void checkErrOrThrow(obx_err err);
bool checkSuccessOrThrow(obx_err err);
void checkPtrOrThrow(const void* ptr, const char* contextPrefix = nullptr);
void checkIdOrThrow(uint64_t id, const char* contextPrefix = nullptr);
/// "Pass-through" variant of checkPtrOrThrow() - prefer the latter if this is not required (less templating).
template <typename T>
T* checkedPtrOrThrow(T* ptr, const char* contextPrefix = nullptr) {
internal::checkPtrOrThrow(ptr, contextPrefix);
return ptr;
}
/// Dereferences the given pointer, which must be non-null.
/// @throws IllegalStateException if ptr is nullptr
template <typename EX = IllegalStateException, typename T>
T& toRef(T* ptr, const char* message = "Can not dereference a null pointer") {
if (ptr == nullptr) throw EX(message);
return *ptr;
}
#ifdef OBX_CPP_FILE
[[noreturn]] void throwIllegalArgumentException(const char* text1, const char* text2) {
std::string msg(text1);
if (text2) msg.append(text2);
throw IllegalArgumentException(msg);
}
[[noreturn]] void throwIllegalStateException(const char* text1, const char* text2) {
std::string msg(text1);
if (text2) msg.append(text2);
throw IllegalStateException(msg);
}
[[noreturn]] void throwLastError(obx_err err, const char* contextPrefix) {
std::string msg;
if (contextPrefix) msg.append(contextPrefix).append(": ");
if (err == OBX_SUCCESS) { // Zero, there's no error actually: this is atypical corner case, which should be avoided
msg += "No error occurred (operation was successful)";
throw IllegalStateException(msg);
} else {
appendLastErrorText(err, msg);
throwError(err, msg);
}
}
void appendLastErrorText(obx_err err, std::string& outMessage) {
obx_err lastErr = obx_last_error_code();
if (err == lastErr) {
assert(lastErr != 0); // checked indirectly against err before
outMessage += obx_last_error_message();
} else { // Do not use obx_last_error_message() as primary msg because it originated from another code
outMessage.append("Error code ").append(std::to_string(err));
if (lastErr != 0) {
outMessage.append(" (last: ").append(std::to_string(lastErr));
outMessage.append(", last msg: ").append(obx_last_error_message()).append(")");
}
}
}
[[noreturn]] void throwError(obx_err err, const std::string& message) {
if (err == OBX_SUCCESS) { // Zero, there's no error actually: this is atypical corner case, which should be avoided
throw IllegalStateException("No error occurred; operation was successful. Given message: " + message);
} else {
if (err == OBX_ERROR_ILLEGAL_ARGUMENT) {
throw IllegalArgumentException(message);
} else if (err == OBX_ERROR_ILLEGAL_STATE) {
throw IllegalStateException(message);
} else if (err == OBX_ERROR_SHUTTING_DOWN) {
throw ShuttingDownException(message);
} else if (err == OBX_ERROR_MAX_DATA_SIZE_EXCEEDED) {
throw MaxDataSizeExceededException(message);
} else if (err == OBX_ERROR_FEATURE_NOT_AVAILABLE) {
throw FeatureNotAvailableException(message);
} else {
throw DbException(message, err);
}
}
}
void checkErrOrThrow(obx_err err) {
if (err != OBX_SUCCESS) throwLastError(err);
}
bool checkSuccessOrThrow(obx_err err) {
if (err == OBX_NO_SUCCESS) return false;
if (err == OBX_SUCCESS) return true;
throwLastError(err);
}
void checkPtrOrThrow(const void* ptr, const char* contextPrefix) {
if (ptr == nullptr) throwLastError(obx_last_error_code(), contextPrefix);
}
void checkIdOrThrow(uint64_t id, const char* contextPrefix) {
if (id == 0) throwLastError(obx_last_error_code(), contextPrefix);
}
#endif
} // namespace internal
/// Bytes, which must be resolved "lazily" via get() and released via this object (destructor).
/// Unlike void* style bytes, this may represent allocated resources and/or bytes that are only produced on demand.
class BytesLazy {
OBX_bytes_lazy* cPtr_;
public:
BytesLazy() : cPtr_(nullptr) {}
explicit BytesLazy(OBX_bytes_lazy* cBytes) : cPtr_(cBytes) {}
BytesLazy(BytesLazy&& src) noexcept : cPtr_(src.cPtr_) { src.cPtr_ = nullptr; }
/// No copying allowed: OBX_bytes_lazy needs a single owner (no method to "clone" it).
BytesLazy(const BytesLazy& src) = delete;
~BytesLazy() { clear(); }
/// @returns true if it holds actual bytes resources (e.g. not default-constructed and not clear()ed yet).
bool hasBytes() { return cPtr_ != nullptr; }
/// @returns true if it does not hold any bytes resources (e.g. default-constructed or already clear()ed).
bool isNull() { return cPtr_ == nullptr; }
void swap(BytesLazy& other) { std::swap(cPtr_, other.cPtr_); }
/// Clears any bytes resources
void clear() {
obx_bytes_lazy_free(cPtr_);
cPtr_ = nullptr;
}
/// Gets the bytes and its size using the given "out" references.
void get(const void*& outBytes, size_t& outSize) const {
if (cPtr_ == nullptr) throw IllegalStateException("This instance does not hold any bytes resources");
internal::checkErrOrThrow(obx_bytes_lazy_get(cPtr_, &outBytes, &outSize));
}
/// Note that this will potentially resolve actual bytes just like get().
/// Also, it would be more efficient to only call get() to get everything in a single call.
size_t size() {
if (cPtr_ == nullptr) throw IllegalStateException("This instance does not hold any bytes resources");
size_t size = 0;
internal::checkErrOrThrow(obx_bytes_lazy_get(cPtr_, nullptr, &size));
return size;
}
};
namespace {
template <typename EntityT>
constexpr obx_schema_id entityId() {
return EntityT::_OBX_MetaInfo::entityId();
}
} // namespace
template <class T>
class Box;
class BoxTypeless;
template <class T>
class AsyncBox;
class Transaction;
class Sync;
class SyncClient;
class SyncServer;
class Closable {
public:
virtual ~Closable() = default;
virtual bool isClosed() = 0;
virtual void close() = 0;
};
using ObxLogCallback = std::function<void(OBXLogLevel logLevel, const char* text, size_t textSize)>;
/// Options provide a way to configure Store when opening it.
/// Options functions can be chained, e.g. options.directory("mypath/objectbox").maxDbSizeInKb(2048);
/// Note: Options objects can be used only once to create a store as they are "consumed" during Store creation.
/// Thus, you need to create a new Option object for each Store that is created.
class Options {
friend class Store;
friend class SyncServer;
mutable OBX_store_options* opt = nullptr;
OBX_store_options* release() {
OBX_store_options* result = opt;
opt = nullptr;
return result;
}
public:
Options() {
opt = obx_opt();
internal::checkPtrOrThrow(opt, "Could not create store options");
}
/// @deprecated is this used by generator?
explicit Options(OBX_model* model) : Options() { this->model(model); }
~Options() { obx_opt_free(opt); }
/// Set the model on the options. The default is no model.
/// NOTE: the model is always freed by this function, including when an error occurs.
Options& model(OBX_model* model) {
internal::checkErrOrThrow(obx_opt_model(opt, model));
return *this;
}
/// Set the store directory on the options. The default is "objectbox".
/// Use the prefix "memory:" to open an in-memory database, e.g. "memory:myApp" (see docs for details).
Options& directory(const char* dir) {
internal::checkErrOrThrow(obx_opt_directory(opt, dir));
return *this;
}
/// Set the store directory on the options. The default is "objectbox".
/// Use the prefix "memory:" to open an in-memory database, e.g. "memory:myApp" (see docs for details).
Options& directory(const std::string& dir) { return directory(dir.c_str()); }
/// Gets the option for "directory"; this is either the default, or, the value set by directory().
std::string getDirectory() const {
const char* dir = obx_opt_get_directory(opt);
internal::checkPtrOrThrow(dir, "Could not get directory");
return dir;
}
/// Set the maximum db size on the options. The default is 1Gb.
Options& maxDbSizeInKb(uint64_t sizeInKb) {
obx_opt_max_db_size_in_kb(opt, sizeInKb);
return *this;
}
/// Gets the option for "max DB size"; this is either the default, or, the value set by maxDbSizeInKb().
uint64_t getMaxDbSizeInKb() const { return obx_opt_get_max_db_size_in_kb(opt); }
/// Data size tracking is more involved than DB size tracking, e.g. it stores an internal counter.
/// Thus only use it if a stricter, more accurate limit is required (it's off by default).
/// It tracks the size of actual data bytes of objects (system and metadata is not considered).
/// On the upside, reaching the data limit still allows data to be removed (assuming DB limit is not reached).
/// Max data and DB sizes can be combined; data size must be below the DB size.
Options& maxDataSizeInKb(uint64_t sizeInKb) {
obx_opt_max_data_size_in_kb(opt, sizeInKb);
return *this;
}
/// Gets the option for "max DB size"; this is either the default, or, the value set by maxDataSizeInKb().
uint64_t getMaxDataSizeInKb() const { return obx_opt_get_max_data_size_in_kb(opt); }
/// Set the file mode on the options. The default is 0644 (unix-style)
Options& fileMode(unsigned int fileMode) {
obx_opt_file_mode(opt, fileMode);
return *this;
}
/// Set the maximum number of readers (related to read transactions.
/// "Readers" are an finite resource for which we need to define a maximum number upfront.
/// The default value is enough for most apps and usually you can ignore it completely.
/// However, if you get the OBX_ERROR_MAX_READERS_EXCEEDED error, you should verify your threading.
/// For each thread, ObjectBox uses multiple readers.
/// Their number (per thread) depends on number of types, relations, and usage patterns.
/// Thus, if you are working with many threads (e.g. in a server-like scenario), it can make sense to increase
/// the maximum number of readers.
///
/// \note The internal default is currently 126. So when hitting this limit, try values around 200-500.
///
/// \attention Each thread that performed a read transaction and is still alive holds on to a reader slot.
/// These slots only get vacated when the thread ends. Thus be mindful with the number of active threads.
/// Alternatively, you can opt to try the experimental noReaderThreadLocals option flag.
Options& maxReaders(unsigned int maxReaders) {
obx_opt_max_readers(opt, maxReaders);
return *this;
}
/// Disables the usage of thread locals for "readers" related to read transactions.
/// This can make sense if you are using a lot of threads that are kept alive.
/// \note This is still experimental, as it comes with subtle behavior changes at a low level and may affect
/// corner cases with e.g. transactions, which may not be fully tested at the moment.
Options& noReaderThreadLocals(bool flag) {
obx_opt_no_reader_thread_locals(opt, flag);
return *this;
}
/// Set the model on the options copying the given bytes. The default is no model.
Options& modelBytes(const void* bytes, size_t size) {
internal::checkErrOrThrow(obx_opt_model_bytes(opt, bytes, size));
return *this;
}
/// Like modelBytes() BUT WITHOUT copying the given bytes.
/// Thus, you must keep the bytes available until after the store is created.
Options& modelBytesDirect(const void* bytes, size_t size) {
internal::checkErrOrThrow(obx_opt_model_bytes_direct(opt, bytes, size));
return *this;
}
/// When the DB is opened initially, ObjectBox can do a consistency check on the given amount of pages.
/// Reliable file systems already guarantee consistency, so this is primarily meant to deal with unreliable
/// OSes, file systems, or hardware. Thus, usually a low number (e.g. 1-20) is sufficient and does not impact
/// startup performance significantly. To completely disable this you can pass 0, but we recommend a setting of
/// at least 1.
/// Note: ObjectBox builds upon ACID storage, which guarantees consistency given that the file system is working
/// correctly (in particular fsync).
/// @param pageLimit limits the number of checked pages (currently defaults to 0, but will be increased in the
/// future)
/// @param flags flags used to influence how the validation checks are performed
Options& validateOnOpenPages(size_t pageLimit, uint32_t flags = OBXValidateOnOpenPagesFlags_None) {
obx_opt_validate_on_open_pages(opt, pageLimit, flags);
return *this;
}
/// When the DB is opened initially, ObjectBox can do a validation over the key/value pairs to check, for example,
/// whether they're consistent towards our internal specification.
/// @param flags flags used to influence how the validation checks are performed;
/// only OBXValidateOnOpenKvFlags_None is supported for now.
Options& validateOnOpenKv(uint32_t flags = OBXValidateOnOpenKvFlags_None) {
obx_opt_validate_on_open_kv(opt, flags);
return *this;
}
/// Don't touch unless you know exactly what you are doing:
/// Advanced setting typically meant for language bindings (not end users). See OBXPutPaddingMode description.
Options& putPaddingMode(OBXPutPaddingMode mode) {
obx_opt_put_padding_mode(opt, mode);
return *this;
}
/// Advanced setting meant only for special scenarios: setting to false causes opening the database in a
/// limited, schema-less mode. If you don't know what this means exactly: ignore this flag. Defaults to true.
Options& readSchema(bool value) {
obx_opt_read_schema(opt, value);
return *this;
}
/// Advanced setting recommended to be used together with read-only mode to ensure no data is lost.
/// Ignores the latest data snapshot (committed transaction state) and uses the previous snapshot instead.
/// When used with care (e.g. backup the DB files first), this option may also recover data removed by the
/// latest transaction. Defaults to false.
Options& usePreviousCommit(bool value) {
obx_opt_use_previous_commit(opt, value);
return *this;
}
/// Open store in read-only mode: no schema update, no write transactions. Defaults to false.
Options& readOnly(bool value) {
obx_opt_read_only(opt, value);
return *this;
}
/// Configure debug flags; e.g. to influence logging. Defaults to NONE.
Options& debugFlags(uint32_t flags) {
obx_opt_debug_flags(opt, flags);
return *this;
}
/// Adds debug flags to potentially existing ones (that were previously set).
Options& addDebugFlags(uint32_t flags) {
obx_opt_add_debug_flags(opt, flags);
return *this;
}
/// Gets the option for "debug flags"; this is either the default, or, the value set by debugFlags().
uint32_t getDebugFlags() const { return obx_opt_get_debug_flags(opt); }
/// Maximum of async elements in the queue before new elements will be rejected.
/// Hitting this limit usually hints that async processing cannot keep up;
/// data is produced at a faster rate than it can be persisted in the background.
/// In that case, increasing this value is not the only alternative; other values might also optimize
/// throughput. For example, increasing maxInTxDurationMicros may help too.
Options& asyncMaxQueueLength(size_t value) {
obx_opt_async_max_queue_length(opt, value);
return *this;
}
/// Producers (AsyncTx submitter) is throttled when the queue size hits this
Options& asyncThrottleAtQueueLength(size_t value) {
obx_opt_async_throttle_at_queue_length(opt, value);
return *this;
}
/// Sleeping time for throttled producers on each submission
Options& asyncThrottleMicros(uint32_t value) {
obx_opt_async_throttle_micros(opt, value);
return *this;
}
/// Maximum duration spent in a transaction before AsyncQ enforces a commit.
/// This becomes relevant if the queue is constantly populated at a high rate.
Options& asyncMaxInTxDuration(uint32_t micros) {
obx_opt_async_max_in_tx_duration(opt, micros);
return *this;
}
/// Maximum operations performed in a transaction before AsyncQ enforces a commit.
/// This becomes relevant if the queue is constantly populated at a high rate.
Options& asyncMaxInTxOperations(uint32_t value) {
obx_opt_async_max_in_tx_operations(opt, value);
return *this;
}
/// Before the AsyncQ is triggered by a new element in queue to starts a new run, it delays actually starting
/// the transaction by this value. This gives a newly starting producer some time to produce more than one a
/// single operation before AsyncQ starts. Note: this value should typically be low to keep latency low and
/// prevent accumulating too much operations.
Options& asyncPreTxnDelay(uint32_t delayMicros) {
obx_opt_async_pre_txn_delay(opt, delayMicros);
return *this;
}
/// Before the AsyncQ is triggered by a new element in queue to starts a new run, it delays actually starting
/// the transaction by this value. This gives a newly starting producer some time to produce more than one a
/// single operation before AsyncQ starts. Note: this value should typically be low to keep latency low and
/// prevent accumulating too much operations.
Options& asyncPreTxnDelay(uint32_t delayMicros, uint32_t delay2Micros, size_t minQueueLengthForDelay2) {
obx_opt_async_pre_txn_delay4(opt, delayMicros, delay2Micros, minQueueLengthForDelay2);
return *this;
}
/// Similar to preTxDelay but after a transaction was committed.
/// One of the purposes is to give other transactions some time to execute.
/// In combination with preTxDelay this can prolong non-TX batching time if only a few operations are around.
Options& asyncPostTxnDelay(uint32_t delayMicros) {
obx_opt_async_post_txn_delay(opt, delayMicros);
return *this;
}
/// Similar to preTxDelay but after a transaction was committed.
/// One of the purposes is to give other transactions some time to execute.
/// In combination with preTxDelay this can prolong non-TX batching time if only a few operations are around.
/// @param subtractProcessingTime If set, the delayMicros is interpreted from the start of TX processing.
/// In other words, the actual delay is delayMicros minus the TX processing time including the commit.
/// This can make timings more accurate (e.g. when fixed batching interval are given).
Options& asyncPostTxnDelay(uint32_t delayMicros, uint32_t delay2Micros, size_t minQueueLengthForDelay2,
bool subtractProcessingTime = false) {
obx_opt_async_post_txn_delay5(opt, delayMicros, delay2Micros, minQueueLengthForDelay2, subtractProcessingTime);
return *this;
}
/// Numbers of operations below this value are considered "minor refills"
Options& asyncMinorRefillThreshold(size_t queueLength) {
obx_opt_async_minor_refill_threshold(opt, queueLength);
return *this;
}
/// If non-zero, this allows "minor refills" with small batches that came in (off by default).
Options& asyncMinorRefillMaxCount(uint32_t value) {
obx_opt_async_minor_refill_max_count(opt, value);
return *this;
}
/// Default value: 10000, set to 0 to deactivate pooling
Options& asyncMaxTxPoolSize(size_t value) {
obx_opt_async_max_tx_pool_size(opt, value);
return *this;
}
/// Total cache size; default: ~ 0.5 MB
Options& asyncObjectBytesMaxCacheSize(uint64_t value) {
obx_opt_async_object_bytes_max_cache_size(opt, value);
return *this;
}
/// Maximal size for an object to be cached (only cache smaller ones)
Options& asyncObjectBytesMaxSizeToCache(uint64_t value) {
obx_opt_async_object_bytes_max_size_to_cache(opt, value);
return *this;
}
/// Registers a log callback, which is called for a selection of log events.
/// Note: this does not replace the default logging, which is much more extensive (at least at this point).
Options& logCallback(obx_log_callback* callback, void* userData) {
obx_opt_log_callback(opt, callback, userData);
return *this;
}
/// Before opening the database, this options instructs to restore the database content from the given backup file.
/// Note: backup is a server-only feature.
/// By default, actually restoring the backup is only performed if no database already exists
/// (database does not contain data).
/// @param flags For default behavior pass 0, or adjust defaults using OBXBackupRestoreFlags bit flags,
/// e.g., to overwrite all existing data in the database.
Options& backupRestore(const char* backupFile, uint32_t flags = 0) {
obx_opt_backup_restore(opt, backupFile, flags);
return *this;
}
/// Enables Write-ahead logging (WAL); for now this is only supported for in-memory DBs.
/// @param flags WAL itself is enabled by setting flag OBXWalFlags_EnableWal (also the default parameter value).
/// Combine with other flags using bitwise OR or switch off WAL by passing 0.
Options& wal(uint32_t flags = OBXWalFlags_EnableWal) {
obx_opt_wal(opt, flags);
return *this;
}
/// The WAL file gets consolidated when it reached this size limit when opening the database.
/// This setting is meant for applications that prefer to consolidate on startup,
/// which may avoid consolidations on commits while the application is running.
/// The default is 4096 (4 MB).
Options& walMaxFileSizeOnOpenInKb(uint64_t size_in_kb) {
obx_opt_wal_max_file_size_on_open_in_kb(opt, size_in_kb);
return *this;
}
/// The WAL file gets consolidated when it reaches this size limit after a commit.
/// As consolidation takes some time, it is a trade-off between accumulating enough data
/// and the time the consolidation takes (longer with more data).
/// The default is 16384 (16 MB).
Options& walMaxFileSizeInKb(uint64_t size_in_kb) {
obx_opt_wal_max_file_size_in_kb(opt, size_in_kb);
return *this;
}
};
/// Transactions can be started in read (only) or write mode.
enum class TxMode { READ, WRITE };
/// \brief A ObjectBox store represents a database storing data in a given directory on a local file system.
///
/// Once opened using one of the constructors, Store is an entry point to data access APIs such as Box, Query, and
/// Transaction.
///
/// It's possible open multiple stores in different directories, e.g. at the same time.
class Store {
std::atomic<OBX_store*> cStore_;
const bool owned_; ///< whether the store pointer is owned (true except for SyncServer::store())
std::shared_ptr<Closable> syncClient_;
std::mutex syncClientMutex_;
friend Sync;
friend SyncClient;
friend SyncServer;
explicit Store(OBX_store* ptr, bool owned) : cStore_(ptr), owned_(owned) {
OBX_VERIFY_ARGUMENT(cStore_ != nullptr);
}
public:
/// Return the (runtime) version of the library to be printed.
/// The current format is "major.minor.patch" (e.g. "1.0.0") but may change in any future release.
/// Thus, only use for information purposes.
/// @see getVersion() for integer based versions
static const char* versionCString() { return obx_version_string(); }
/// Creates a new string containing versionCString()
static std::string versionString() { return std::string(versionCString()); }
/// Return the version of the ObjectBox core to be printed (currently also contains a version date and features).
/// The format may change in any future release; only use for information purposes.
static const char* versionCoreCString() { return obx_version_core_string(); }
/// Creates a new string containing versionCoreCString()
static std::string versionCoreString() { return std::string(versionCoreCString()); }
/// Return the version of the library as ints. Pointers may be null
static void getVersion(int* major, int* minor, int* patch) { obx_version(major, minor, patch); }
/// Enable (or disable) debug logging for ObjectBox internals.
/// This requires a version of the library with the DebugLog feature.
/// You can check if the feature is available with obx_has_feature(OBXFeature_DebugLog).
static void debugLog(bool enabled) { internal::checkErrOrThrow(obx_debug_log(enabled)); }
/// Checks if debug logs are enabled for ObjectBox internals.
/// This depends on the availability of the DebugLog feature.
/// If the feature is available, it returns the current state, which is adjustable via obx_debug_log().
/// Otherwise, it always returns false for standard release builds
/// (or true if you are having a special debug version).
static bool debugLogEnabled() { return obx_debug_log_enabled(); }
/// Delete the store files from the given directory
static void removeDbFiles(const std::string& directory) {
internal::checkErrOrThrow(obx_remove_db_files(directory.c_str()));
}
static size_t getDbFileSize(const std::string& directory) { return obx_db_file_size(directory.c_str()); }
// -- Instance methods ---------------------------------------------------
/// Creates a Store with the given model and default Options.
explicit Store(OBX_model* model) : Store(Options().model(model)) {}
/// Creates a Store with the given Options, which also contain the data model.
explicit Store(Options& options)
: Store(internal::checkedPtrOrThrow(obx_store_open(options.release()), "Can not open store"), true) {}
/// Creates a Store with the given Options, which also contain the data model.
explicit Store(Options&& options) : Store(options) {}
/// Wraps an existing C-API store pointer, taking ownership (don't close it manually anymore)
explicit Store(OBX_store* cStore) : Store(cStore, true) {}
/// Can't be copied, single owner of C resources is required (to avoid double-free during destruction)
Store(const Store&) = delete;
Store(Store&& source) noexcept;
virtual ~Store();
/// @throws ShuttingDownException if the Store is closed (close() was call on the store).
OBX_store* cPtr() const;
/// @returns non-zero ID for the Store
uint64_t id() const;
/// Get Store type
/// @return One of ::OBXStoreTypeId
uint32_t getStoreTypeId() { return obx_store_type_id(cPtr()); }
/// Get the size of the store. For a disk-based store type, this corresponds to the size on disk, and for the
/// in-memory store type, this is roughly the used memory bytes occupied by the data.
/// @return the size in bytes of the database, or 0 if the file does not exist or some error occurred.
uint64_t getDbSize() const { return obx_store_size(cPtr()); }
/// The size in bytes occupied by the database on disk (if any).
/// @returns 0 if the underlying database is in-memory only, or the size could not be determined.
uint64_t getDbSizeOnDisk() const { return obx_store_size_on_disk(cPtr()); }
template <class EntityBinding>
Box<EntityBinding> box() {
return Box<EntityBinding>(*this);
}
/// Starts a transaction using the given mode.
Transaction tx(TxMode mode);
/// Starts a read(-only) transaction.
Transaction txRead();
/// Starts a (read &) write transaction.
Transaction txWrite();
/// Does not throw if the given type name is not found (it may still throw in other conditions).
obx_schema_id getEntityTypeIdNoThrow(const char* entityName) const {
return obx_store_entity_id(cPtr(), entityName);
}
obx_schema_id getEntityTypeId(const char* entityName) const {
obx_schema_id typeId = getEntityTypeIdNoThrow(entityName);
if (typeId == 0) internal::throwIllegalStateException("No entity type found for name: ", entityName);
return typeId;
}
/// Does not throw if the given property name is not found (it may still throw in other conditions).
obx_schema_id getPropertyIdNoThrow(obx_schema_id entityId, const char* propertyName) const {
return obx_store_entity_property_id(cPtr(), entityId, propertyName);
}
obx_schema_id getPropertyId(obx_schema_id entityId, const char* propertyName) const {
obx_schema_id propertyId = getPropertyIdNoThrow(entityId, propertyName);
if (propertyId == 0) internal::throwIllegalStateException("No property found for name: ", propertyName);
return propertyId;
}
obx_schema_id getPropertyId(const char* entityName, const char* propertyName) const {
return getPropertyId(getEntityTypeId(entityName), propertyName);
}
BoxTypeless boxTypeless(const char* entityName);
/// Await all (including future) async submissions to be completed (the async queue becomes empty).
/// @returns true if all submissions were completed (or async processing was not started)
/// @returns false if shutting down or an error occurred
bool awaitCompletion() { return obx_store_await_async_completion(cPtr()); }
/// Await previously submitted async operations to be completed (the async queue may still contain elements).
/// @returns true if all submissions were completed (or async processing was not started)
/// @returns false if shutting down or an error occurred
bool awaitSubmitted() { return obx_store_await_async_submitted(cPtr()); }
/// Backs up the store DB to the given backup-file, using the given flags.
/// Note: backup is a server-only feature.
/// @param flags 0 for defaults or OBXBackupFlags bit flags
void backUpToFile(const char* backupFile, uint32_t flags = 0) const {
obx_err err = obx_store_back_up_to_file(cPtr(), backupFile, flags);
internal::checkErrOrThrow(err);
}
/// @return an existing SyncClient associated with the store (if available; see Sync::client() to create one)
/// @note: implemented in objectbox-sync.hpp
std::shared_ptr<SyncClient> syncClient();
/// Prepares the store to close by setting its internal state to "closing".
/// Methods like tx() an boxFor() will throw ShuttingDownException once closing is initiated.
/// Unlike close(), this method will return immediately and does not free resources just yet.
/// This is typically used in a multi-threaded context to allow an orderly shutdown in stages which go through a
/// "not accepting new requests" state.
void prepareToClose();
/// Closes all resources of this store before the destructor is called, e.g. to avoid waiting in the destructor.
/// Avoid calling methods on the Store after this call; most methods will throw ShuttingDownException in that case.
/// Calling close() more than once have no effect, and concurrent calls to close() are fine too.
/// \note This waits for write transactions to finish before returning from this call.
/// \note The Store destructor also calls close(), so you do not have to call this method explicitly unless you want
/// to control the timing of closing resources and potentially waiting for asynchronous resources (e.g. transactions
/// and internal queues) to finish up.
void close();
};
#ifdef OBX_CPP_FILE
Store::Store(Store&& source) noexcept : cStore_(source.cStore_.load()), owned_(source.owned_) {
source.cStore_ = nullptr;
std::lock_guard<std::mutex> lock(source.syncClientMutex_);
syncClient_ = std::move(source.syncClient_);
}
Store::~Store() { close(); }
void Store::close() {
{
// Clean up SyncClient by explicitly closing it, even if it isn't the only shared_ptr to the instance.
// This prevents invalid use of store after it's been closed.
std::shared_ptr<Closable> syncClient;
{
std::lock_guard<std::mutex> lock(syncClientMutex_);
syncClient = std::move(syncClient_);
syncClient_ = nullptr; // to make the current state obvious
}
if (syncClient && !syncClient->isClosed()) {
#ifndef NDEBUG // todo we probably want our LOG macros here too
long useCount = syncClient.use_count();
if (useCount > 1) { // print external refs only thus "- 1"
printf("SyncClient still active with %ld references when store got closed\n", (useCount - 1));
}
#endif // NDEBUG
syncClient->close();
}
}
if (owned_) {
OBX_store* storeToClose = cStore_.exchange(nullptr); // Close exactly once
obx_store_close(storeToClose);
}
}
OBX_store* Store::cPtr() const {
OBX_store* store = cStore_.load();
if (store == nullptr) throw ShuttingDownException("Store is already closed");
return store;
}
uint64_t Store::id() const {
uint64_t id = obx_store_id(cPtr());
internal::checkIdOrThrow(id);
return id;
}
void Store::prepareToClose() {
obx_err err = obx_store_prepare_to_close(cPtr());
internal::checkErrOrThrow(err);
}
#endif
/// Provides RAII wrapper for an active database transaction on the current thread (do not use across threads). A
/// Transaction object is considered a "top level transaction" if it is the first one on the call stack in the thread.
/// If the thread already has an ongoing Transaction, additional Transaction instances are considered "inner
/// transactions".
///
/// The top level transaction defines the actual transaction scope on the DB level. Internally, the top level
/// Transaction object manages (creates and destroys) a Transaction object. Inner transactions use the Transaction
/// object of the top level Transaction.
///
/// For write transactions, the top level call to success() actually commits the underlying Transaction. If inner
/// transactions are spawned, all of them must call success() in order for the top level transaction to be successful
/// and actually commit.
class Transaction {
TxMode mode_;
OBX_txn* cTxn_;
public:
Transaction(Store& store, TxMode mode);
/// Delete because the default copy constructor can break things (i.e. a Transaction can not be copied).
Transaction(const Transaction&) = delete;
/// Move constructor, used by Store::tx()
Transaction(Transaction&& source) noexcept : mode_(source.mode_), cTxn_(source.cTxn_) { source.cTxn_ = nullptr; }
/// Copy-and-swap style
Transaction& operator=(Transaction source);
/// Never throws
virtual ~Transaction() { closeNoThrow(); };
/// A Transaction is active if it was not ended via success(), close() or moving.
bool isActive() { return cTxn_ != nullptr; }
/// The transaction pointer of the ObjectBox C API.
/// @throws if this Transaction was already closed or moved
OBX_txn* cPtr() const;
/// "Finishes" this write transaction successfully; performs a commit if this is the top level transaction and all
/// inner transactions (if any) were also successful. This object will also be "closed".
/// @throws Exception if this is not a write TX or it was closed before (e.g. via success()).
void success();
/// Explicit close to free up resources (non-throwing version).
/// It's OK to call this method multiple times; additional calls will have no effect.
obx_err closeNoThrow();
/// Explicit close to free up resources; unlike closeNoThrow() (which is also called by the destructor), this
/// version throw in the unlikely case of failing.
/// It's OK to call this method multiple times; additional calls will have no effect.
void close();
/// The data size of the committed state (not updated for this transaction).
uint64_t getDataSizeCommitted() const;
/// Cumulative change (delta) of data size by this pending transaction (uncommitted).
int64_t getDataSizeChange() const;
};
#ifdef OBX_CPP_FILE
Transaction::Transaction(Store& store, TxMode mode)
: mode_(mode), cTxn_(mode == TxMode::WRITE ? obx_txn_write(store.cPtr()) : obx_txn_read(store.cPtr())) {
internal::checkPtrOrThrow(cTxn_, "Can not start transaction");
}
Transaction& Transaction::operator=(Transaction source) {
std::swap(mode_, source.mode_);
std::swap(cTxn_, source.cTxn_);
return *this;
}
OBX_txn* Transaction::cPtr() const {
OBX_VERIFY_STATE(cTxn_);
return cTxn_;
}
void Transaction::success() {
OBX_txn* txn = cTxn_;
OBX_VERIFY_STATE(txn);
cTxn_ = nullptr;
internal::checkErrOrThrow(obx_txn_success(txn));
}
obx_err Transaction::closeNoThrow() {
OBX_txn* txnToClose = cTxn_;
cTxn_ = nullptr;
return obx_txn_close(txnToClose);
}
void Transaction::close() { internal::checkErrOrThrow(closeNoThrow()); }
uint64_t Transaction::getDataSizeCommitted() const {