diff --git a/src/ProjectFileIO.cpp b/src/ProjectFileIO.cpp index 5aaede104..c268bbb0c 100644 --- a/src/ProjectFileIO.cpp +++ b/src/ProjectFileIO.cpp @@ -492,13 +492,35 @@ sqlite3 *ProjectFileIO::OpenDB(FilePath fileName) return nullptr; } - mTemporary = temp; + Prepare(GetSamples, + "SELECT samples FROM sampleblocks WHERE blockid = ?1;"); - SetFileName(fileName); + Prepare(GetSummary256, + "SELECT summary256 FROM sampleblocks WHERE blockid = ?1;"); + + Prepare(GetSummary64k, + "SELECT summary64k FROM sampleblocks WHERE blockid = ?1;"); + + Prepare(LoadSampleBlock, + "SELECT sampleformat, summin, summax, sumrms," + " length('summary256'), length('summary64k'), length('samples')" + " FROM sampleblocks WHERE blockid = ?1;"); + + Prepare(InsertSampleBlock, + "INSERT INTO sampleblocks (sampleformat, summin, summax, sumrms," + " summary256, summary64k, samples)" + " VALUES(?1,?2,?3,?4,?5,?6,?7);"); + + Prepare(DeleteSampleBlock, + "DELETE FROM sampleblocks WHERE blockid = ?1;"); // Install our checkpoint hook sqlite3_wal_hook(mDB, CheckpointHook, this); + mTemporary = temp; + + SetFileName(fileName); + return mDB; } @@ -537,6 +559,13 @@ bool ProjectFileIO::CloseDB() } } + // We're done with the prepared statements + for (auto stmt : mStatements) + { + sqlite3_finalize(stmt.second); + } + mStatements.clear(); + // Close the DB rc = sqlite3_close(mDB); if (rc != SQLITE_OK) @@ -551,25 +580,28 @@ bool ProjectFileIO::CloseDB() return true; } -bool ProjectFileIO::DeleteDB() +void ProjectFileIO::Prepare(enum StatementID id, const char *sql) { - wxASSERT(mDB == nullptr); + int rc; - if (mTemporary && !mFileName.empty()) + sqlite3_stmt *stmt = nullptr; + + rc = sqlite3_prepare_v3(mDB, sql, -1, SQLITE_PREPARE_PERSISTENT, &stmt, 0); + if (rc != SQLITE_OK) { - wxFileName temp(FileNames::TempDir()); - if (temp == wxPathOnly(mFileName)) - { - if (!wxRemoveFile(mFileName)) - { - SetError(XO("Failed to close the project file")); - - return false; - } - } + THROW_INCONSISTENCY_EXCEPTION; } - return true; + mStatements.insert({id, stmt}); +} + +sqlite3_stmt *ProjectFileIO::GetStatement(enum StatementID id) +{ + auto iter = mStatements.find(id); + + wxASSERT(iter != mStatements.end()); + + return iter->second; } bool ProjectFileIO::TransactionStart(const wxString &name) @@ -1036,10 +1068,10 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, // Copy sample blocks from the main DB to the outbound DB for (auto blockid : blockids) { - // BIND blockid parameter + // Bind statement parameters if (sqlite3_bind_int64(stmt, 1, blockid) != SQLITE_OK) { - THROW_INCONSISTENCY_EXCEPTION; + wxASSERT_MSG(false, wxT("Binding failed...bug!!!")); } // Process it @@ -1052,7 +1084,7 @@ sqlite3 *ProjectFileIO::CopyTo(const FilePath &destpath, return nullptr; } - // BIND blockid parameter + // Reset statement to beginning if (sqlite3_reset(stmt) != SQLITE_OK) { THROW_INCONSISTENCY_EXCEPTION; @@ -1680,15 +1712,13 @@ bool ProjectFileIO::WriteDoc(const char *table, const wxMemoryBuffer &dict = autosave.GetDict(); const wxMemoryBuffer &data = autosave.GetData(); - // BIND SQL autosave + // Bind statement parameters // Might return SQL_MISUSE which means it's our mistake that we violated // preconditions; should return SQL_OK which is 0 - if ( - sqlite3_bind_blob(stmt, 1, dict.GetData(), dict.GetDataLen(), SQLITE_STATIC) || - sqlite3_bind_blob(stmt, 2, data.GetData(), data.GetDataLen(), SQLITE_STATIC) - ) + if (sqlite3_bind_blob(stmt, 1, dict.GetData(), dict.GetDataLen(), SQLITE_STATIC) || + sqlite3_bind_blob(stmt, 2, data.GetData(), data.GetDataLen(), SQLITE_STATIC)) { - THROW_INCONSISTENCY_EXCEPTION; + wxASSERT_MSG(false, wxT("Binding failed...bug!!!")); } rc = sqlite3_step(stmt); @@ -1938,10 +1968,12 @@ bool ProjectFileIO::ImportProject(const FilePath &fileName) SampleBlockID blockid; attr->GetValue().ToLongLong(&blockid); - // BIND blockid parameter + // Bind statement parameters + // Might return SQL_MISUSE which means it's our mistake that we violated + // preconditions; should return SQL_OK which is 0 if (sqlite3_bind_int64(stmt, 1, blockid) != SQLITE_OK) { - THROW_INCONSISTENCY_EXCEPTION; + wxASSERT_MSG(false, wxT("Binding failed...bug!!!")); } // Process it @@ -2412,6 +2444,6 @@ bool AutoCommitTransaction::Rollback() wxASSERT(mInTrans); mInTrans = !mIO.TransactionCommit(mName); - + return mInTrans; } diff --git a/src/ProjectFileIO.h b/src/ProjectFileIO.h index 83f5e34cb..4af4a1a96 100644 --- a/src/ProjectFileIO.h +++ b/src/ProjectFileIO.h @@ -24,6 +24,7 @@ Paul Licameli split from AudacityProject.h struct sqlite3; struct sqlite3_context; +struct sqlite3_stmt; struct sqlite3_value; class AudacityProject; @@ -159,7 +160,18 @@ private: sqlite3 *OpenDB(FilePath fileName = {}); bool CloseDB(); - bool DeleteDB(); + + enum StatementID + { + GetSamples, + GetSummary256, + GetSummary64k, + LoadSampleBlock, + InsertSampleBlock, + DeleteSampleBlock + }; + void Prepare(enum StatementID id, const char *sql); + sqlite3_stmt *GetStatement(enum StatementID id); bool Query(const char *sql, const ExecCB &callback); @@ -233,6 +245,8 @@ private: std::atomic< std::uint64_t > mCheckpointWaitingPages{ 0 }; std::atomic< std::uint64_t > mCheckpointCurrentPages{ 0 }; + std::map mStatements; + friend SqliteSampleBlock; friend AutoCommitTransaction; }; diff --git a/src/SqliteSampleBlock.cpp b/src/SqliteSampleBlock.cpp index c4752bbf0..8124287e1 100644 --- a/src/SqliteSampleBlock.cpp +++ b/src/SqliteSampleBlock.cpp @@ -64,11 +64,11 @@ private: bool GetSummary(float *dest, size_t frameoffset, size_t numframes, - const char *srccolumn, + ProjectFileIO::StatementID id, size_t srcbytes); size_t GetBlob(void *dest, sampleFormat destformat, - const char *srccolumn, + ProjectFileIO::StatementID id, sampleFormat srcformat, size_t srcoffset, size_t srcbytes); @@ -98,9 +98,6 @@ private: double mSumMax; double mSumRms; - const char *columns = - "sampleformat, summin, summax, sumrms, summary256, summary64k, samples"; - #if defined(WORDS_BIGENDIAN) #error All sample block data is little endian...big endian not yet supported #endif @@ -291,7 +288,7 @@ size_t SqliteSampleBlock::DoGetSamples(samplePtr dest, { return GetBlob(dest, destformat, - "samples", + ProjectFileIO::GetSamples, mSampleFormat, sampleoffset * SAMPLE_SIZE(mSampleFormat), numsamples * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat); @@ -333,25 +330,33 @@ bool SqliteSampleBlock::GetSummary256(float *dest, size_t frameoffset, size_t numframes) { - return GetSummary(dest, frameoffset, numframes, "summary256", mSummary256Bytes); + return GetSummary(dest, + frameoffset, + numframes, + ProjectFileIO::GetSummary256, + mSummary256Bytes); } bool SqliteSampleBlock::GetSummary64k(float *dest, size_t frameoffset, size_t numframes) { - return GetSummary(dest, frameoffset, numframes, "summary64k", mSummary64kBytes); + return GetSummary(dest, + frameoffset, + numframes, + ProjectFileIO::GetSummary64k, + mSummary256Bytes); } bool SqliteSampleBlock::GetSummary(float *dest, size_t frameoffset, size_t numframes, - const char *srccolumn, + ProjectFileIO::StatementID id, size_t srcbytes) { return GetBlob(dest, floatSample, - srccolumn, + id, floatSample, frameoffset * 3 * SAMPLE_SIZE(floatSample), numframes * 3 * SAMPLE_SIZE(floatSample)) / 3 / SAMPLE_SIZE(floatSample); @@ -398,7 +403,7 @@ MinMaxRMS SqliteSampleBlock::DoGetMinMaxRMS(size_t start, size_t len) size_t copied = GetBlob(samples, floatSample, - "samples", + ProjectFileIO::GetSamples, mSampleFormat, start * SAMPLE_SIZE(mSampleFormat), len * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat); @@ -439,7 +444,7 @@ size_t SqliteSampleBlock::GetSpaceUsage() const size_t SqliteSampleBlock::GetBlob(void *dest, sampleFormat destformat, - const char *srccolumn, + ProjectFileIO::StatementID id, sampleFormat srcformat, size_t srcoffset, size_t srcbytes) @@ -456,77 +461,71 @@ size_t SqliteSampleBlock::GetBlob(void *dest, int rc; size_t minbytes = 0; - char sql[256]; - sqlite3_snprintf(sizeof(sql), - sql, - "SELECT %s FROM sampleblocks WHERE blockid = %lld;", - srccolumn, - mBlockID); + // Retrieve prepared statement + sqlite3_stmt *stmt = mIO.GetStatement(id); - sqlite3_stmt *stmt = nullptr; - auto cleanup = finally([&] + // Bind statement paraemters + // Might return SQLITE_MISUSE which means it's our mistake that we violated + // preconditions; should return SQL_OK which is 0 + if (sqlite3_bind_int64(stmt, 1, mBlockID)) { - if (stmt) - { - sqlite3_finalize(stmt); - } - }); - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); - if (rc != SQLITE_OK) - { - wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); - } - else - { - rc = sqlite3_step(stmt); - if (rc != SQLITE_ROW) - { - wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); - } - else - { - samplePtr src = (samplePtr) sqlite3_column_blob(stmt, 0); - size_t blobbytes = (size_t) sqlite3_column_bytes(stmt, 0); - - srcoffset = std::min(srcoffset, blobbytes); - minbytes = std::min(srcbytes, blobbytes - srcoffset); - - if (srcoffset != 0) - { - srcoffset += 0; - } - CopySamples(src + srcoffset, - srcformat, - (samplePtr) dest, - destformat, - minbytes / SAMPLE_SIZE(srcformat)); - - dest = ((samplePtr) dest) + minbytes; - } + wxASSERT_MSG(false, wxT("Binding failed...bug!!!")); } - if ( rc != SQLITE_ROW ) + // Execute the statement + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) + { + wxLogDebug(wxT("SqliteSampleBlock::GetBlob - SQLITE error %s"), sqlite3_errmsg(db)); + + // Clear statement bindings and rewind statement + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + // Just showing the user a simple message, not the library error too // which isn't internationalized - throw SimpleMessageBoxException{ XO("Failed to retrieve samples") }; + throw SimpleMessageBoxException{ XO("Failed to retrieve project data") }; + } + + // Retrieve returned data + samplePtr src = (samplePtr) sqlite3_column_blob(stmt, 0); + size_t blobbytes = (size_t) sqlite3_column_bytes(stmt, 0); + + srcoffset = std::min(srcoffset, blobbytes); + minbytes = std::min(srcbytes, blobbytes - srcoffset); + + if (srcoffset != 0) + { + srcoffset += 0; + } + + CopySamples(src + srcoffset, + srcformat, + (samplePtr) dest, + destformat, + minbytes / SAMPLE_SIZE(srcformat)); + + dest = ((samplePtr) dest) + minbytes; if (srcbytes - minbytes) { memset(dest, 0, srcbytes - minbytes); } + // Clear statement bindings and rewind statement + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + return srcbytes; } void SqliteSampleBlock::Load(SampleBlockID sbid) { auto db = mIO.DB(); + int rc; wxASSERT(sbid > 0); - int rc; - mValid = false; mSummary256Bytes = 0; mSummary64kBytes = 0; @@ -536,41 +535,33 @@ void SqliteSampleBlock::Load(SampleBlockID sbid) mSumMax = -FLT_MAX; mSumMin = 0.0; - char sql[256]; - sqlite3_snprintf(sizeof(sql), - sql, - "SELECT sampleformat, summin, summax, sumrms," - " length('summary256'), length('summary64k'), length('samples')" - " FROM sampleblocks WHERE blockid = %lld;", - sbid); + // Retrieve prepared statement + sqlite3_stmt *stmt = mIO.GetStatement(ProjectFileIO::LoadSampleBlock); - sqlite3_stmt *stmt = nullptr; - auto cleanup = finally([&] + // Bind statement paraemters + // Might return SQLITE_MISUSE which means it's our mistake that we violated + // preconditions; should return SQL_OK which is 0 + if (sqlite3_bind_int64(stmt, 1, sbid)) { - if (stmt) - { - sqlite3_finalize(stmt); - } - }); - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); - if (rc != SQLITE_OK) - { - wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); - } - else { - rc = sqlite3_step(stmt); - if (rc != SQLITE_ROW) - { - wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); - } + wxASSERT_MSG(false, wxT("Binding failed...bug!!!")); } - if ( rc != SQLITE_ROW ) + // Execute the statement + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) + { + wxLogDebug(wxT("SqliteSampleBlock::Load - SQLITE error %s"), sqlite3_errmsg(db)); + + // Clear statement bindings and rewind statement + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + // Just showing the user a simple message, not the library error too // which isn't internationalized - throw SimpleMessageBoxException{ XO("Failed to retrieve samples") }; + throw SimpleMessageBoxException{ XO("Failed to retrieve sample block") }; + } + // Retrieve returned data mBlockID = sbid; mSampleFormat = (sampleFormat) sqlite3_column_int(stmt, 0); mSumMin = sqlite3_column_double(stmt, 1); @@ -581,6 +572,10 @@ void SqliteSampleBlock::Load(SampleBlockID sbid) mSampleBytes = sqlite3_column_int(stmt, 6); mSampleCount = mSampleBytes / SAMPLE_SIZE(mSampleFormat); + // Clear statement bindings and rewind statement + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + mValid = true; } @@ -589,85 +584,89 @@ void SqliteSampleBlock::Commit() auto db = mIO.DB(); int rc; - char sql[256]; - sqlite3_snprintf(sizeof(sql), - sql, - "INSERT INTO sampleblocks (%s) VALUES(?,?,?,?,?,?,?);", - columns); + // Retrieve prepared statement + sqlite3_stmt *stmt = mIO.GetStatement(ProjectFileIO::InsertSampleBlock); - sqlite3_stmt *stmt = nullptr; - auto cleanup = finally([&] - { - if (stmt) - { - sqlite3_finalize(stmt); - } - }); - - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); - if (rc != SQLITE_OK) - { - wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); - // Just showing the user a simple message, not the library error too - // which isn't internationalized - throw SimpleMessageBoxException{ mIO.GetLastError() }; - } - - // BIND SQL sampleblocks - // Might return SQL_MISUSE which means it's our mistake that we violated + // Bind statement paraemters + // Might return SQLITE_MISUSE which means it's our mistake that we violated // preconditions; should return SQL_OK which is 0 - if ( - sqlite3_bind_int(stmt, 1, mSampleFormat) || - sqlite3_bind_double(stmt, 2, mSumMin) || - sqlite3_bind_double(stmt, 3, mSumMax) || - sqlite3_bind_double(stmt, 4, mSumRms) || - sqlite3_bind_blob(stmt, 5, mSummary256.get(), mSummary256Bytes, SQLITE_STATIC) || - sqlite3_bind_blob(stmt, 6, mSummary64k.get(), mSummary64kBytes, SQLITE_STATIC) || - sqlite3_bind_blob(stmt, 7, mSamples.get(), mSampleBytes, SQLITE_STATIC) - ) - THROW_INCONSISTENCY_EXCEPTION; + if (sqlite3_bind_int(stmt, 1, mSampleFormat) || + sqlite3_bind_double(stmt, 2, mSumMin) || + sqlite3_bind_double(stmt, 3, mSumMax) || + sqlite3_bind_double(stmt, 4, mSumRms) || + sqlite3_bind_blob(stmt, 5, mSummary256.get(), mSummary256Bytes, SQLITE_STATIC) || + sqlite3_bind_blob(stmt, 6, mSummary64k.get(), mSummary64kBytes, SQLITE_STATIC) || + sqlite3_bind_blob(stmt, 7, mSamples.get(), mSampleBytes, SQLITE_STATIC)) + { + wxASSERT_MSG(false, wxT("Binding failed...bug!!!")); + } + // Execute the statement rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { - wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); + wxLogDebug(wxT("SqliteSampleBlock::Commit - SQLITE error %s"), sqlite3_errmsg(db)); + + // Clear statement bindings and rewind statement + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + // Just showing the user a simple message, not the library error too // which isn't internationalized - throw SimpleMessageBoxException{ mIO.GetLastError() }; + throw SimpleMessageBoxException{ XO("Failed to add sample block") }; } + // Retrieve returned data mBlockID = sqlite3_last_insert_rowid(db); + // Reset local arrays mSamples.reset(); mSummary256.reset(); mSummary64k.reset(); + // Clear statement bindings and rewind statement + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + mValid = true; } void SqliteSampleBlock::Delete() { auto db = mIO.DB(); + int rc; - if (mBlockID) + wxASSERT(mBlockID > 0); + + // Retrieve prepared statement + sqlite3_stmt *stmt = mIO.GetStatement(ProjectFileIO::DeleteSampleBlock); + + // Bind statement paraemters + // Might return SQLITE_MISUSE which means it's our mistake that we violated + // preconditions; should return SQL_OK which is 0 + if (sqlite3_bind_int64(stmt, 1, mBlockID)) { - int rc; - - char sql[256]; - sqlite3_snprintf(sizeof(sql), - sql, - "DELETE FROM sampleblocks WHERE blockid = %lld;", - mBlockID); - - rc = sqlite3_exec(db, sql, nullptr, nullptr, nullptr); - if (rc != SQLITE_OK) - { - wxLogDebug(wxT("SQLITE error %s"), sqlite3_errmsg(db)); - // Just showing the user a simple message, not the library error too - // which isn't internationalized - throw SimpleMessageBoxException{ XO("Failed to purge unused samples") }; - } + wxASSERT_MSG(false, wxT("Binding failed...bug!!!")); } + + // Execute the statement + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) + { + wxLogDebug(wxT("SqliteSampleBlock::Load - SQLITE error %s"), sqlite3_errmsg(db)); + + // Clear statement bindings and rewind statement + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); + + // Just showing the user a simple message, not the library error too + // which isn't internationalized + throw SimpleMessageBoxException{ XO("Failed to delete sample block") }; + } + + // Clear statement bindings and rewind statement + sqlite3_clear_bindings(stmt); + sqlite3_reset(stmt); } void SqliteSampleBlock::SaveXML(XMLWriter &xmlFile)