diff --git a/iModelCore/BeSQLite/BeSQLite.cpp b/iModelCore/BeSQLite/BeSQLite.cpp index ba0663d230..05abcd466e 100644 --- a/iModelCore/BeSQLite/BeSQLite.cpp +++ b/iModelCore/BeSQLite/BeSQLite.cpp @@ -2774,6 +2774,36 @@ DbResult Db::DetachDb(Utf8CP alias) const return rc; } +/*---------------------------------------------------------------------------------**//** +* ++---------------+---------------+---------------+---------------+---------------+------*/ +std::vector Db::GetAttachedDbs() const { + if (!IsDbOpen()) + return {}; + + std::vector result; + Statement stmt; + stmt.Prepare(*this, "PRAGMA database_list"); + while (stmt.Step() == BE_SQLITE_ROW) { + AttachFileInfo info; + info.m_alias = stmt.GetValueText(1); + info.m_fileName = stmt.GetValueText(2); + if (info.m_alias.EqualsIAscii("main")) { + info.m_type = AttachFileTypes::Main; + } else if (info.m_alias.EqualsIAscii("schema_sync_db")){ + info.m_type = AttachFileTypes::SchemaSync; + } else if (info.m_alias.EqualsIAscii("ecchange")){ + info.m_type = AttachFileTypes::ECChangeCache; + } else if (info.m_alias.EqualsIAscii("temp")){ + info.m_type = AttachFileTypes::Temp; + } else { + info.m_type = AttachFileTypes::Unknown; + } + result.push_back(info); + } + return result; +} + /*---------------------------------------------------------------------------------**//** * +---------------+---------------+---------------+---------------+---------------+------*/ diff --git a/iModelCore/BeSQLite/PublicAPI/BeSQLite/BeSQLite.h b/iModelCore/BeSQLite/PublicAPI/BeSQLite/BeSQLite.h index 9ad1a9a843..71bfb2fe33 100644 --- a/iModelCore/BeSQLite/PublicAPI/BeSQLite/BeSQLite.h +++ b/iModelCore/BeSQLite/PublicAPI/BeSQLite/BeSQLite.h @@ -393,6 +393,21 @@ enum class WalCheckpointMode { Truncate=3, /* Like RESTART but also truncate WAL */ }; +enum class AttachFileTypes { + Unknown, + Main, + Temp, + SchemaSync, + ECChangeCache +}; + +struct AttachFileInfo final { +public: + Utf8String m_fileName; + Utf8String m_alias; + AttachFileTypes m_type; +}; + //======================================================================================= //! A 4-digit number that specifies the version of the "profile" (schema) of a Db // @bsiclass @@ -2919,6 +2934,7 @@ struct EXPORT_VTABLE_ATTRIBUTE Db : NonCopyableClass //! Detach a previously attached database. This method is necessary for the same reason AttachDb is necessary. //! @param[in] alias The alias by which the database was attached. BE_SQLITE_EXPORT DbResult DetachDb(Utf8CP alias) const; + BE_SQLITE_EXPORT std::vector GetAttachedDbs() const; //! Execute a single SQL statement on this Db. //! This merely binds, steps, and finalizes the statement. It is no more efficient than performing those steps individually, diff --git a/iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.cpp b/iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.cpp index d8510ae355..83a250982c 100644 --- a/iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.cpp +++ b/iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.cpp @@ -107,20 +107,71 @@ ECSqlRowAdaptor& CachedQueryAdaptor::GetJsonAdaptor() { //--------------------------------------------------------------------------------------- // @bsimethod //--------------------------------------------------------------------------------------- -void CachedConnection::Execute(std::function cb, std::unique_ptr request) { - if (true) { - recursive_guard_t lock(m_mutexReq); - if (!m_isChangeSummaryCacheAttached) { - BeFileName primaryChangeCacheFile; - if (GetPrimaryDb().TryGetChangeCacheFileName(primaryChangeCacheFile)) { - if (!m_db.IsChangeCacheAttached()) { - if (BE_SQLITE_OK == m_db.AttachChangeCache(primaryChangeCacheFile)) { - m_isChangeSummaryCacheAttached = true; - } - } - } +void CachedConnection::SyncAttachDbs() { + recursive_guard_t lock(m_mutexReq); + if (m_request != nullptr || !m_db.IsDbOpen()) { + // cannot sync attach dbs if request is pending. + return; + } + + // simple fast way to verify if attach file have changed or not. + const auto primaryAttachDbs = GetPrimaryDb().GetAttachedDbs(); + const auto thisAttachDbs = GetDb().GetAttachedDbs(); + std::once_flag cachedClearFlag; + auto reset = [&]() { + m_adaptorCache.Reset(); + m_db.ClearECDbCache(); + }; + + // detach dbs that does not exist on primary connection + for (auto& attachFile : thisAttachDbs) { + if (attachFile.m_type == AttachFileTypes::Main || attachFile.m_type == AttachFileTypes::Temp) { + continue; + } + + BeAssert(attachFile.m_type != AttachFileTypes::SchemaSync); + + auto it = std::find_if(primaryAttachDbs.begin(), primaryAttachDbs.end(), + [&attachFile](auto& primaryAttach) { + return primaryAttach.m_alias.EqualsIAscii(attachFile.m_alias) && + primaryAttach.m_fileName.EqualsIAscii(attachFile.m_fileName); + }); + + if (it == primaryAttachDbs.end()) { + std::call_once(cachedClearFlag, reset); + if (attachFile.m_type == AttachFileTypes::ECChangeCache) + m_db.DetachChangeCache(); + else + m_db.DetachDb(attachFile.m_alias.c_str()); + } + } + + // attach dbs that exist on primary connection but not on cached connection. + for (auto& attachFile : primaryAttachDbs) { + if (attachFile.m_type == AttachFileTypes::SchemaSync || attachFile.m_type == AttachFileTypes::Main || attachFile.m_type == AttachFileTypes::Temp) { + continue; + } + + auto it = std::find_if(thisAttachDbs.begin(), thisAttachDbs.end(), + [&attachFile](auto& thisAttach) { + return thisAttach.m_alias.EqualsIAscii(attachFile.m_alias) && thisAttach.m_fileName.EqualsIAscii(attachFile.m_fileName); + }); + + if (it == thisAttachDbs.end()) { + std::call_once(cachedClearFlag, reset); + if (attachFile.m_type == AttachFileTypes::ECChangeCache) + m_db.AttachChangeCache(BeFileName(attachFile.m_fileName.c_str())); + else + m_db.AttachDb(attachFile.m_fileName.c_str(), attachFile.m_alias.c_str()); } } +} + +//--------------------------------------------------------------------------------------- +// @bsimethod +//--------------------------------------------------------------------------------------- +void CachedConnection::Execute(std::function cb, std::unique_ptr request) { + SyncAttachDbs(); SetRequest(std::move(request)); cb(m_adaptorCache, *m_request); ClearRequest(); @@ -232,6 +283,33 @@ std::shared_ptr CachedConnection::Make(ConnectionCache& cache, } return newConn; } + +//--------------------------------------------------------------------------------------- +// @bsimethod +//--------------------------------------------------------------------------------------- +void ConnectionCache::SyncAttachDbs() { + FNV1HashBuilder builder; + for (auto& file : GetPrimaryDb().GetAttachedDbs()) { + if (file.m_type == AttachFileTypes::SchemaSync || file.m_type == AttachFileTypes::Main || file.m_type == AttachFileTypes::Temp) { + continue; + } + builder.UpdateString(file.m_alias); + builder.UpdateString(file.m_fileName); + } + + const auto hashCode = builder.GetHashCode(); + if (hashCode == m_primaryAttachFileHash) { + return; + } + + recursive_guard_t lock(m_mutex); + m_primaryAttachFileHash = hashCode; + for (auto& conn: m_conns) { + if (conn != nullptr) + conn->SyncAttachDbs(); + } +} + //--------------------------------------------------------------------------------------- // @bsimethod //--------------------------------------------------------------------------------------- @@ -1321,6 +1399,7 @@ QueryMonitor::QueryMonitor(RunnableRequestQueue& queue, QueryExecutor& executor, return false; }, false); + m_executor.GetConnectionCache().SyncAttachDbs(); std::unique_lock lock(m_queryMonitorMutex); m_queryMonitorCv.wait_for(lock,m_pollInterval,[&]{ return m_stop.load() == true; }); m_queryMonitorCv.notify_all(); diff --git a/iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.h b/iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.h index 7873546cad..288767c4d6 100644 --- a/iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.h +++ b/iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.h @@ -155,6 +155,7 @@ struct CachedConnection final : std::enable_shared_from_this { public: CachedConnection(ConnectionCache& cache, uint16_t id):m_cache(cache), m_id(id), m_adaptorCache(*this),m_isChangeSummaryCacheAttached(false),m_retryHandler(QueryRetryHandler::Create(60s)){} ~CachedConnection(); + void SyncAttachDbs(); void Interrupt() const { m_db.Interrupt();} void Execute(std::function, std::unique_ptr); void Reset(bool detachDbs); @@ -179,7 +180,7 @@ struct ConnectionCache final { ECDb const& m_primaryDb; recursive_mutex_t m_mutex; uint32_t m_poolSize; - + uint64_t m_primaryAttachFileHash = 0; public: ConnectionCache(ECDb const& primaryDb, uint32_t pool_size); ECDb const& GetPrimaryDb() const { return m_primaryDb; } @@ -189,6 +190,7 @@ struct ConnectionCache final { void InterruptIf(std::function predicate, bool cancel); void SetCacheStatementsPerWork(uint32_t); void SetMaxPoolSize(uint32_t newSize) {m_poolSize = newSize; } + void SyncAttachDbs(); }; struct RunnableRequestQueue; diff --git a/iModelCore/ECDb/ECDb/ECDbImpl.cpp b/iModelCore/ECDb/ECDb/ECDbImpl.cpp index 655b1a9d49..bed0aeb8a5 100644 --- a/iModelCore/ECDb/ECDb/ECDbImpl.cpp +++ b/iModelCore/ECDb/ECDb/ECDbImpl.cpp @@ -263,23 +263,74 @@ void ECDb::Impl::RegisterECSqlPragmas() const GetPragmaManager().Register(PragmaExperimentalFeatures::Create()); GetPragmaManager().Register(PragmaParseTree::Create()); GetPragmaManager().Register(PragmaPurgeOrphanRelationships::Create()); + GetPragmaManager().Register(PragmaDbList::Create()); } //-------------------------------------------------------------------------------------- // @bsimethod //---------------+---------------+---------------+---------------+---------------+------ -DbResult ECDb::Impl::OnDbAttached(Utf8CP dbFileName, Utf8CP tableSpaceName) const - { - DbTableSpace tableSpace(tableSpaceName, dbFileName); - if (!DbTableSpace::IsAttachedECDbFile(m_ecdb, tableSpaceName)) - return BE_SQLITE_OK; //only need to react to attached ECDb files +DbResult ECDb::Impl::OnDbAttached(Utf8CP dbFileName, Utf8CP tableSpaceName) const { + auto tryGetProfileVersion = [&](ProfileVersion& ver) { + Statement stmt; + auto rc = stmt.Prepare(m_ecdb, SqlPrintfString("SELECT [StrData] FROM [%s].[be_Prop] WHERE [Namespace]='ec_Db' AND [Name] ='SchemaVersion'", tableSpaceName).GetUtf8CP()); + if (rc != BE_SQLITE_OK) { + return false; + } + if (BE_SQLITE_ROW != stmt.Step()) { + return false; + } - if (SUCCESS != m_schemaManager->GetDispatcher().AddManager(tableSpace)) - return BE_SQLITE_ERROR; + ver = ProfileVersion(0, 0, 0, 0); + if (!stmt.GetValueText(0)) + return false; - GetChangeManager().OnDbAttached(tableSpace, dbFileName); - return BE_SQLITE_OK; + if (BentleyStatus::SUCCESS != ver.FromJson(stmt.GetValueText(0))){ + return false; + } + return true; + }; + ProfileVersion attachDbProfileVer(0, 0, 0, 0); + if (!tryGetProfileVersion(attachDbProfileVer)) { + m_issueReporter.ReportV( + IssueSeverity::Error, + IssueCategory::BusinessProperties, + IssueType::ECSchema, + ECDbIssueId::ECDb_0735, + "Attached db '%s' will not be accessible via ECSQL as it does not support ECDb profile.", + tableSpaceName); + } + + const auto profileState = Db::CheckProfileVersion( + ECDb::CurrentECDbProfileVersion(), + attachDbProfileVer, + ECDb::MinimumUpgradableECDbProfileVersion(), + "ECDb" + ); + + const auto canOpen = (m_ecdb.IsReadonly() && (profileState.GetCanOpen() ==ProfileState::CanOpen::Readonly || profileState.GetCanOpen() == ProfileState::CanOpen::Readwrite)) || (!m_ecdb.IsReadonly() && profileState.GetCanOpen() ==ProfileState::CanOpen::Readwrite); + if (canOpen) { + DbTableSpace tableSpace(tableSpaceName, dbFileName); + if (!DbTableSpace::IsAttachedECDbFile(m_ecdb, tableSpaceName)) + return BE_SQLITE_OK; //only need to react to attached ECDb files + + if (SUCCESS != m_schemaManager->GetDispatcher().AddManager(tableSpace)) + return BE_SQLITE_ERROR; + + GetChangeManager().OnDbAttached(tableSpace, dbFileName); + } else { + + m_issueReporter.ReportV( + IssueSeverity::Error, + IssueCategory::BusinessProperties, + IssueType::ECSchema, + ECDbIssueId::ECDb_0736, + "Attached db with alias '%s' will not be accessible via ECSQL. Attach file EC profile version '%s' is incompatible with current runtime %s", + tableSpaceName, + attachDbProfileVer.ToString().c_str(), + ECDb::CurrentECDbProfileVersion().ToString().c_str()); } + return BE_SQLITE_OK; +} //-------------------------------------------------------------------------------------- // @bsimethod diff --git a/iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.cpp b/iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.cpp index 64d43ae775..4e92908160 100644 --- a/iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.cpp +++ b/iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.cpp @@ -789,7 +789,7 @@ DbResult PragmaPurgeOrphanRelationships::Read(PragmaManager::RowSet& rowSet, ECD const auto targetClassName = relCP->GetTarget().GetConstraintClasses().front()->GetECSqlName().c_str(); const auto ecSqlQuery = R"sql( - delete from %s where ECInstanceId in + delete from %s where ECInstanceId in (select r.ECInstanceId from %s r left join %s s on r.SourceECInstanceId = s.ECInstanceId where s.ECInstanceId is null union select r.ECInstanceId from %s r left join %s t on r.TargetECInstanceId = t.ECInstanceId where t.ECInstanceId is null) @@ -822,6 +822,42 @@ DbResult PragmaPurgeOrphanRelationships::Write(PragmaManager::RowSet& rowSet, EC return BE_SQLITE_ERROR; } +//======================================================================================= +// PragmaDbList +//======================================================================================= +//--------------------------------------------------------------------------------------- +// @bsimethod +//--------------------------------------------------------------------------------------- +DbResult PragmaDbList::Read(PragmaManager::RowSet& rowSet, ECDbCR ecdb, PragmaVal const& val, PragmaManager::OptionsMap const& options) { + auto result = std::make_unique(ecdb); + result->AppendProperty("sno", PRIMITIVETYPE_Integer); + result->AppendProperty("alias", PRIMITIVETYPE_String); + result->AppendProperty("fileName", PRIMITIVETYPE_String); + result->AppendProperty("profile", PRIMITIVETYPE_String); + result->FreezeSchemaChanges(); + const auto dbs = ecdb.GetAttachedDbs(); + int i = 0; + for (auto& db : dbs) { + auto row = result->AppendRow(); + row.appendValue() = i++; + row.appendValue() = db.m_alias; + row.appendValue() = db.m_fileName; + row.appendValue() = ecdb.Schemas().GetDispatcher().ExistsManager(db.m_alias) ? "ECDb" : "SQLite"; + } + + rowSet = std::move(result); + return BE_SQLITE_OK; +} + +//--------------------------------------------------------------------------------------- +// @bsimethod +//--------------------------------------------------------------------------------------- +DbResult PragmaDbList::Write(PragmaManager::RowSet& rowSet, ECDbCR ecdb, PragmaVal const&, PragmaManager::OptionsMap const& options) { + ecdb.GetImpl().Issues().ReportV(IssueSeverity::Error, IssueCategory::BusinessProperties, IssueType::ECSQL, ECDbIssueId::ECDb_0552, "PRAGMA %s is readonly.", GetName().c_str()); + rowSet = std::make_unique(ecdb); + rowSet->FreezeSchemaChanges(); + return BE_SQLITE_READONLY; +} END_BENTLEY_SQLITE_EC_NAMESPACE diff --git a/iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.h b/iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.h index 96e2461bc1..d751a77681 100644 --- a/iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.h +++ b/iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.h @@ -23,6 +23,17 @@ struct PragmaExplainQuery : PragmaManager::GlobalHandler { static std::unique_ptr Create () { return std::make_unique(); } }; +//======================================================================================= +// @bsiclass +//+===============+===============+===============+===============+===============+====== +struct PragmaDbList : PragmaManager::GlobalHandler { + PragmaDbList():GlobalHandler("db_list","List all attach dbs"){} + ~PragmaDbList(){} + virtual DbResult Read(PragmaManager::RowSet&, ECDbCR, PragmaVal const&, PragmaManager::OptionsMap const&) override; + virtual DbResult Write(PragmaManager::RowSet&, ECDbCR, PragmaVal const&, PragmaManager::OptionsMap const&) override; + static std::unique_ptr Create () { return std::make_unique(); } +}; + //======================================================================================= // @bsiclass //+===============+===============+===============+===============+===============+====== diff --git a/iModelCore/ECDb/ECDb/IssueReporter.cpp b/iModelCore/ECDb/ECDb/IssueReporter.cpp index 57933a93e0..4f07e0ac8c 100644 --- a/iModelCore/ECDb/ECDb/IssueReporter.cpp +++ b/iModelCore/ECDb/ECDb/IssueReporter.cpp @@ -731,6 +731,9 @@ IssueId ECDbIssueId::ECDb_0731 = IssueId("ECDb_0731"); IssueId ECDbIssueId::ECDb_0732 = IssueId("ECDb_0732"); IssueId ECDbIssueId::ECDb_0733 = IssueId("ECDb_0733"); IssueId ECDbIssueId::ECDb_0734 = IssueId("ECDb_0734"); +IssueId ECDbIssueId::ECDb_0735 = IssueId("ECDb_0735"); +IssueId ECDbIssueId::ECDb_0736 = IssueId("ECDb_0736"); + //--------------------------------------------------------------------------------------- // @bsimethod diff --git a/iModelCore/ECDb/ECDb/IssueReporter.h b/iModelCore/ECDb/ECDb/IssueReporter.h index 9295562e9f..cc49c14fef 100644 --- a/iModelCore/ECDb/ECDb/IssueReporter.h +++ b/iModelCore/ECDb/ECDb/IssueReporter.h @@ -752,6 +752,8 @@ struct ECDB_EXPORT ECDbIssueId static ECN::IssueId ECDb_0732; static ECN::IssueId ECDb_0733; static ECN::IssueId ECDb_0734; + static ECN::IssueId ECDb_0735; + static ECN::IssueId ECDb_0736; }; //--------------------------------------------------------------------------------------- diff --git a/iModelCore/ECDb/ECDb/SchemaManagerDispatcher.cpp b/iModelCore/ECDb/ECDb/SchemaManagerDispatcher.cpp index 042ff637e9..6e0304c9e4 100644 --- a/iModelCore/ECDb/ECDb/SchemaManagerDispatcher.cpp +++ b/iModelCore/ECDb/ECDb/SchemaManagerDispatcher.cpp @@ -252,6 +252,16 @@ SchemaManager::Dispatcher::Iterable SchemaManager::Dispatcher::GetIterable(Utf8C return Iterable(*manager); } + +//--------------------------------------------------------------------------------------- +//@bsimethod +//+---------------+---------------+---------------+---------------+---------------+------ +bool SchemaManager::Dispatcher::ExistsManager(Utf8StringCR tableSpace) const + { + BeMutexHolder lock(m_mutex); + return m_managers.find(tableSpace) != m_managers.end(); + } + //--------------------------------------------------------------------------------------- //@bsimethod //+---------------+---------------+---------------+---------------+---------------+------ diff --git a/iModelCore/ECDb/ECDb/SchemaManagerDispatcher.h b/iModelCore/ECDb/ECDb/SchemaManagerDispatcher.h index 7494bc1949..2951cab8a3 100644 --- a/iModelCore/ECDb/ECDb/SchemaManagerDispatcher.h +++ b/iModelCore/ECDb/ECDb/SchemaManagerDispatcher.h @@ -331,6 +331,7 @@ struct SchemaManager::Dispatcher final MainSchemaManager const& Main() const { BeAssert(m_main != nullptr); return *m_main; } BentleyStatus AddManager(DbTableSpace const&) const; BentleyStatus RemoveManager(DbTableSpace const&) const; + bool ExistsManager(Utf8StringCR tableSpace) const; bool OwnsSchema(ECN::ECSchemaCR schema) const; bvector GetSchemas(bool loadSchemaEntities, Utf8CP tableSpace) const; ECN::ECSchemaPtr LocateSchema(ECN::SchemaKeyR, ECN::SchemaMatchType, ECN::ECSchemaReadContextR, Utf8CP tableSpace) const; diff --git a/iModelJsNodeAddon/IModelJsNative.cpp b/iModelJsNodeAddon/IModelJsNative.cpp index 90ad6bc7ca..9352e62250 100644 --- a/iModelJsNodeAddon/IModelJsNative.cpp +++ b/iModelJsNodeAddon/IModelJsNative.cpp @@ -287,7 +287,7 @@ template struct SQLiteOps { if (!v.IsObject()) { BeNapi::ThrowJsException(info.Env(), "font data not valid"); } - + FontFace face(v); faces.push_back(face); } @@ -295,7 +295,7 @@ template struct SQLiteOps { if (faces.empty()) { BeNapi::ThrowJsException(info.Env(), "font data not valid"); } - + auto db = &GetOpenedDb(info); auto dgnDb = dynamic_cast(db); std::unique_ptr fontDbHolder; @@ -312,7 +312,7 @@ template struct SQLiteOps { BeNapi::ThrowJsException(info.Env(), "unable to embed font"); } } - + Napi::Value IsOpen(NapiInfoCR info) { auto db = _GetMyDb(); return Napi::Boolean::New(info.Env(), nullptr != db && db->IsDbOpen()); @@ -428,7 +428,21 @@ struct NativeECDb : BeObjectWrap, SQLiteOps { return Napi::Number::New(Env(), (int)status); } - + void AttachDb(NapiInfoCR info) { + REQUIRE_ARGUMENT_STRING(0, fileName); + REQUIRE_ARGUMENT_STRING(1, alias); + auto rc = GetOpenedDb(info).AttachDb(fileName.c_str(), alias.c_str()); + if (rc != BE_SQLITE_OK) { + BeNapi::ThrowJsException(info.Env(), "Failed to attach file", (int)rc); + } + } + void DetachDb(NapiInfoCR info) { + REQUIRE_ARGUMENT_STRING(0, alias); + auto rc = GetOpenedDb(info).DetachDb(alias.c_str()); + if (rc != BE_SQLITE_OK) { + BeNapi::ThrowJsException(info.Env(), "Failed to detach file", (int)rc); + } + } void ConcurrentQueryExecute(NapiInfoCR info) { REQUIRE_ARGUMENT_ANY_OBJ(0, requestObj); REQUIRE_ARGUMENT_FUNCTION(1, callback); @@ -600,6 +614,8 @@ struct NativeECDb : BeObjectWrap, SQLiteOps { Napi::HandleScope scope(env); Napi::Function t = DefineClass(env, "ECDb", { InstanceMethod("abandonChanges", &NativeECDb::AbandonChanges), + InstanceMethod("attachDb", &NativeECDb::AttachDb), + InstanceMethod("detachDb", &NativeECDb::DetachDb), InstanceMethod("closeDb", &NativeECDb::CloseDb), InstanceMethod("concurrentQueryExecute", &NativeECDb::ConcurrentQueryExecute), InstanceMethod("concurrentQueryResetConfig", &NativeECDb::ConcurrentQueryResetConfig), @@ -1501,7 +1517,7 @@ struct NativeDgnDb : BeObjectWrap, SQLiteOps BeMutexHolder lock(FontManager::GetMutex()); db.Fonts().Invalidate(); } - + Napi::Value WriteFullElementDependencyGraphToFile(NapiInfoCR info) { auto& db = GetOpenedDb(info); @@ -2589,6 +2605,21 @@ struct NativeDgnDb : BeObjectWrap, SQLiteOps } db.Txns().RevertTimelineChanges(changesets, skipSchemaChanges); } + void AttachDb(NapiInfoCR info) { + REQUIRE_ARGUMENT_STRING(0, fileName); + REQUIRE_ARGUMENT_STRING(1, alias); + auto rc = GetOpenedDb(info).AttachDb(fileName.c_str(), alias.c_str()); + if (rc != BE_SQLITE_OK) { + BeNapi::ThrowJsException(info.Env(), "Failed to attach file", (int)rc); + } + } + void DetachDb(NapiInfoCR info) { + REQUIRE_ARGUMENT_STRING(0, alias); + auto rc = GetOpenedDb(info).DetachDb(alias.c_str()); + if (rc != BE_SQLITE_OK) { + BeNapi::ThrowJsException(info.Env(), "Failed to detach file", (int)rc); + } + } void ConcurrentQueryExecute(NapiInfoCR info) { REQUIRE_ARGUMENT_ANY_OBJ(0, requestObj); REQUIRE_ARGUMENT_FUNCTION(1, callback); @@ -2688,6 +2719,8 @@ struct NativeDgnDb : BeObjectWrap, SQLiteOps Napi::HandleScope scope(env); Napi::Function t = DefineClass(env, "DgnDb", { InstanceMethod("abandonChanges", &NativeDgnDb::AbandonChanges), + InstanceMethod("attachDb", &NativeDgnDb::AttachDb), + InstanceMethod("detachDb", &NativeDgnDb::DetachDb), InstanceMethod("abandonCreateChangeset", &NativeDgnDb::AbandonCreateChangeset), InstanceMethod("addChildPropagatesChangesToParentRelationship", &NativeDgnDb::AddChildPropagatesChangesToParentRelationship), InstanceMethod("invalidateFontMap", &NativeDgnDb::InvalidateFontMap), diff --git a/iModelJsNodeAddon/api_package/ts/src/NativeLibrary.ts b/iModelJsNodeAddon/api_package/ts/src/NativeLibrary.ts index c0f78d06b9..b12103af50 100644 --- a/iModelJsNodeAddon/api_package/ts/src/NativeLibrary.ts +++ b/iModelJsNodeAddon/api_package/ts/src/NativeLibrary.ts @@ -521,6 +521,8 @@ export declare namespace IModelJsNative { class DgnDb implements IConcurrentQueryManager, SQLiteOps { constructor(); public readonly cloudContainer?: CloudContainer; + public attachDb(filename: string, alias: string): void; + public detachDb(alias: string): void; public getNoCaseCollation(): NoCaseCollation; public setNoCaseCollation(collation: NoCaseCollation): void; public schemaSyncSetDefaultUri(syncDbUri: string): void; @@ -768,6 +770,9 @@ export declare namespace IModelJsNative { public concurrentQueryExecute(request: DbRequest, onResponse: ConcurrentQuery.OnResponse): void; public concurrentQueryResetConfig(config?: QueryConfig): QueryConfig; public concurrentQueryShutdown(): void; + public attachDb(filename: string, alias: string): void; + public detachDb(alias: string): void; + } class ChangedElementsECDb implements IDisposable {