diff --git a/src/ProjectFileIO.cpp b/src/ProjectFileIO.cpp index f4c2bcdc3..0e3f6a237 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -54,9 +54,9 @@ static const char *ProjectFileSchema = // // See the CMakeList.txt for the SQLite lib for more // settings. - "PRAGMA .application_id = %d;" - "PRAGMA .user_version = %d;" - "PRAGMA .journal_mode = WAL;" + "PRAGMA .application_id = %d;" + "PRAGMA .user_version = %d;" + "PRAGMA .journal_mode = WAL;" "" // project is a binary representation of an XML file. // it's in binary for speed. @@ -70,7 +70,7 @@ static const char *ProjectFileSchema = // There is no limit to document blob size. // dict will be smallish, with an entry for each // kind of field. - "CREATE TABLE IF NOT EXISTS .project" + "CREATE TABLE IF NOT EXISTS .project" "(" " id INTEGER PRIMARY KEY," " dict BLOB," @@ -90,7 +90,7 @@ static const char *ProjectFileSchema = // There is no limit to document blob size. // dict will be smallish, with an entry for each // kind of field. - "CREATE TABLE IF NOT EXISTS .autosave" + "CREATE TABLE IF NOT EXISTS .autosave" "(" " id INTEGER PRIMARY KEY," " dict BLOB," @@ -99,7 +99,7 @@ static const char *ProjectFileSchema = "" // CREATE SQL tags // tags is not used (yet) - "CREATE TABLE IF NOT EXISTS .tags" + "CREATE TABLE IF NOT EXISTS .tags" "(" " name TEXT," " value BLOB" @@ -116,7 +116,7 @@ static const char *ProjectFileSchema = // blockID is a 64 bit number. // // summin to summary64K are summaries at 3 distance scales. - "CREATE TABLE IF NOT EXISTS .sampleblocks" + "CREATE TABLE IF NOT EXISTS .sampleblocks" "(" " blockid INTEGER PRIMARY KEY AUTOINCREMENT," " sampleformat INTEGER," @@ -128,6 +128,12 @@ static const char *ProjectFileSchema = " samples BLOB" ");"; +// Settings applied to each database connection +static const char *ConnectionConfiguration = + "PRAGMA .synchronous = OFF;" + "PRAGMA .locking_mode = EXCLUSIVE;" + "PRAGMA .wal_autocheckpoint = 1000;"; + // This singleton handles initialization/shutdown of the SQLite library. // It is needed because our local SQLite is built with SQLITE_OMIT_AUTOINIT // defined. @@ -145,7 +151,7 @@ public: if (mRc == SQLITE_OK) { // Use the "unix-excl" VFS to make access to the DB exclusive. This gets - // rid of the "-shm" shared memory file. + // rid of the "-shm" shared memory file. // // Though it shouldn't, it doesn't matter if this fails. auto vfs = sqlite3_vfs_find("unix-excl"); @@ -352,6 +358,31 @@ void ProjectFileIO::UseConnection( sqlite3 *db, const FilePath &filePath ) SetFileName( filePath ); } +void ProjectFileIO::ConfigConnection(sqlite3 *db, const wxString &schema) +{ + int rc; + + wxString sql = ConnectionConfiguration; + + if (schema.empty()) + { + sql.Replace(wxT("."), wxT("")); + } + else + { + sql.Replace(wxT(""), schema); + } + + rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr); + if (rc != SQLITE_OK) + { + // This non-fatal...for now + SetDBError(XO("Failed to set connection configuration")); + } + + return; +} + sqlite3 *ProjectFileIO::OpenDB(FilePath fileName) { wxASSERT(mDB == nullptr); @@ -381,6 +412,9 @@ sqlite3 *ProjectFileIO::OpenDB(FilePath fileName) return nullptr; } + // Ensure attached DB connection gets configured + ConfigConnection(mDB); + if (!CheckVersion()) { CloseDB(); @@ -674,13 +708,13 @@ bool ProjectFileIO::CheckVersion() return true; } -bool ProjectFileIO::InstallSchema(sqlite3 *db, const char *dbname /* = "main" */) +bool ProjectFileIO::InstallSchema(sqlite3 *db, const char *schema /* = "main" */) { int rc; wxString sql; sql.Printf(ProjectFileSchema, ProjectFileID, ProjectFileVersion); - sql.Replace("", dbname); + sql.Replace("", schema); rc = sqlite3_exec(db, sql.mb_str().data(), nullptr, nullptr, nullptr); if (rc != SQLITE_OK) @@ -751,13 +785,6 @@ bool ProjectFileIO::CheckForOrphans(BlockIDs &blockids) return true; } -/* static */ -void ProjectFileIO::UpdateCallback(void *data, int operation, char const *dbname, char const *table, long long rowid) -{ - UpdateCB cb = *static_cast(data); - cb(operation, dbname, table, rowid); -} - sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, const TranslatableString &msg, bool prune /* = false */) @@ -816,20 +843,19 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, // Cleanup in case things go awry auto cleanup = finally([&] { - // Detach the destination database, whether it was successfully attached or not - sqlite3_exec(db, "DETACH DATABASE dest;", nullptr, nullptr, nullptr); - if (!success) { sqlite3_close(destdb); + sqlite3_exec(db, "DETACH DATABASE outbound;", nullptr, nullptr, nullptr); + wxRemoveFile(destpath); } }); // Attach the destination database wxString sql; - sql.Printf("ATTACH DATABASE '%s' AS dest;", destpath); + sql.Printf("ATTACH DATABASE '%s' AS outbound;", destpath); rc = sqlite3_exec(db, sql.mb_str().data(), nullptr, nullptr, nullptr); if (rc != SQLITE_OK) @@ -840,15 +866,18 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, return nullptr; } + // Ensure attached DB connection gets configured + ConfigConnection(db, "outbound"); + // Install our schema into the new database - if (!InstallSchema(db, "dest")) + if (!InstallSchema(db, "outbound")) { // Message already set return nullptr; } rc = sqlite3_exec(db, - "INSERT INTO dest.tags SELECT * FROM main.tags;", + "INSERT INTO outbound.tags SELECT * FROM main.tags;", nullptr, nullptr, nullptr); @@ -862,6 +891,30 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, } { + // Ensure statement gets cleaned up + sqlite3_stmt *stmt = nullptr; + auto cleanup = finally([&] + { + if (stmt) + { + sqlite3_finalize(stmt); + } + }); + + // Prepare the statement only once + rc = sqlite3_prepare_v2(db, + "INSERT INTO outbound.sampleblocks" + " SELECT * FROM main.sampleblocks" + " WHERE blockid = ?;", + -1, &stmt, 0); + if (rc != SQLITE_OK) + { + SetDBError( + XO("Unable to prepare project file command:\n\n%s").Format(sql) + ); + return nullptr; + } + /* i18n-hint: This title appears on a dialog that indicates the progress in doing something.*/ ProgressDialog progress(XO("Progress"), msg, pdlgHideStopButton); @@ -872,32 +925,28 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, for (auto blockid : blockids) { - wxString sql; - sql.Printf("INSERT INTO dest.sampleblocks" - " SELECT * FROM main.sampleblocks" - " WHERE blockid = %lld", - blockid); + // BIND blockid parameter + if (sqlite3_bind_int64(stmt, 1, blockid) != SQLITE_OK) + { + THROW_INCONSISTENCY_EXCEPTION; + } - rc = sqlite3_exec(db, sql.mb_str().data(), nullptr, nullptr, nullptr); - if (rc != SQLITE_OK) + // Process it + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { SetDBError( - XO("Failed to copy project file") + XO("Failed to update the project file.\nThe following command failed:\n\n%s").Format(sql) ); - return nullptr; } -/* - rc = sqlite3_wal_checkpoint_v2(db, "dest", SQLITE_CHECKPOINT_FULL, nullptr, nullptr); - if (rc != SQLITE_OK) + + // BIND blockid parameter + if (sqlite3_reset(stmt) != SQLITE_OK) { - SetDBError( - XO("Failed to copy project file") - ); - - return nullptr; + THROW_INCONSISTENCY_EXCEPTION; } -*/ + result = progress.Update(++count, total); if (result != ProgressResult::Success) { @@ -909,7 +958,7 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, } // Copy failed - if (rc != SQLITE_OK) + if (rc != SQLITE_DONE) { SetDBError( XO("Failed to copy project file") @@ -918,6 +967,9 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, return nullptr; } + // Detach the destination database + sqlite3_exec(db, "DETACH DATABASE outbound;", nullptr, nullptr, nullptr); + // Open the newly created database rc = sqlite3_open(destpath, &destdb); if (rc != SQLITE_OK) @@ -929,6 +981,9 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, return nullptr; } + // Ensure connection gets configured + ConfigConnection(destdb); + // Tell cleanup everything is good to go success = true; @@ -1038,6 +1093,9 @@ bool ProjectFileIO::Vacuum() return false; } + // Ensure connection gets configured + ConfigConnection(mDB); + // Copy the original database to a new database while pruning unused sample blocks auto newDB = CopyTo(origName, XO("Compacting project"), true); @@ -1495,7 +1553,7 @@ bool ProjectFileIO::ImportProject(const FilePath &fileName) { if (indb) { - sqlite3_close(indb); + DiscardConnection(); } RestoreConnection(); @@ -1514,6 +1572,9 @@ bool ProjectFileIO::ImportProject(const FilePath &fileName) return false; } + // Ensure connection gets configured + ConfigConnection(indb); + // The inbound project file becomes the active project file UseConnection(indb, fileName); @@ -1635,11 +1696,18 @@ bool ProjectFileIO::ImportProject(const FilePath &fileName) // Get access to the current project file auto db = DB(); - // Make sure the inbound project file gets detached + // Cleanup... + sqlite3_stmt *stmt = nullptr; auto cleanup = finally([&] { + // Ensure the prepared statement gets cleaned up + if (stmt) + { + sqlite3_finalize(stmt); + } + // Detach the inbound project file, whether it was successfully attached or not - sqlite3_exec(db, "DETACH DATABASE dest;", nullptr, nullptr, nullptr); + sqlite3_exec(db, "DETACH DATABASE inbound;", nullptr, nullptr, nullptr); }); // Attach the inbound project file @@ -1656,9 +1724,31 @@ bool ProjectFileIO::ImportProject(const FilePath &fileName) return false; } + // Ensure attached DB connection gets configured + ConfigConnection(db, "inbound"); + + // Prepare the statement to copy the sample block from the inbound project to the + // active project. All columns other than the blockid column gets copied. + wxString columns(wxT("sampleformat, summin, summax, sumrms, summary256, summary64k, samples")); + sql.Printf("INSERT INTO main.sampleblocks (%s)" + " SELECT %s" + " FROM inbound.sampleblocks" + " WHERE blockid = ?;", + columns, + columns); + + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + if (rc != SQLITE_OK) + { + SetDBError( + XO("Unable to prepare project file command:\n\n%s").Format(sql) + ); + return false; + } + /* i18n-hint: This title appears on a dialog that indicates the progress in doing something.*/ - ProgressDialog progress(XO("Progress"), XO("Importing project")); + ProgressDialog progress(XO("Progress"), XO("Importing project"), pdlgHideStopButton); ProgressResult result = ProgressResult::Success; wxLongLong_t count = 0; @@ -1705,32 +1795,31 @@ bool ProjectFileIO::ImportProject(const FilePath &fileName) SampleBlockID blockid; attr->GetValue().ToLongLong(&blockid); - // Copy the sample block from the inbound project to the active project. - // All columns other than the blockid column gets copied. - wxString columns(wxT("sampleformat, summin, summax, sumrms, summary256, summary64k, samples")); - wxString sql; - sql.Printf("INSERT INTO main.sampleblocks (%s)" - " SELECT %s" - " FROM inbound.sampleblocks" - " WHERE blockid = %lld", - columns, - columns, - blockid); - rc = sqlite3_exec(db, sql.mb_str().data(), nullptr, nullptr, nullptr); - if (rc != SQLITE_OK) + // BIND blockid parameter + if (sqlite3_bind_int64(stmt, 1, blockid) != SQLITE_OK) + { + THROW_INCONSISTENCY_EXCEPTION; + } + + // Process it + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { SetDBError( - XO("Failed to import sample block") + XO("Failed to import sample block.\nThe following command failed:\n\n%s").Format(sql) ); - - result = ProgressResult::Failed; - - break; + return false; } // Replace the original blockid with the new one attr->SetValue(wxString::Format(wxT("%lld"), sqlite3_last_insert_rowid(db))); + // BIND blockid parameter + if (sqlite3_reset(stmt) != SQLITE_OK) + { + THROW_INCONSISTENCY_EXCEPTION; + } + // Remember that we copied this node in case the user cancels result = progress.Update(++count, total); if (result != ProgressResult::Success) diff --git a/src/ProjectFileIO.h b/src/ProjectFileIO.h index 82495ac67..3d6948663 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -138,7 +138,10 @@ private: void RestoreConnection(); // Use a connection that is already open rather than invoke OpenDB - void UseConnection( sqlite3 *db, const FilePath &filePath ); + void UseConnection(sqlite3 *db, const FilePath &filePath); + + // Make sure the connection/schema combo is configured the way we want + void ConfigConnection(sqlite3 *db, const wxString &schema = wxT("main")); sqlite3 *OpenDB(FilePath fileName = {}); bool CloseDB(); @@ -153,7 +156,7 @@ private: bool GetBlob(const char *sql, wxMemoryBuffer &buffer); bool CheckVersion(); - bool InstallSchema(sqlite3 *db, const char *dbname = "main"); + bool InstallSchema(sqlite3 *db, const char *schema = "main"); bool UpgradeSchema(); // Write project or autosave XML (binary) documents @@ -174,9 +177,6 @@ private: void SetError(const TranslatableString & msg); void SetDBError(const TranslatableString & msg); - using UpdateCB = std::function; - static void UpdateCallback(void *data, int operation, char const *dbname, char const *table, long long rowid); - unsigned long long CalculateUsage(); private: