diff --git a/include/cbl/CBLCollection.h b/include/cbl/CBLCollection.h index 070702ce..288f2fc3 100644 --- a/include/cbl/CBLCollection.h +++ b/include/cbl/CBLCollection.h @@ -155,12 +155,10 @@ CBLScope* CBLDatabase_DefaultScope(const CBLDatabase* db, CBLError* _cbl_nullable outError) CBLAPI; /** Returns the default collection. - @note The default collection may not exist if it was deleted. - Also, the default collection cannot be recreated after being deleted. @note You are responsible for releasing the returned collection. @param db The database. @param outError On failure, the error will be written here. - @return A \ref CBLCollection instance, or NULL if the default collection doesn't exist or an error occurred. */ + @return A \ref CBLCollection instance, or NULL if an error occurred. */ CBLCollection* _cbl_nullable CBLDatabase_DefaultCollection(const CBLDatabase* db, CBLError* _cbl_nullable outError) CBLAPI; diff --git a/src/CBLCollection.cc b/src/CBLCollection.cc index f7b91481..c8f6e179 100644 --- a/src/CBLCollection.cc +++ b/src/CBLCollection.cc @@ -70,18 +70,8 @@ namespace cbl_internal { change.collection = _collection; change.docID = _docID; - Retained db; - try { - db = _collection->database(); - } catch (...) { - C4Error error = C4Error::fromCurrentException(); - CBL_Log(kCBLLogDomainDatabase, kCBLLogWarning, - "Document changed notification failed: %s", error.description().c_str()); - } - - if (db) { - db->notify(this, change); - } + auto db = _collection->database(); + db->notify(this, change); } Retained _collection; diff --git a/src/CBLCollection_CAPI.cc b/src/CBLCollection_CAPI.cc index 89d2e8a5..a3610503 100644 --- a/src/CBLCollection_CAPI.cc +++ b/src/CBLCollection_CAPI.cc @@ -87,7 +87,7 @@ CBLScope* CBLDatabase_DefaultScope(const CBLDatabase* db, CBLError* outError) no CBLCollection* CBLDatabase_DefaultCollection(const CBLDatabase* db, CBLError* outError) noexcept { try { - return const_cast(db)->getDefaultCollection(false).detach(); + return const_cast(db)->getDefaultCollection().detach(); } catchAndBridge(outError) } @@ -115,9 +115,7 @@ uint64_t CBLCollection_Count(const CBLCollection* collection) noexcept { /** Private API */ CBLDatabase* CBLCollection_Database(const CBLCollection* collection) noexcept { - try { - return collection->database(); - } catchAndWarn() + return collection->database(); } /** Private API */ diff --git a/src/CBLCollection_Internal.hh b/src/CBLCollection_Internal.hh index bc6817fc..43866766 100644 --- a/src/CBLCollection_Internal.hh +++ b/src/CBLCollection_Internal.hh @@ -36,9 +36,17 @@ public: CBLCollection(C4Collection* c4col, CBLScope* scope, CBLDatabase* database) :_c4col(c4col, database) ,_scope(scope) + ,_database(retain(database)) ,_name(c4col->getName()) { } + ~CBLCollection() { + LOCK(_adoptMutex); + if (!_adopted) { + release(_database); + } + } + #pragma mark - ACCESSORS: Retained scope() const noexcept {return _scope;} @@ -47,9 +55,7 @@ public: bool isValid() const noexcept {return _c4col.isValid();} uint64_t count() const {return _c4col.useLocked()->getDocumentCount();} uint64_t lastSequence() const {return static_cast(_c4col.useLocked()->getLastSequence());} - - /** Throw NotOpen if the collection or database is invalid */ - CBLDatabase* database() const {return _c4col.database();} + CBLDatabase* database() const {return _database; } #pragma mark - DOCUMENTS: @@ -161,17 +167,24 @@ protected: friend struct CBLDocument; friend struct cbl_internal::ListenerToken; + // Called by the database to take an ownership. + // Release the database to avoid the circular reference + void adopt(CBLDatabase* db) { + assert(_database == db); + LOCK(_adoptMutex); + if (!_adopted) { + _scope->adopt(db); + release(_database); + _adopted = true; + } + } + auto useLocked() { return _c4col.useLocked(); } template void useLocked(LAMBDA callback) { _c4col.useLocked(callback); } template RESULT useLocked(LAMBDA callback) { return _c4col.useLocked(callback); } - /** Called by the database when the database is released. */ - void close() { - _c4col.close(); // This will invalidate the database pointer in the access lock - } - private: Retained getDocument(slice docID, bool isMutable, bool allRevisions) const { @@ -204,18 +217,7 @@ private: } void collectionChanged() { - Retained db; - try { - db = database(); - } catch (...) { - C4Error error = C4Error::fromCurrentException(); - CBL_Log(kCBLLogDomainDatabase, kCBLLogWarning, - "Collection changed notification failed: %s", error.description().c_str()); - } - - if (db) { - db->notify(std::bind(&CBLCollection::callCollectionChangeListeners, this)); - } + _database->notify(std::bind(&CBLCollection::callCollectionChangeListeners, this)); } void callCollectionChangeListeners() { @@ -253,12 +255,10 @@ private: :shared_access_lock(std::move(c4col), *database->c4db()) ,_c4db(database->c4db()) ,_col(c4col) - ,_db(database) { _sentry = [this](C4Collection* c4col) { if (!_isValid()) { - C4Error::raise(LiteCoreDomain, kC4ErrorNotOpen, - "Invalid collection: either deleted, or db closed"); + C4Error::raise(LiteCoreDomain, kC4ErrorNotOpen, "Invalid collection: either deleted or db closed"); } }; } @@ -268,22 +268,11 @@ private: return _isValid(); } - CBLDatabase* database() const { - auto lock = useLocked(); - return _db; - } - - /** Invalidate the database pointer */ - void close() noexcept { - LOCK_GUARD lock(getMutex()); - _db = nullptr; - } - private: - bool _isValid() const noexcept { return _db && _col->isValid(); } + // Unsafe: need to call under lock: + bool _isValid() const noexcept { return !_c4db->isClosedNoLock() && _col->isValid(); } CBLDatabase::SharedC4DatabaseAccessLock _c4db; // For retaining the shared lock - CBLDatabase* _cbl_nullable _db; C4Collection* _col; }; @@ -294,6 +283,10 @@ private: alloc_slice _name; Retained _scope; + CBLDatabase* _database; // Retained unless being adopted + bool _adopted {false}; // Adopted by the database + mutable std::mutex _adoptMutex; + std::unique_ptr _observer; Listeners _listeners; Listeners _docListeners; diff --git a/src/CBLDatabase.cc b/src/CBLDatabase.cc index b76d018f..e2cc55e7 100644 --- a/src/CBLDatabase.cc +++ b/src/CBLDatabase.cc @@ -88,7 +88,6 @@ CBLDatabase::CBLDatabase(C4Database* _cbl_nonnull db, slice name_, slice dir_) ,_notificationQueue(this) { _c4db = std::make_shared(db); - _defaultCollection = getCollection(kC4DefaultCollectionName, kC4DefaultScopeID); } @@ -129,14 +128,6 @@ void CBLDatabase::closeAndDelete() { /** Must called under _c4db lock. */ void CBLDatabase::_closed() { - // Close scopes: - for (auto& i : _scopes) { - i.second->close(); - } - // Close collections: - for (auto& i : _collections) { - i.second->close(); - } // Close the access lock: _c4db->close(); } @@ -151,34 +142,11 @@ Retained CBLDatabase::getScope(slice scopeName) { auto c4db = _c4db->useLocked(); - CBLScope* scope = nullptr; - bool exist = c4db->hasScope(scopeName); - if (auto i = _scopes.find(scopeName); i != _scopes.end()) { - if (!exist) { - // Detach instead of close so that the retained scope - // object can retain the database and can be valid to use. - i->second->detach(); - - // Remove from the _scopes map. - _scopes.erase(i); - - // Remove all collections in the scope from the _collections map: - // This is techically required to prevent circular reference - // (db->collection->scope->db) when the scope is detached. - removeCBLCollections(scopeName); - return nullptr; - } - scope = i->second.get(); - } - - if (!scope && exist) { - auto retainedScope = make_retained(scopeName, this); - scope = retainedScope.get(); - _scopes.insert({scope->name(), std::move(retainedScope)}); + if (!exist) { + return nullptr; } - - return scope; + return new CBLScope(scopeName, this); } @@ -191,33 +159,13 @@ Retained CBLDatabase::getCollection(slice collectionName, slice s auto c4db = _c4db->useLocked(); - CBLCollection* collection = nullptr; auto spec = C4Database::CollectionSpec(collectionName, scopeName); - if (auto i = _collections.find(spec); i != _collections.end()) { - collection = i->second.get(); - } - - if (collection && collection->isValid()) { - return collection; - } - auto c4col = c4db->getCollection(spec); if (!c4col) { - if (collection) { - removeCBLCollection(spec); // Invalidate cache - } return nullptr; } - - auto scope = getScope(scopeName); - if (!scope) { - // Note (Edge Case): - // The scope is NULL because at the same time, its all collections including the - // the one just created were deleted on a different thread using another database. - return nullptr; - } - - return createCBLCollection(c4col, scope.get()); + auto scope = new CBLScope(scopeName, this); + return new CBLCollection(c4col, scope, this); } @@ -227,26 +175,10 @@ Retained CBLDatabase::createCollection(slice collectionName, slic auto c4db = _c4db->useLocked(); - auto col = getCollection(collectionName, scopeName); - if (col) { - return col; - } - auto spec = C4Database::CollectionSpec(collectionName, scopeName); auto c4col = c4db->createCollection(spec); - - bool cache = true; - auto scope = getScope(scopeName); - if (!scope) { - // Note (Edge Case): - // The scope is NULL because at the same time, its all collections including the - // the one just created were deleted on a different thread using another database. - // So create the scope in non-cached mode, and the returned collection will not - // be cached. - cache = false; - scope = new CBLScope(scopeName, this, cache); - } - return createCBLCollection(c4col, scope.get(), cache); + auto scope = new CBLScope(scopeName, this); + return new CBLCollection(c4col, scope, this); } @@ -258,60 +190,26 @@ bool CBLDatabase::deleteCollection(slice collectionName, slice scopeName) { auto spec = C4Database::CollectionSpec(collectionName, scopeName); c4db->deleteCollection(spec); - removeCBLCollection(spec); return true; } Retained CBLDatabase::getDefaultScope() { - _c4db->useLocked(); return getScope(kC4DefaultScopeID); } -Retained CBLDatabase::getDefaultCollection(bool mustExist) { - auto db = _c4db->useLocked(); - - if (_defaultCollection && !_defaultCollection->isValid()) { - _defaultCollection = nullptr; - } - - if (!_defaultCollection && mustExist) { - C4Error::raise(LiteCoreDomain, kC4ErrorNotOpen, - "Invalid collection: either deleted, or db closed"); - } - - return _defaultCollection; +Retained CBLDatabase::getDefaultCollection() { + return getCollection(kC4DefaultCollectionName, kC4DefaultScopeID); } -Retained CBLDatabase::createCBLCollection(C4Collection* c4col, CBLScope* scope, bool cache) { - auto retainedCollection = make_retained(c4col, scope, const_cast(this)); - auto collection = retainedCollection.get(); - if (cache) { - _collections.insert({C4Database::CollectionSpec(c4col->getSpec()), std::move(retainedCollection)}); - } - return collection; -} - - -void CBLDatabase::removeCBLCollection(C4Database::CollectionSpec spec) { - if (auto i = _collections.find(spec); i != _collections.end()) { - i->second.get()->close(); - _collections.erase(i); - } -} - -void CBLDatabase::removeCBLCollections(slice scopeName) { - auto i = _collections.begin(); - while (i != _collections.end()) { - if (i->first.scope == scopeName) { - i->second->close(); - i = _collections.erase(i); - } else { - i++; - } +Retained CBLDatabase::getInternalDefaultCollection() { + if (!_defaultCollection) { + _defaultCollection = getCollection(kC4DefaultCollectionName, kC4DefaultScopeID); + _defaultCollection->adopt(this); // Prevent the retain cycle } + return _defaultCollection; } diff --git a/src/CBLDatabase_CAPI.cc b/src/CBLDatabase_CAPI.cc index bc914dcd..d61ad4bb 100644 --- a/src/CBLDatabase_CAPI.cc +++ b/src/CBLDatabase_CAPI.cc @@ -142,7 +142,7 @@ const CBLDatabaseConfiguration CBLDatabase_Config(const CBLDatabase* db) noexcep uint64_t CBLDatabase_Count(const CBLDatabase* db) noexcept { try { - auto col = const_cast(db)->getDefaultCollection(true); + auto col = const_cast(db)->getInternalDefaultCollection(); return col->count(); } catchAndWarn(); } @@ -150,7 +150,7 @@ uint64_t CBLDatabase_Count(const CBLDatabase* db) noexcept { /** Private API */ uint64_t CBLDatabase_LastSequence(const CBLDatabase* db) noexcept { try { - auto col = const_cast(db)->getDefaultCollection(true); + auto col = const_cast(db)->getInternalDefaultCollection(); return col->lastSequence(); } catchAndWarn() } @@ -163,7 +163,7 @@ const CBLDocument* CBLDatabase_GetDocument(const CBLDatabase* db, FLString docID CBLError* outError) noexcept { try { - auto col = const_cast(db)->getDefaultCollection(true); + auto col = const_cast(db)->getInternalDefaultCollection(); return CBLCollection_GetDocument(col, docID, outError); } catchAndBridge(outError) } @@ -173,7 +173,7 @@ CBLDocument* CBLDatabase_GetMutableDocument(CBLDatabase* db, FLString docID, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_GetMutableDocument(col, docID, outError); } catchAndBridge(outError) } @@ -193,7 +193,7 @@ bool CBLDatabase_SaveDocumentWithConcurrencyControl(CBLDatabase* db, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_SaveDocumentWithConcurrencyControl(col, doc, concurrency, outError); } catchAndBridge(outError) } @@ -205,7 +205,7 @@ bool CBLDatabase_SaveDocumentWithConflictHandler(CBLDatabase* db, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_SaveDocumentWithConflictHandler(col, doc, conflictHandler, context, outError); } catchAndBridge(outError) @@ -216,7 +216,7 @@ bool CBLDatabase_DeleteDocument(CBLDatabase *db, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_DeleteDocument(col, doc, outError); } catchAndBridge(outError) } @@ -227,7 +227,7 @@ bool CBLDatabase_DeleteDocumentWithConcurrencyControl(CBLDatabase *db, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_DeleteDocumentWithConcurrencyControl(col, doc, concurrency, outError); } catchAndBridge(outError) } @@ -238,7 +238,7 @@ bool CBLDatabase_DeleteDocumentByID(CBLDatabase* db, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_DeleteDocumentByID(col, docID, outError); } catchAndBridge(outError) } @@ -248,7 +248,7 @@ bool CBLDatabase_PurgeDocument(CBLDatabase* db, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); CBLDocument::checkCollectionMatches(doc->collection(), col); return CBLCollection_PurgeDocumentByID(col, doc->docID(), outError); } catchAndBridge(outError) @@ -260,7 +260,7 @@ bool CBLDatabase_PurgeDocumentByID(CBLDatabase* db, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_PurgeDocumentByID(col, docID, outError); } catchAndBridge(outError) } @@ -270,7 +270,7 @@ CBLTimestamp CBLDatabase_GetDocumentExpiration(CBLDatabase* db, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_GetDocumentExpiration(col, docID, outError); } catchAndBridge(outError) } @@ -281,7 +281,7 @@ bool CBLDatabase_SetDocumentExpiration(CBLDatabase* db, CBLError* outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_SetDocumentExpiration(col, docID, expiration, outError); } catchAndBridge(outError) } @@ -296,7 +296,7 @@ bool CBLDatabase_CreateValueIndex(CBLDatabase *db, CBLError *outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_CreateValueIndex(col, name, config, outError); } catchAndBridge(outError) } @@ -308,7 +308,7 @@ bool CBLDatabase_CreateFullTextIndex(CBLDatabase *db, CBLError *outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_CreateFullTextIndex(col, name, config, outError); } catchAndBridge(outError) } @@ -319,7 +319,7 @@ bool CBLDatabase_DeleteIndex(CBLDatabase *db, CBLError *outError) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); return CBLCollection_DeleteIndex(col, name, outError); } catchAndBridge(outError) } @@ -327,7 +327,7 @@ bool CBLDatabase_DeleteIndex(CBLDatabase *db, FLArray CBLDatabase_GetIndexNames(CBLDatabase *db) noexcept { try { - auto col = db->getDefaultCollection(true); + auto col = db->getInternalDefaultCollection(); CBLError error; auto result = CBLCollection_GetIndexNames(col, &error); @@ -380,7 +380,7 @@ CBLListenerToken* CBLDatabase_AddChangeListener(const CBLDatabase* db, }; try { - CBLCollection* col = const_cast(db)->getDefaultCollection(true).detach(); + CBLCollection* col = const_cast(db)->getInternalDefaultCollection().detach(); wrappedContext->collection = col; auto token = CBLCollection_AddChangeListener(col, wrappedListener, wrappedContext); @@ -421,7 +421,7 @@ CBLListenerToken* CBLDatabase_AddDocumentChangeListener(const CBLDatabase* db, }; try { - CBLCollection* col = const_cast(db)->getDefaultCollection(true).detach(); + CBLCollection* col = const_cast(db)->getInternalDefaultCollection().detach(); wrappedContext->collection = col; auto token = CBLCollection_AddDocumentChangeListener(col, docID, wrappedListener, wrappedContext); diff --git a/src/CBLDatabase_Internal.hh b/src/CBLDatabase_Internal.hh index 5a0a8a07..01eeb54b 100644 --- a/src/CBLDatabase_Internal.hh +++ b/src/CBLDatabase_Internal.hh @@ -154,11 +154,12 @@ public: Retained getDefaultScope(); + Retained getDefaultCollection(); + /** - * Returned the default collection retained by the database. It will be used for any database's operations - * that refer to the default collection. If the default collection doesn't exist when getting from the database, - * the method will throw kC4ErrorNotOpen exception. */ - Retained getDefaultCollection(bool mustExist); + * The cached default collection for internal use only. + */ + Retained getInternalDefaultCollection(); #pragma mark - Queries & Indexes: @@ -215,7 +216,7 @@ protected: :access_lock(std::move(db)) { _sentry = [this](C4Database* db) { - if (_closed) { + if (isClosedNoLock()) { C4Error::raise(LiteCoreDomain, kC4ErrorNotOpen, "Database is closed or deleted"); } }; @@ -226,6 +227,10 @@ protected: _closed = true; } + bool isClosedNoLock() { + return _closed; + } + template /** If the AccessLock is closed, the call will be ignored instead of letting the sentry throws. */ void useLockedIgnoredWhenClosed(CALLBACK callback) { @@ -315,17 +320,6 @@ private: } } - /** - Create a CBLCollection from the C4Collection. - The created CBLCollection will be retained and cached in the _collections map. */ - Retained createCBLCollection(C4Collection* c4col, CBLScope* scope, bool cache= true); - - /** Remove and close the collection from the _collections map */ - void removeCBLCollection(C4Database::CollectionSpec spec); - - /** Remove and close all collections in the specify scope the _collections map */ - void removeCBLCollections(slice scopeName); - void callDocListeners(); void stopActiveStoppables() { @@ -386,9 +380,7 @@ private: alloc_slice const _name; alloc_slice const _dir; - mutable ScopesMap _scopes; - mutable CollectionsMap _collections; - Retained _defaultCollection; + Retained _defaultCollection; // Internal default collection // For sending notifications: NotificationQueue _notificationQueue; diff --git a/src/CBLDocument.cc b/src/CBLDocument.cc index b2650a3d..7c61033f 100644 --- a/src/CBLDocument.cc +++ b/src/CBLDocument.cc @@ -68,7 +68,6 @@ CBLDocument::~CBLDocument() { CBLDatabase* _cbl_nullable CBLDocument::database() const { - // Could throw kC4ErrorNotOpen if the collection is deleted, or database is released. return _collection ? _collection->database() : nullptr; } diff --git a/src/CBLPrivate.h b/src/CBLPrivate.h index 355bc19d..fe22f1a1 100644 --- a/src/CBLPrivate.h +++ b/src/CBLPrivate.h @@ -28,8 +28,8 @@ CBL_CAPI_BEGIN void CBLLog_BeginExpectingExceptions() CBLAPI; void CBLLog_EndExpectingExceptions() CBLAPI; -/** Returns the collection's database, or NULL if the collection is invalid, or the database is released. */ - CBLDatabase* _cbl_nullable CBLCollection_Database(const CBLCollection*) CBLAPI; +/** Returns the collection's database, */ + CBLDatabase* CBLCollection_Database(const CBLCollection*) CBLAPI; /** Returns the last sequence number assigned in the database (default collection). This starts at zero and increments every time a document is saved or deleted. */ diff --git a/src/CBLReplicator_CAPI.cc b/src/CBLReplicator_CAPI.cc index 8e3ca5e1..6a8752f9 100644 --- a/src/CBLReplicator_CAPI.cc +++ b/src/CBLReplicator_CAPI.cc @@ -84,14 +84,22 @@ void CBLReplicator_SetSuspended(CBLReplicator* repl, bool sus) noexcept {repl- FLDict CBLReplicator_PendingDocumentIDs(CBLReplicator *repl, CBLError *outError) noexcept { try { - auto col = repl->database()->getDefaultCollection(true); + auto col = repl->defaultCollection(); + if (!col) { + C4Error::raise(LiteCoreDomain, kC4ErrorInvalidParameter, + "The default collection is not included in the replicator config."); + } return CBLReplicator_PendingDocumentIDs2(repl, col, outError); } catchAndBridge(outError) } bool CBLReplicator_IsDocumentPending(CBLReplicator *repl, FLString docID, CBLError *outError) noexcept { try { - auto col = repl->database()->getDefaultCollection(true); + auto col = repl->defaultCollection(); + if (!col) { + C4Error::raise(LiteCoreDomain, kC4ErrorInvalidParameter, + "The default collection is not included in the replicator config."); + } return CBLReplicator_IsDocumentPending2(repl, docID, col, outError); } catchAndBridge(outError) } diff --git a/src/CBLReplicator_Internal.hh b/src/CBLReplicator_Internal.hh index 9ddf7435..5d8d4013 100644 --- a/src/CBLReplicator_Internal.hh +++ b/src/CBLReplicator_Internal.hh @@ -72,7 +72,7 @@ public: call_once(once, std::bind(&C4RegisterBuiltInWebSocket)); if (_conf.database) { - _defaultCollection = _conf.database->getDefaultCollection(true); + _defaultCollection = _conf.database->getInternalDefaultCollection(); } _conf.validate(); @@ -237,9 +237,21 @@ public: _stoppable = make_unique(this); } + CBLCollection* _cbl_nullable defaultCollection() { + if (_defaultCollection) { + return _defaultCollection; + } + + if (auto i = _collections.find(kC4DefaultCollectionSpec); i != _collections.end()) { + return i->second.collection; + } + + return nullptr; + } const ReplicatorConfiguration* configuration() const {return &_conf;} CBLDatabase* database() const {return _db;} + void setHostReachable(bool reachable) {_c4repl->setHostReachable(reachable);} void setSuspended(bool suspended) {_c4repl->setSuspended(suspended);} void stop() {_c4repl->stop();} diff --git a/src/CBLScope.cc b/src/CBLScope.cc index c5d449df..b0d0f821 100644 --- a/src/CBLScope.cc +++ b/src/CBLScope.cc @@ -23,6 +23,5 @@ using namespace fleece; Retained CBLScope::getCollection(slice collectionName) const { LOCK(_mutex); - checkOpen(); return _database->getCollection(collectionName, _name); } diff --git a/src/CBLScope_Internal.hh b/src/CBLScope_Internal.hh index fb06feec..f62f92b3 100644 --- a/src/CBLScope_Internal.hh +++ b/src/CBLScope_Internal.hh @@ -29,18 +29,14 @@ public: #pragma mark - CONSTRUCTORS: - CBLScope(slice name, CBLDatabase* database, bool cached= true) + CBLScope(slice name, CBLDatabase* database) :_name(name) - ,_database(database) - { - if (!cached) { - _detach(); - } - } + ,_database(retain(database)) + { } ~CBLScope() { LOCK(_mutex); - if (_detached) { + if (!_adopted) { release(_database); } } @@ -52,8 +48,6 @@ public: #pragma mark - COLLECTIONS: fleece::MutableArray collectionNames() const { - LOCK(_mutex); - checkOpen(); return _database->collectionNames(_name); } @@ -61,45 +55,27 @@ public: protected: - friend struct CBLDatabase; + friend struct CBLCollection; - // Need to call under the _mutex lock - void checkOpen() const { - if (!_database) { - C4Error::raise(LiteCoreDomain, kC4ErrorNotOpen, - "Invalid scope: scope deleted or db closed"); - } - } - - // Called by database when the database is closed or released - // to invalidate the database pointer. - void close() { + // Called by the collection to give the ownership to the database + // when the collection is adopted by the database. + // Release the database to avoid the circular reference + void adopt(CBLDatabase* db) { + assert(_database == db); LOCK(_mutex); - assert(!_detached); - _database = nullptr; - } - - // Called by the database when the scope is removed from - // the database cache (e.g. when no collections are in the scope). - // The detach() allows the scope to retain and keep using the database. - void detach() { - LOCK(_mutex); - _detach(); + if (!_adopted) { + release(_database); + _adopted = true; + } } private: - void _detach() { - assert(!_detached); - retain(_database); - _detached = true; - } - - CBLDatabase* _cbl_nullable _database; - bool _detached {false}; // Detached mode in which database is retained + CBLDatabase* _database; // Retained unless being adopted + bool _adopted {false}; // Adopted by the database alloc_slice const _name; // Name (never empty) - mutable std::recursive_mutex _mutex; + mutable std::mutex _mutex; }; CBL_ASSUME_NONNULL_END diff --git a/test/CollectionTest.cc b/test/CollectionTest.cc index fdbad312..61e0a503 100644 --- a/test/CollectionTest.cc +++ b/test/CollectionTest.cc @@ -399,8 +399,6 @@ TEST_CASE_METHOD(CollectionTest, "Create Existing Collection", "[Collection]") { REQUIRE(col2); CHECK(CBLCollection_Name(col2) == "colA"_sl); - CHECK(col1 == col2); - CBLCollection_Release(col1); CBLCollection_Release(col2); } @@ -462,10 +460,10 @@ TEST_CASE_METHOD(CollectionTest, "Get Collections from Scope", "[Collection]") { CHECK(CBLScope_Name(scope) == "scopeA"_sl); CBLCollection* colA2 = CBLScope_Collection(scope, "colA"_sl, &error); - CHECK(colA == colA2); + CHECK(CBLCollection_Name(colA2) == "colA"_sl); CBLCollection* colB2 = CBLScope_Collection(scope, "colB"_sl, &error); - CHECK(colB == colB2); + CHECK(CBLCollection_Name(colB2) == "colB"_sl); CHECK(!CBLScope_Collection(scope, "colC"_sl, &error)); CHECK(error.code == 0); @@ -540,8 +538,6 @@ TEST_CASE_METHOD(CollectionTest, "Valid Collection and Scope Names", "[Collectio CBLCollection* col2 = CBLDatabase_Collection(db, slice(name), slice(name), &error); REQUIRE(col2); - CHECK(col1 == col2); - CBLCollection_Release(col1); CBLCollection_Release(col2); } diff --git a/test/CollectionTest_Cpp.cc b/test/CollectionTest_Cpp.cc index 4a910d88..7626722b 100644 --- a/test/CollectionTest_Cpp.cc +++ b/test/CollectionTest_Cpp.cc @@ -232,7 +232,6 @@ TEST_CASE_METHOD(CollectionTest_Cpp, "C++ Create Existing Collection", "[Collect REQUIRE(col2); CHECK(col2.name() == "colA"); CHECK(col2.scopeName() == "scopeA"); - CHECK(col1 == col2); } TEST_CASE_METHOD(CollectionTest_Cpp, "C++ Delete Collection", "[Collection]") { diff --git a/test/LogTest.cc b/test/LogTest.cc index 976aed3c..1001238b 100644 --- a/test/LogTest.cc +++ b/test/LogTest.cc @@ -260,7 +260,7 @@ TEST_CASE_METHOD(LogTest, "File Logging : Set Log Level", "[Log][FileLog]") { } // Verify: - int lineCount = 1; // Header: + int lineCount = 2; // 2 header lines : for (CBLLogLevel level : kLogLevels) { if (level == kCBLLogNone) continue; diff --git a/test/QueryTest.cc b/test/QueryTest.cc index e6500540..56a7b94e 100644 --- a/test/QueryTest.cc +++ b/test/QueryTest.cc @@ -505,7 +505,7 @@ TEST_CASE_METHOD(QueryTest, "Query Listener", "[Query][LiveQuery]") { cerr << "Adding listener\n"; ListenerState state; - auto listenerToken = CBLQuery_AddChangeListener(query, [](void *context, CBLQuery* query, CBLListenerToken* token) { + CBLListenerToken* listenerToken = CBLQuery_AddChangeListener(query, [](void *context, CBLQuery* query, CBLListenerToken* token) { ((ListenerState*)context)->receivedCallback(context, query, token); }, &state); @@ -543,7 +543,7 @@ TEST_CASE_METHOD(QueryTest, "Remove Query Listener", "[Query][LiveQuery]") { cerr << "Adding listener\n"; ListenerState state; - auto listenerToken = CBLQuery_AddChangeListener(query, [](void *context, CBLQuery* query, CBLListenerToken* token) { + CBLListenerToken* listenerToken = CBLQuery_AddChangeListener(query, [](void *context, CBLQuery* query, CBLListenerToken* token) { ((ListenerState*)context)->receivedCallback(context, query, token); }, &state); @@ -585,7 +585,7 @@ TEST_CASE_METHOD(QueryTest, "Query Listener and Changing parameters", "[Query][L cerr << "Adding listener\n"; ListenerState state; - auto listenerToken = CBLQuery_AddChangeListener(query, [](void *context, CBLQuery* query, CBLListenerToken* token) { + CBLListenerToken* listenerToken = CBLQuery_AddChangeListener(query, [](void *context, CBLQuery* query, CBLListenerToken* token) { ((ListenerState*)context)->receivedCallback(context, query, token); }, &state); @@ -623,10 +623,10 @@ TEST_CASE_METHOD(QueryTest, "Multiple Query Listeners", "[Query][LiveQuery]") { cerr << "Adding listener\n"; ListenerState state1; - auto token1 = CBLQuery_AddChangeListener(query, callback, &state1); + CBLListenerToken* token1 = CBLQuery_AddChangeListener(query, callback, &state1); ListenerState state2; - auto token2 = CBLQuery_AddChangeListener(query, callback, &state2); + CBLListenerToken* token2 = CBLQuery_AddChangeListener(query, callback, &state2); cerr << "Waiting for listener 1...\n"; state1.waitForCount(1); @@ -654,7 +654,7 @@ TEST_CASE_METHOD(QueryTest, "Multiple Query Listeners", "[Query][LiveQuery]") { cerr << "Adding another listener\n"; ListenerState state3; - auto token3 = CBLQuery_AddChangeListener(query, callback, &state3); + CBLListenerToken* token3 = CBLQuery_AddChangeListener(query, callback, &state3); cerr << "Waiting for the listener 3...\n"; state3.waitForCount(1); @@ -684,7 +684,7 @@ TEST_CASE_METHOD(QueryTest, "Query Listener and Coalescing notification", "[Quer cerr << "Adding listener\n"; ListenerState state; - auto listenerToken = CBLQuery_AddChangeListener(query, [](void *context, CBLQuery* query, CBLListenerToken* token) { + CBLListenerToken* listenerToken = CBLQuery_AddChangeListener(query, [](void *context, CBLQuery* query, CBLListenerToken* token) { ((ListenerState*)context)->receivedCallback(context, query, token); }, &state); @@ -970,14 +970,14 @@ TEST_CASE_METHOD(QueryTest, "FTS with FTS Index in Named Collection", "[Query]") createDocWithJSON(people, "person2", "{\"name\": { \"first\": \"Jasper\",\"last\":\"Okorududu\"}, \"random\": \"1\"}"); createDocWithJSON(people, "person3", "{\"name\": { \"first\": \"Monica\",\"last\":\"Polina\"}, \"random\": \"2\"}"); - CBLCollection_Release(people); - CBLFullTextIndexConfiguration index = {}; index.expressionLanguage = kCBLN1QLLanguage; index.expressions = "name.first"_sl; index.ignoreAccents = false; CHECK(CBLCollection_CreateFullTextIndex(people, "index"_sl, index, &error)); + CBLCollection_Release(people); + SECTION("name"){ queryString = "SELECT name " "FROM test.people " diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 20bf8565..476a079a 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 20bf8565c434def7ce4d82824e54971e69d0595c +Subproject commit 476a079a57ab4076c927ed3a0e81cd50a54b1cbd