Changed transactions to use a wrapper that ensures a transaction is allocated.
This commit is contained in:
@@ -493,6 +493,23 @@ public static class ExtensionMethods
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the transaction for the given connection and executes a "BEGIN IMMEDIATE" command to ensure that the transaction can be comitted or rolled back.
|
||||
/// This works around a quirk in SQLite where the transaction is not initialized until the first command is executed.
|
||||
/// This means that if the transaction is rolled back or committed with no commands executed, it will fail with an exception.
|
||||
/// </summary>
|
||||
/// <param name="self">The connection to create the transaction on</param>
|
||||
/// <returns>The transaction</returns>
|
||||
public static IDbTransaction BeginTransactionSafe(this IDbConnection self)
|
||||
{
|
||||
var transaction = self.BeginTransaction();
|
||||
using (var cmd = self.CreateCommand(transaction, "BEGIN IMMEDIATE;"))
|
||||
cmd.ExecuteNonQuery();
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a command with the given command string, and adds parameters to fit the input
|
||||
/// </summary>
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Duplicati.Library.Main.Database
|
||||
|
||||
public void Fix()
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand())
|
||||
{
|
||||
cmd.Transaction = tr;
|
||||
|
||||
@@ -654,7 +654,7 @@ AND Fileset.ID NOT IN
|
||||
// TODO: Remove this
|
||||
public IDbTransaction BeginTransaction()
|
||||
{
|
||||
return m_connection.BeginTransaction();
|
||||
return m_connection.BeginTransactionSafe();
|
||||
}
|
||||
|
||||
protected class TemporaryTransactionWrapper : IDisposable
|
||||
@@ -671,7 +671,7 @@ AND Fileset.ID NOT IN
|
||||
}
|
||||
else
|
||||
{
|
||||
m_parent = connection.BeginTransaction();
|
||||
m_parent = connection.BeginTransactionSafe();
|
||||
m_isTemporary = true;
|
||||
}
|
||||
}
|
||||
@@ -1626,7 +1626,7 @@ AND oldVersion.FilesetID = (SELECT ID FROM Fileset WHERE ID != @FilesetId ORDER
|
||||
|
||||
public void PurgeLogData(DateTime threshold)
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand(tr))
|
||||
{
|
||||
var t = Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(threshold);
|
||||
@@ -1643,7 +1643,7 @@ AND oldVersion.FilesetID = (SELECT ID FROM Fileset WHERE ID != @FilesetId ORDER
|
||||
|
||||
public void PurgeDeletedVolumes(DateTime threshold)
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand(tr))
|
||||
{
|
||||
m_removedeletedremotevolumeCommand.SetParameterValue("@Now", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(threshold))
|
||||
@@ -1663,7 +1663,7 @@ AND oldVersion.FilesetID = (SELECT ID FROM Fileset WHERE ID != @FilesetId ORDER
|
||||
{
|
||||
if (m_connection.State == ConnectionState.Open && !m_hasExecutedVacuum)
|
||||
{
|
||||
using (var transaction = m_connection.BeginTransaction())
|
||||
using (var transaction = m_connection.BeginTransactionSafe())
|
||||
using (var command = m_connection.CreateCommand(transaction))
|
||||
{
|
||||
// SQLite recommends that PRAGMA optimize is run just before closing each database connection.
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace Duplicati.Library.Main.Database
|
||||
m_previousTable = "Previous-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray());
|
||||
m_currentTable = "Current-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray());
|
||||
|
||||
m_transaction = m_connection.BeginTransaction();
|
||||
m_transaction = m_connection.BeginTransactionSafe();
|
||||
|
||||
using (var cmd = m_connection.CreateCommand(m_transaction))
|
||||
{
|
||||
|
||||
@@ -289,7 +289,7 @@ namespace Duplicati.Library.Main.Database
|
||||
|
||||
public void FixDuplicateMetahash()
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand(tr))
|
||||
{
|
||||
var sql_count =
|
||||
@@ -355,7 +355,7 @@ namespace Duplicati.Library.Main.Database
|
||||
|
||||
public void FixDuplicateFileentries()
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand(tr))
|
||||
{
|
||||
var sql_count = @"SELECT COUNT(*) FROM (SELECT ""PrefixID"", ""Path"", ""BlocksetID"", ""MetadataID"", COUNT(*) as ""Duplicates"" FROM ""FileLookup"" GROUP BY ""PrefixID"", ""Path"", ""BlocksetID"", ""MetadataID"") WHERE ""Duplicates"" > 1";
|
||||
@@ -398,7 +398,7 @@ namespace Duplicati.Library.Main.Database
|
||||
{
|
||||
var blocklistbuffer = new byte[blocksize];
|
||||
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand(tr))
|
||||
using (var blockhasher = HashFactory.CreateHasher(blockhashalgorithm))
|
||||
{
|
||||
@@ -535,7 +535,7 @@ namespace Duplicati.Library.Main.Database
|
||||
|
||||
public void FixDuplicateBlocklistHashes(long blocksize, long hashsize)
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand(tr))
|
||||
{
|
||||
var dup_sql = @"SELECT * FROM (SELECT ""BlocksetID"", ""Index"", COUNT(*) AS ""EC"" FROM ""BlocklistHash"" GROUP BY ""BlocksetID"", ""Index"") WHERE ""EC"" > 1";
|
||||
@@ -591,7 +591,7 @@ namespace Duplicati.Library.Main.Database
|
||||
|
||||
public void CheckAllBlocksAreInVolume(string filename, IEnumerable<KeyValuePair<string, long>> blocks)
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand(tr))
|
||||
{
|
||||
var tablename = "ProbeBlocks-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray());
|
||||
|
||||
@@ -246,7 +246,7 @@ END ");
|
||||
// If we get a list of filenames, the lookup table is faster
|
||||
// unfortunately we cannot do this if the filesystem is case sensitive as
|
||||
// SQLite only supports ASCII compares
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
{
|
||||
var p = expression.GetSimpleList();
|
||||
var m_filenamestable = "Filenames-" + m_temptabsetguid;
|
||||
@@ -1173,7 +1173,7 @@ ORDER BY ""A"".""TargetPath"", ""BB"".""Index"""));
|
||||
|
||||
public DirectBlockMarker(IDbConnection connection, string blocktablename, string filetablename, string statstablename)
|
||||
{
|
||||
m_transaction = connection.BeginTransaction();
|
||||
m_transaction = connection.BeginTransactionSafe();
|
||||
m_blocktablename = blocktablename;
|
||||
m_filetablename = filetablename;
|
||||
|
||||
@@ -1362,7 +1362,7 @@ WHERE ""FileID"" = @TargetFileId AND ""Index"" = @Index AND ""Hash"" = @Hash AND
|
||||
|
||||
public IEnumerable<IFastSource> GetFilesAndSourceBlocksFast(long blocksize)
|
||||
{
|
||||
using (var transaction = m_connection.BeginTransaction())
|
||||
using (var transaction = m_connection.BeginTransactionSafe())
|
||||
using (var cmdReader = m_connection.CreateCommand())
|
||||
using (var cmd = m_connection.CreateCommand(transaction))
|
||||
{
|
||||
|
||||
@@ -64,14 +64,14 @@ internal class ReusableTransaction : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="message">The log message to use</param>
|
||||
/// <param name="restart">True if the transaction should be restarted</param>
|
||||
public void Commit(string? message = null, bool restart = true)
|
||||
public void Commit(string? message, bool restart = true)
|
||||
{
|
||||
if (m_transaction == null)
|
||||
throw new InvalidOperationException("Transaction is already disposed");
|
||||
|
||||
if (m_transaction != null)
|
||||
{
|
||||
using (var timer = string.IsNullOrWhiteSpace(message) ? null : new Logging.Timer(LOGTAG, message, "CommitTransaction"))
|
||||
using (var timer = string.IsNullOrWhiteSpace(message) ? null : new Logging.Timer(LOGTAG, message, $"CommitTransaction: {message}"))
|
||||
m_transaction.Commit();
|
||||
m_transaction.Dispose();
|
||||
m_transaction = null;
|
||||
|
||||
@@ -203,7 +203,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
|
||||
// Commit as we have uploaded a volume
|
||||
if (!m_options.Dryrun)
|
||||
rtr.Commit();
|
||||
rtr.Commit("CommitCompact");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,7 +317,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
await backend.WaitForEmptyAsync(db, rtr.Transaction, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!m_options.Dryrun)
|
||||
rtr.Commit();
|
||||
rtr.Commit("CommitDelete");
|
||||
|
||||
await foreach (var d in PerformDelete(backend, remoteFilesToRemove, cancellationToken).ConfigureAwait(false))
|
||||
yield return d;
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
db.UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null, rtr.Transaction);
|
||||
|
||||
if (!m_options.Dryrun)
|
||||
rtr.Commit();
|
||||
rtr.Commit("CommitBeforeDelete");
|
||||
|
||||
foreach (var f in lst)
|
||||
{
|
||||
|
||||
@@ -247,7 +247,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
.DoCompactAsync(cdb, true, ctr, backendManager)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
ctr.Commit(restart: false);
|
||||
ctr.Commit("PostCompact", restart: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -649,7 +649,7 @@ namespace Duplicati.Library.Main.Operation
|
||||
using (var rdb = new LocalRecreateDatabase(db, m_options))
|
||||
RecreateDatabaseHandler.RecreateFilesetFromRemoteList(rdb, tr.Transaction, compressor, entry.Key, m_options, new FilterExpression());
|
||||
|
||||
tr.Commit();
|
||||
tr.Commit("PostRepairFileset");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ namespace Duplicati.Server.Database
|
||||
internal void SetMetadata(IDictionary<string, string> values, long id, IDbTransaction? transaction)
|
||||
{
|
||||
lock (m_lock)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransaction() : null)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransactionSafe() : null)
|
||||
{
|
||||
OverwriteAndUpdateDb(
|
||||
tr,
|
||||
@@ -254,7 +254,7 @@ namespace Duplicati.Server.Database
|
||||
internal void SetFilters(IEnumerable<IFilter> values, long id, IDbTransaction? transaction = null)
|
||||
{
|
||||
lock (m_lock)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransaction() : null)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransactionSafe() : null)
|
||||
{
|
||||
OverwriteAndUpdateDb(
|
||||
tr,
|
||||
@@ -292,7 +292,7 @@ namespace Duplicati.Server.Database
|
||||
internal void SetSettings(IEnumerable<ISetting> values, long id, IDbTransaction? transaction = null)
|
||||
{
|
||||
lock (m_lock)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransaction() : null)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransactionSafe() : null)
|
||||
{
|
||||
if (m_encryptSensitiveFields)
|
||||
values = values.Select(x => new Setting
|
||||
@@ -338,7 +338,7 @@ namespace Duplicati.Server.Database
|
||||
internal void SetSources(IEnumerable<string> values, long id, IDbTransaction transaction)
|
||||
{
|
||||
lock (m_lock)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransaction() : null)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransactionSafe() : null)
|
||||
{
|
||||
OverwriteAndUpdateDb(
|
||||
tr,
|
||||
@@ -595,7 +595,7 @@ namespace Duplicati.Server.Database
|
||||
{
|
||||
lock (m_lock)
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
{
|
||||
using (var cmd = m_connection.CreateCommand(tr, @"UPDATE ""Backup"" SET ""DBPath""= @Dbpath WHERE ""ID""= @Id"))
|
||||
{
|
||||
@@ -636,7 +636,7 @@ namespace Duplicati.Server.Database
|
||||
throw new Exception("Unable to generate a unique database file name");
|
||||
}
|
||||
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
{
|
||||
OverwriteAndUpdateDb(
|
||||
tr,
|
||||
@@ -728,7 +728,7 @@ namespace Duplicati.Server.Database
|
||||
internal void AddOrUpdateSchedule(ISchedule item)
|
||||
{
|
||||
lock (m_lock)
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
{
|
||||
AddOrUpdateSchedule(item, tr);
|
||||
tr.Commit();
|
||||
@@ -778,7 +778,7 @@ namespace Duplicati.Server.Database
|
||||
|
||||
lock (m_lock)
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
{
|
||||
var existing = GetScheduleIDsFromTags(new string[] { "ID=" + ID.ToString() });
|
||||
if (existing.Any())
|
||||
@@ -958,7 +958,7 @@ namespace Duplicati.Server.Database
|
||||
//Workaround to clean up the database after invalid settings update
|
||||
public void FixInvalidBackupId()
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand(tr))
|
||||
{
|
||||
cmd.SetCommandAndParameters(@"DELETE FROM ""Option"" WHERE ""BackupID"" = @BackupId")
|
||||
@@ -1009,7 +1009,7 @@ namespace Duplicati.Server.Database
|
||||
public void SetUISettings(string scheme, IDictionary<string, string?> values, IDbTransaction? transaction = null)
|
||||
{
|
||||
lock (m_lock)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransaction() : null)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransactionSafe() : null)
|
||||
{
|
||||
OverwriteAndUpdateDb(
|
||||
tr,
|
||||
@@ -1033,7 +1033,7 @@ namespace Duplicati.Server.Database
|
||||
public void UpdateUISettings(string scheme, IDictionary<string, string?> values, IDbTransaction? transaction = null)
|
||||
{
|
||||
lock (m_lock)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransaction() : null)
|
||||
using (var tr = transaction == null ? m_connection.BeginTransactionSafe() : null)
|
||||
{
|
||||
OverwriteAndUpdateDb(
|
||||
tr,
|
||||
@@ -1086,7 +1086,7 @@ namespace Duplicati.Server.Database
|
||||
{
|
||||
var t = Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(purgeDate);
|
||||
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
using (var cmd = m_connection.CreateCommand(tr))
|
||||
{
|
||||
cmd.SetCommandAndParameters(@"DELETE FROM ""ErrorLog"" WHERE ""Timestamp"" < @Time")
|
||||
@@ -1165,7 +1165,7 @@ namespace Duplicati.Server.Database
|
||||
{
|
||||
if (transaction == null)
|
||||
{
|
||||
using (var tr = m_connection.BeginTransaction())
|
||||
using (var tr = m_connection.BeginTransactionSafe())
|
||||
{
|
||||
var r = DeleteFromDb(tablename, id, tr);
|
||||
tr.Commit();
|
||||
|
||||
Reference in New Issue
Block a user