AUP3: Fix previous commit and rework db handling

This moves direct handling of the sqlite3 DB handle into it's own
class, along with the checkpointing thread and prepared statement
cache.
This commit is contained in:
Leland Lucius 2020-07-22 11:41:03 -05:00
parent 7e9a3f49b8
commit 6f771d1783
4 changed files with 550 additions and 463 deletions

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@ struct sqlite3_value;
class AudacityProject;
class AutoCommitTransaction;
class DBConnection;
class ProjectSerializer;
class SqliteSampleBlock;
class TrackList;
@ -40,6 +41,8 @@ using WaveTrackArray = std::vector < std::shared_ptr < WaveTrack > >;
// From SampleBlock.h
using SampleBlockID = long long;
using Connection = std::unique_ptr<DBConnection>;
///\brief Object associated with a project that manages reading and writing
/// of Audacity project file formats, and autosave
class ProjectFileIO final
@ -143,6 +146,11 @@ private:
// if opening fails.
sqlite3 *DB();
Connection &Conn();
bool OpenConnection(FilePath fileName = {});
bool CloseConnection();
// Put the current database connection aside, keeping it open, so that
// another may be opened with OpenDB()
void SaveConnection();
@ -153,26 +161,8 @@ private:
// Close any current connection and switch back to using the saved
void RestoreConnection();
// Use a connection that is already open rather than invoke OpenDB
void UseConnection(sqlite3 *db, const FilePath &filePath);
// Make sure the connection/schema combo is configured the way we want
void Config(sqlite3 *db, const char *config, const wxString &schema = wxT("main"));
sqlite3 *OpenDB(FilePath fileName = {});
bool CloseDB();
enum StatementID
{
GetSamples,
GetSummary256,
GetSummary64k,
LoadSampleBlock,
InsertSampleBlock,
DeleteSampleBlock
};
void Prepare(enum StatementID id, const char *sql);
sqlite3_stmt *GetStatement(enum StatementID id);
// Use a connection that is already open rather than invoke OpenConnection
void UseConnection(Connection &&conn, const FilePath &filePath);
bool Query(const char *sql, const ExecCB &callback);
@ -184,7 +174,7 @@ private:
bool UpgradeSchema();
// Write project or autosave XML (binary) documents
bool WriteDoc(const char *table, const ProjectSerializer &autosave, sqlite3 *db = nullptr);
bool WriteDoc(const char *table, const ProjectSerializer &autosave, const char *schema = "main");
// Application defined function to verify blockid exists is in set of blockids
using BlockIDs = std::set<SampleBlockID>;
@ -194,10 +184,10 @@ private:
bool CheckForOrphans(BlockIDs &blockids);
// Return a database connection if successful, which caller must close
sqlite3 *CopyTo(const FilePath &destpath,
const TranslatableString &msg,
bool prune = false,
const std::shared_ptr<TrackList> &tracks = nullptr);
Connection CopyTo(const FilePath &destpath,
const TranslatableString &msg,
bool prune = false,
const std::shared_ptr<TrackList> &tracks = nullptr);
void SetError(const TranslatableString & msg);
void SetDBError(const TranslatableString & msg);
@ -232,24 +222,17 @@ private:
// Project had unused blocks during last Vacuum()
bool mHadUnused;
sqlite3 *mPrevDB;
Connection mPrevConn;
FilePath mPrevFileName;
bool mPrevTemporary;
sqlite3 *mDB;
Connection mCurrConn;
TranslatableString mLastError;
TranslatableString mLibraryError;
std::thread mCheckpointThread;
std::condition_variable mCheckpointCondition;
std::mutex mCheckpointMutex;
std::atomic_bool mCheckpointStop{ false };
std::atomic< std::uint64_t > mCheckpointWaitingPages{ 0 };
std::atomic< std::uint64_t > mCheckpointCurrentPages{ 0 };
std::map<enum StatementID, sqlite3_stmt *> mStatements;
friend SqliteSampleBlock;
friend AutoCommitTransaction;
friend DBConnection;
};
class AutoCommitTransaction
@ -267,6 +250,58 @@ private:
wxString mName;
};
class DBConnection
{
public:
DBConnection(ProjectFileIO *io);
~DBConnection();
bool Open(const char *fileName);
bool Close();
bool SafeMode(const char *schema = "main");
bool FastMode(const char *schema = "main");
bool Assign(sqlite3 *handle);
sqlite3 *Detach();
sqlite3 *DB();
int GetLastRC() const ;
const wxString GetLastMessage() const;
enum StatementID
{
GetSamples,
GetSummary256,
GetSummary64k,
LoadSampleBlock,
InsertSampleBlock,
DeleteSampleBlock
};
sqlite3_stmt *GetStatement(enum StatementID id);
sqlite3_stmt *Prepare(enum StatementID id, const char *sql);
private:
bool ModeConfig(sqlite3 *db, const char *schema, const char *config);
void CheckpointThread();
static int CheckpointHook(void *data, sqlite3 *db, const char *schema, int pages);
private:
ProjectFileIO &mIO;
sqlite3 *mDB;
std::thread mCheckpointThread;
std::condition_variable mCheckpointCondition;
std::mutex mCheckpointMutex;
std::atomic_bool mCheckpointStop{ false };
std::atomic_int mCheckpointWaitingPages{ 0 };
std::atomic_int mCheckpointCurrentPages{ 0 };
std::map<enum StatementID, sqlite3_stmt *> mStatements;
};
class wxTopLevelWindow;
// TitleRestorer restores project window titles to what they were, in its destructor.

