Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow attach/detach db to IModelDb connection #956

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions iModelCore/BeSQLite/BeSQLite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2774,6 +2774,36 @@ DbResult Db::DetachDb(Utf8CP alias) const
return rc;
}

/*---------------------------------------------------------------------------------**//**
*
+---------------+---------------+---------------+---------------+---------------+------*/
std::vector<AttachFileInfo> Db::GetAttachedDbs() const {
if (!IsDbOpen())
return {};

std::vector<AttachFileInfo> 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;
}

/*---------------------------------------------------------------------------------**//**
*
+---------------+---------------+---------------+---------------+---------------+------*/
Expand Down
16 changes: 16 additions & 0 deletions iModelCore/BeSQLite/PublicAPI/BeSQLite/BeSQLite.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<AttachFileInfo> 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,
Expand Down
103 changes: 91 additions & 12 deletions iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,20 +107,71 @@ ECSqlRowAdaptor& CachedQueryAdaptor::GetJsonAdaptor() {
//---------------------------------------------------------------------------------------
// @bsimethod
//---------------------------------------------------------------------------------------
void CachedConnection::Execute(std::function<void(QueryAdaptorCache&,RunnableRequestBase&)> cb, std::unique_ptr<RunnableRequestBase> 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<void(QueryAdaptorCache&,RunnableRequestBase&)> cb, std::unique_ptr<RunnableRequestBase> request) {
SyncAttachDbs();
SetRequest(std::move(request));
cb(m_adaptorCache, *m_request);
ClearRequest();
Expand Down Expand Up @@ -232,6 +283,33 @@ std::shared_ptr<CachedConnection> 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
//---------------------------------------------------------------------------------------
Expand Down Expand Up @@ -1321,6 +1399,7 @@ QueryMonitor::QueryMonitor(RunnableRequestQueue& queue, QueryExecutor& executor,
return false;
}, false);

m_executor.GetConnectionCache().SyncAttachDbs();
std::unique_lock<std::mutex> lock(m_queryMonitorMutex);
m_queryMonitorCv.wait_for(lock,m_pollInterval,[&]{ return m_stop.load() == true; });
m_queryMonitorCv.notify_all();
Expand Down
4 changes: 3 additions & 1 deletion iModelCore/ECDb/ECDb/ConcurrentQueryManagerImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ struct CachedConnection final : std::enable_shared_from_this<CachedConnection> {
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<void(QueryAdaptorCache&,RunnableRequestBase&)>, std::unique_ptr<RunnableRequestBase>);
void Reset(bool detachDbs);
Expand All @@ -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; }
Expand All @@ -189,6 +190,7 @@ struct ConnectionCache final {
void InterruptIf(std::function<bool(RunnableRequestBase const&)> predicate, bool cancel);
void SetCacheStatementsPerWork(uint32_t);
void SetMaxPoolSize(uint32_t newSize) {m_poolSize = newSize; }
void SyncAttachDbs();
};

struct RunnableRequestQueue;
Expand Down
69 changes: 60 additions & 9 deletions iModelCore/ECDb/ECDb/ECDbImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 37 additions & 1 deletion iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<StaticPragmaResult>(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<StaticPragmaResult>(ecdb);
rowSet->FreezeSchemaChanges();
return BE_SQLITE_READONLY;
}

END_BENTLEY_SQLITE_EC_NAMESPACE

11 changes: 11 additions & 0 deletions iModelCore/ECDb/ECDb/ECSql/ECSqlPragmas.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ struct PragmaExplainQuery : PragmaManager::GlobalHandler {
static std::unique_ptr<PragmaManager::Handler> Create () { return std::make_unique<PragmaExplainQuery>(); }
};

//=======================================================================================
// @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<PragmaManager::Handler> Create () { return std::make_unique<PragmaDbList>(); }
};

//=======================================================================================
// @bsiclass
//+===============+===============+===============+===============+===============+======
Expand Down
3 changes: 3 additions & 0 deletions iModelCore/ECDb/ECDb/IssueReporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions iModelCore/ECDb/ECDb/IssueReporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

//---------------------------------------------------------------------------------------
Expand Down
Loading
Loading