// Copyright (C) 2026, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Duplicati.Library.Utility;
using Microsoft.Data.Sqlite;
#nullable enable
namespace Duplicati.Library.Main.Database;
///
/// Extension method for
///
public static partial class ExtensionMethods
{
///
/// Converts the value at the given index in the reader to an Int64.
///
/// The to read from.
/// The index of the value to convert.
/// The default value to return if the value is null or cannot be converted.
/// The converted Int64 value, or the default value if the conversion fails.
public static long ConvertValueToInt64(this SqliteDataReader reader, int index, long defaultvalue = -1)
{
try
{
if (!reader.IsDBNull(index))
return reader.GetInt64(index);
}
catch { }
return defaultvalue;
}
///
/// Converts the value at the given index in the reader to a string.
///
/// The to read from.
/// The index of the value to convert.
/// The converted string value, or null if the value is null or cannot be converted.
public static string? ConvertValueToString(this SqliteDataReader reader, int index)
{
var v = reader.GetValue(index);
if (v == null || v == DBNull.Value)
return null;
return v.ToString();
}
///
/// Creates a new with the given command text.
///
/// The to create the command for.
/// The command text to set for the command.
/// A new with the command text set.
public static SqliteCommand CreateCommand(this SqliteConnection self, string cmdtext)
{
return CreateCommandAsync(self, cmdtext, default).Await();
}
///
/// Creates a new with the given command text and prepares it asynchronously.
///
/// The to create the command for.
/// The command text to set for the command.
/// Cancellation token to cancel the operation.
/// A new with the command text set and prepared asynchronously.
public static async Task CreateCommandAsync(this SqliteConnection self, string cmdtext, CancellationToken cancellationToken)
{
var cmd = self.CreateCommand()
.SetCommandAndParameters(cmdtext);
await cmd.PrepareAsync(cancellationToken).ConfigureAwait(false);
return cmd;
}
///
/// Creates a new with the given transaction.
///
/// The to create the command for.
/// The to set for the command.
/// A new with the transaction set.
public static SqliteCommand CreateCommand(this SqliteConnection self, SqliteTransaction transaction)
{
var cmd = self.CreateCommand();
cmd.SetTransaction(transaction);
return cmd;
}
///
/// Creates a new with the given reusable transaction.
///
/// The to create the command for.
/// The to set for the command.
/// A new with the transaction set.
internal static SqliteCommand CreateCommand(this SqliteConnection self, ReusableTransaction rtr)
{
return self.CreateCommand(rtr.Transaction);
}
///
/// Executes the command asynchronously and returns the number of rows affected.
///
/// The instance to execute on.
/// The command text to execute.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the number of rows affected.
public static async Task ExecuteNonQueryAsync(this SqliteCommand self, string? cmdtext, CancellationToken cancellationToken)
{
return await ExecuteNonQueryAsync(self, true, cmdtext, null, cancellationToken)
.ConfigureAwait(false);
}
///
/// Executes the command asynchronously and returns the number of rows affected.
///
/// The instance to execute on.
/// The command text to execute.
/// The values to use as parameters.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the number of rows affected.
public static async Task ExecuteNonQueryAsync(this SqliteCommand self, string cmd, Dictionary values, CancellationToken cancellationToken)
{
return await ExecuteNonQueryAsync(self, true, cmd, values, cancellationToken)
.ConfigureAwait(false);
}
///
/// Executes the command asynchronously and returns the number of rows affected.
/// This method bypasses logging unless explicitly activated to avoid slowdowns caused by logging.
///
/// The instance to execute on.
/// Whether to write a log entry.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the number of rows affected.
public static Task ExecuteNonQueryPerformanceSensitiveAsync(this SqliteCommand self, bool writeLog, CancellationToken cancellationToken)
{
if (!writeLog)
return self.ExecuteNonQueryAsync(cancellationToken);
return ExecuteNonQueryAsync(self, writeLog, cancellationToken);
}
///
/// Executes the command asynchronously and returns the number of rows affected.
///
/// The instance to execute on.
/// Whether to write a log entry.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the number of rows affected.
public static async Task ExecuteNonQueryAsync(this SqliteCommand self, bool writeLog, CancellationToken cancellationToken)
{
return await ExecuteNonQueryAsync(self, writeLog, null, null, cancellationToken)
.ConfigureAwait(false);
}
///
/// Executes the command asynchronously and returns the number of rows affected.
///
/// The instance to execute on.
/// Whether to write a log entry.
/// The command text to execute.
/// The values to set as parameters.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the number of rows affected.
public static async Task ExecuteNonQueryAsync(this SqliteCommand self, bool writeLog, string? cmd, Dictionary? values, CancellationToken cancellationToken)
{
if (cmd != null)
self.SetCommandAndParameters(cmd);
if (values != null && values.Count > 0)
self.SetParameterValues(values);
using (SlowQueryMonitor.StartQuery(self, "ExecuteNonQueryAsync"))
using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteNonQueryAsync", string.Format("ExecuteNonQueryAsync: {0}", self.GetPrintableCommandText())) : null)
return await self.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
///
/// Executes the command asynchronously and returns a .
///
/// The instance to execute on.
/// The command text to execute.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the .
public static async Task ExecuteReaderAsync(this SqliteCommand self, string cmdtext, CancellationToken cancellationToken)
{
return await ExecuteReaderAsync(self, true, cmdtext, null, cancellationToken)
.ConfigureAwait(false);
}
///
/// Executes the command asynchronously and returns a .
///
/// The instance to execute on.
/// Whether to write a log entry.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the .
public static async Task ExecuteReaderAsync(this SqliteCommand self, bool writeLog, CancellationToken cancellationToken)
{
return await ExecuteReaderAsync(self, writeLog, null, null, cancellationToken)
.ConfigureAwait(false);
}
///
/// Executes the command asynchronously and returns a .
///
/// The instance to execute on.
/// The command text to execute.
/// The values to use as parameters.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the .
public static async Task ExecuteReaderAsync(this SqliteCommand self, string cmdtext, Dictionary? values, CancellationToken cancellationToken)
{
return await ExecuteReaderAsync(self, true, cmdtext, values, cancellationToken)
.ConfigureAwait(false);
}
///
/// Executes the command asynchronously and returns a .
///
/// The instance to execute on.
/// Whether to write a log entry.
/// The command text to execute.
/// The values to set as parameters.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the .
public static async Task ExecuteReaderAsync(this SqliteCommand self, bool writeLog, string? cmdtext, Dictionary? values, CancellationToken cancellationToken)
{
if (cmdtext != null)
self.SetCommandAndParameters(cmdtext);
if (values != null && values.Count > 0)
self.SetParameterValues(values);
using (SlowQueryMonitor.StartQuery(self, "ExecuteReaderAsync"))
using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReader", string.Format("ExecuteReader: {0}", self.GetPrintableCommandText())) : null)
return await self.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
}
///
/// Executes the command asynchronously and returns an enumerable of .
///
/// The instance to execute on.
/// Cancellation token to cancel the operation.
/// An asynchronous enumerable of .
public static IAsyncEnumerable ExecuteReaderEnumerableAsync(this SqliteCommand self, CancellationToken cancellationToken)
{
return ExecuteReaderEnumerableAsync(self, true, null, null, cancellationToken);
}
///
/// Executes the command asynchronously and returns an enumerable of .
///
/// The instance to execute on.
/// The command text to execute.
/// Cancellation token to cancel the operation.
/// An asynchronous enumerable of .
public static IAsyncEnumerable ExecuteReaderEnumerableAsync(this SqliteCommand self, string cmdtext, CancellationToken cancellationToken)
{
return ExecuteReaderEnumerableAsync(self, true, cmdtext, null, cancellationToken);
}
///
/// Executes the command asynchronously and returns an enumerable of .
///
/// The instance to execute on.
/// The command text to execute.
/// The values to use as parameters.
/// Cancellation token to cancel the operation.
/// An asynchronous enumerable of .
public static IAsyncEnumerable ExecuteReaderEnumerableAsync(this SqliteCommand self, string cmdtext, Dictionary? values, CancellationToken cancellationToken)
{
return ExecuteReaderEnumerableAsync(self, true, cmdtext, values, cancellationToken);
}
///
/// Executes the command asynchronously and returns an enumerable of .
///
/// The instance to execute on.
/// Whether to write a log entry.
/// The command text to execute.
/// The values to set as parameters.
/// An asynchronous enumerable of .
public static async IAsyncEnumerable ExecuteReaderEnumerableAsync(this SqliteCommand self, bool writeLog, string? cmdtext, Dictionary? values, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
if (cmdtext != null)
self.SetCommandAndParameters(cmdtext);
if (values != null && values.Count > 0)
self.SetParameterValues(values);
using (SlowQueryMonitor.StartQuery(self, "ExecuteReaderEnumerableAsync"))
using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReaderEnumerableAsync", $"ExecuteReaderEnumerableAsync: {self.GetPrintableCommandText()}") : null)
await using (var rd = await self.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
while (await rd.ReadAsync(cancellationToken).ConfigureAwait(false))
yield return rd;
}
///
/// Executes the command asynchronously and returns the first column of the first row in the result set.
///
/// The instance to execute on.
/// Cancellation token to cancel the operation.
/// A task that when awaited contains the first column of the first row in the result set, or null if no rows are returned.
public static async Task