View File

@ -144,7 +144,13 @@ auto ProjectFileManager::ReadProjectFile( const FilePath &fileName )
{
if (projectFileIO.IsRecovered())
{
bool resaved = projectFileIO.SaveProject(fileName);
bool resaved = false;
if (!projectFileIO.IsTemporary())
{
projectFileIO.SaveProject(fileName);
}
AudacityMessageBox(
resaved
? XO("This project was not saved properly the last time Audacity ran.\n\n"

View File

@ -38,9 +38,9 @@ public:
SampleBlockID GetBlockID() override;
size_t DoGetSamples(samplePtr dest,
sampleFormat destformat,
size_t sampleoffset,
size_t numsamples) override;
sampleFormat destformat,
size_t sampleoffset,
size_t numsamples) override;
sampleFormat GetSampleFormat() const;
size_t GetSampleCount() const override;
@ -64,11 +64,11 @@ private:
bool GetSummary(float *dest,
size_t frameoffset,
size_t numframes,
ProjectFileIO::StatementID id,
sqlite3_stmt *stmt,
size_t srcbytes);
size_t GetBlob(void *dest,
sampleFormat destformat,
ProjectFileIO::StatementID id,
sqlite3_stmt *stmt,
sampleFormat srcformat,
size_t srcoffset,
size_t srcbytes);
@ -286,9 +286,13 @@ size_t SqliteSampleBlock::DoGetSamples(samplePtr dest,
size_t sampleoffset,
size_t numsamples)
{
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::GetSamples,
"SELECT samples FROM sampleblocks WHERE blockid = ?1;");
return GetBlob(dest,
destformat,
ProjectFileIO::GetSamples,
stmt,
mSampleFormat,
sampleoffset * SAMPLE_SIZE(mSampleFormat),
numsamples * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
@ -330,33 +334,33 @@ bool SqliteSampleBlock::GetSummary256(float *dest,
size_t frameoffset,
size_t numframes)
{
return GetSummary(dest,
frameoffset,
numframes,
ProjectFileIO::GetSummary256,
mSummary256Bytes);
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::GetSummary256,
"SELECT summary256 FROM sampleblocks WHERE blockid = ?1;");
return GetSummary(dest, frameoffset, numframes, stmt, mSummary256Bytes);
}
bool SqliteSampleBlock::GetSummary64k(float *dest,
size_t frameoffset,
size_t numframes)
{
return GetSummary(dest,
frameoffset,
numframes,
ProjectFileIO::GetSummary64k,
mSummary256Bytes);
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::GetSummary64k,
"SELECT summary64k FROM sampleblocks WHERE blockid = ?1;");
return GetSummary(dest, frameoffset, numframes, stmt, mSummary256Bytes);
}
bool SqliteSampleBlock::GetSummary(float *dest,
size_t frameoffset,
size_t numframes,
ProjectFileIO::StatementID id,
sqlite3_stmt *stmt,
size_t srcbytes)
{
return GetBlob(dest,
floatSample,
id,
stmt,
floatSample,
frameoffset * 3 * SAMPLE_SIZE(floatSample),
numframes * 3 * SAMPLE_SIZE(floatSample)) / 3 / SAMPLE_SIZE(floatSample);
@ -401,12 +405,7 @@ MinMaxRMS SqliteSampleBlock::DoGetMinMaxRMS(size_t start, size_t len)
SampleBuffer blockData(len, floatSample);
float *samples = (float *) blockData.ptr();
size_t copied = GetBlob(samples,
floatSample,
ProjectFileIO::GetSamples,
mSampleFormat,
start * SAMPLE_SIZE(mSampleFormat),
len * SAMPLE_SIZE(mSampleFormat)) / SAMPLE_SIZE(mSampleFormat);
size_t copied = DoGetSamples((samplePtr) samples, floatSample, start, len);
for (size_t i = 0; i < copied; ++i, ++samples)
{
float sample = *samples;
@ -444,7 +443,7 @@ size_t SqliteSampleBlock::GetSpaceUsage() const
size_t SqliteSampleBlock::GetBlob(void *dest,
sampleFormat destformat,
ProjectFileIO::StatementID id,
sqlite3_stmt *stmt,
sampleFormat srcformat,
size_t srcoffset,
size_t srcbytes)
@ -461,9 +460,6 @@ size_t SqliteSampleBlock::GetBlob(void *dest,
int rc;
size_t minbytes = 0;
// Retrieve prepared statement
sqlite3_stmt *stmt = mIO.GetStatement(id);
// Bind statement paraemters
// Might return SQLITE_MISUSE which means it's our mistake that we violated
// preconditions; should return SQL_OK which is 0
@ -535,8 +531,11 @@ void SqliteSampleBlock::Load(SampleBlockID sbid)
mSumMax = -FLT_MAX;
mSumMin = 0.0;
// Retrieve prepared statement
sqlite3_stmt *stmt = mIO.GetStatement(ProjectFileIO::LoadSampleBlock);
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::LoadSampleBlock,
"SELECT sampleformat, summin, summax, sumrms,"
" length('summary256'), length('summary64k'), length('samples')"
" FROM sampleblocks WHERE blockid = ?1;");
// Bind statement paraemters
// Might return SQLITE_MISUSE which means it's our mistake that we violated
@ -584,8 +583,11 @@ void SqliteSampleBlock::Commit()
auto db = mIO.DB();
int rc;
// Retrieve prepared statement
sqlite3_stmt *stmt = mIO.GetStatement(ProjectFileIO::InsertSampleBlock);
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::InsertSampleBlock,
"INSERT INTO sampleblocks (sampleformat, summin, summax, sumrms,"
" summary256, summary64k, samples)"
" VALUES(?1,?2,?3,?4,?5,?6,?7);");
// Bind statement paraemters
// Might return SQLITE_MISUSE which means it's our mistake that we violated
@ -638,8 +640,9 @@ void SqliteSampleBlock::Delete()
wxASSERT(mBlockID > 0);
// Retrieve prepared statement
sqlite3_stmt *stmt = mIO.GetStatement(ProjectFileIO::DeleteSampleBlock);
// Prepare and cache statement...automatically finalized at DB close
sqlite3_stmt *stmt = mIO.Conn()->Prepare(DBConnection::DeleteSampleBlock,
"DELETE FROM sampleblocks WHERE blockid = ?1;");
// Bind statement paraemters
// Might return SQLITE_MISUSE which means it's our mistake that we violated
@ -651,7 +654,7 @@ void SqliteSampleBlock::Delete()
// Execute the statement
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW)
if (rc != SQLITE_DONE)
{
wxLogDebug(wxT("SqliteSampleBlock::Load - SQLITE error %s"), sqlite3_errmsg(db));