diff --git a/Duplicati/Library/Main/Database/ExtensionMethods.cs b/Duplicati/Library/Main/Database/ExtensionMethods.cs
index 1ff0a1405..c81fc4772 100644
--- a/Duplicati/Library/Main/Database/ExtensionMethods.cs
+++ b/Duplicati/Library/Main/Database/ExtensionMethods.cs
@@ -23,6 +23,8 @@ using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Microsoft.Data.Sqlite;
#nullable enable
@@ -31,397 +33,11 @@ namespace Duplicati.Library.Main.Database;
///
/// Extension method for
///
-public static class ExtensionMethods
+public static partial class ExtensionMethods
{
- ///
- /// The tag used for logging
- ///
- private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ExtensionMethods));
+ // TODO parameter types and sizes
- ///
- /// Adds a parameter to the command with the given value.
- ///
- /// The type of the command
- /// The command to add the parameter to
- /// The name of the parameter
- /// The optional value of the parameter
- /// The command with the parameter added
- public static T AddNamedParameter(this T self, string name, object? value = null)
- where T : IDbCommand
- {
- var p = self.CreateParameter();
- p.ParameterName = name;
- if (value != null)
- p.Value = value;
- self.Parameters.Add(p);
- return self;
- }
-
- ///
- /// Sets the parameter values for the command, the parameters must already be added.
- ///
- /// The type of the command
- /// The command to set the parameter values for
- /// The values to set the parameters to
- /// The command with the parameter values set
- public static T SetParameterValues(this T self, Dictionary values)
- where T : IDbCommand
- {
- foreach (var kvp in values)
- ((IDataParameter)self.Parameters[kvp.Key]!).Value = kvp.Value;
- return self;
- }
-
- ///
- /// Sets the parameter value for the command at the given index.
- ///
- /// The type of the command
- /// The command to set the parameter value for
- /// The name of the parameter to set the value for
- /// The value to set the parameter to
- /// The command with the parameter value set
- public static T SetParameterValue(this T self, string name, object? value)
- where T : IDbCommand
- {
-#if DEBUG
- if (value is not null && value is System.Collections.IEnumerable && value is not string)
- throw new ArgumentException($"Cannot set parameter '{name}' to an array or enumerable type, as the SQLite bindings does not support it.", nameof(value));
-#endif
- ((IDataParameter)self.Parameters[name]!).Value = value;
- return self;
- }
-
- ///
- /// Sets the transaction for the command.
- ///
- /// The type of the command
- /// The command to set the transaction for
- /// The transaction to set for the command
- /// >The command with the transaction set
- public static T SetTransaction(this T self, IDbTransaction? transaction)
- where T : IDbCommand
- {
- self.Transaction = transaction;
- return self;
- }
-
- ///
- /// Gets the printable command text for the given command.
- /// The command to get the printable command text for
- /// The printable command text
- public static string GetPrintableCommandText(this IDbCommand self)
- {
- var txt = self.CommandText;
-
- foreach (var p in self.Parameters.Cast())
- {
- var ix = txt.IndexOf('?');
- if (ix >= 0)
- {
- string v;
- if (p.Value is string)
- v = string.Format("\"{0}\"", p.Value);
- else if (p.Value == null)
- v = "NULL";
- else
- v = string.Format("{0}", p.Value);
-
- txt = txt.Substring(0, ix) + v + txt.Substring(ix + 1);
- }
- }
-
- return txt;
- }
-
- ///
- /// Executes the command and returns the number of rows affected.
- ///
- /// The command instance to execute on
- /// Whether to write a log entry
- /// The number of rows affected
- public static int ExecuteNonQuery(this IDbCommand self, bool writeLog)
- {
- return ExecuteNonQuery(self, writeLog, null);
- }
-
- ///
- /// Executes the command and returns the number of rows affected.
- ///
- /// The command instance to execute on
- /// The command string to execute
- /// The values to use as parameters. The parameters must already be added.
- /// The number of rows affected
- public static int ExecuteNonQuery(this IDbCommand self, string cmd)
- {
- return ExecuteNonQuery(self, true, cmd);
- }
-
- ///
- /// Executes the command and returns the number of rows affected.
- ///
- /// The command instance to execute on
- /// The command string to execute
- /// The values to use as parameters. The parameters must already be added.
- /// The number of rows affected
- public static int ExecuteNonQuery(this IDbCommand self, string cmd, Dictionary values)
- {
- return ExecuteNonQuery(self, true, cmd, values);
- }
-
- ///
- /// Executes the command and returns the number of rows affected.
- ///
- /// The command instance to execute on
- /// Whether to write a log entry
- /// The command string to execute
- /// The values to use as parameters. The parameters must already be added.
- /// The number of rows affected
- public static int ExecuteNonQuery(this IDbCommand self, bool writeLog, string? cmd, Dictionary values)
- {
- if (cmd != null)
- self.SetCommandAndParameters(cmd);
-
- if (values != null && values.Count > 0)
- self.SetParameterValues(values);
-
- using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteNonQuery", string.Format("ExecuteNonQuery: {0}", self.GetPrintableCommandText())) : null)
- return self.ExecuteNonQuery();
- }
-
- ///
- /// Executes the command and returns the number of rows affected.
- ///
- /// The command instance to execute on
- /// Whether to write a log entry
- /// The command string to execute
- /// The number of rows affected
- public static int ExecuteNonQuery(this IDbCommand self, bool writeLog, string? cmd)
- {
- if (cmd != null)
- self.SetCommandAndParameters(cmd);
-
- using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteNonQuery", string.Format("ExecuteNonQuery: {0}", self.GetPrintableCommandText())) : null)
- return self.ExecuteNonQuery();
- }
-
- ///
- /// Executes the command and returns the number of rows affected.
- ///
- /// The command instance to execute on
- /// The transaction to use for the command
- /// The number of rows affected
- public static int ExecuteNonQuery(this IDbCommand self, IDbTransaction? transaction)
- {
- self.Transaction = transaction;
- return self.ExecuteNonQuery();
- }
-
- ///
- /// Executes the command and returns the scalar value of the first row.
- ///
- /// The command instance to execute on
- /// The command string to execute
- /// The scalar value of the first row
- public static object? ExecuteScalar(this IDbCommand self, string cmd)
- {
- return ExecuteScalar(self, true, cmd);
- }
-
- ///
- /// Executes the command and returns the scalar value of the first row.
- ///
- /// The command instance to execute on
- /// Whether to write a log entry
- /// The command string to execute
- /// The scalar value of the first row
- public static object? ExecuteScalar(this IDbCommand self, bool writeLog, string? cmd)
- {
- if (cmd != null)
- self.SetCommandAndParameters(cmd);
-
- using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteScalar", string.Format("ExecuteScalar: {0}", self.GetPrintableCommandText())) : null)
- return self.ExecuteScalar();
- }
-
- ///
- /// Executes the command and returns the scalar int64 value of the first row as a string.
- ///
- /// The command instance to execute on
- /// Whether to write a log entry
- /// The default value to return if no value is found
- /// The scalar int64 value of the first row as a string
- public static long ExecuteScalarInt64(this IDbCommand self, bool writeLog, long defaultvalue = -1)
- {
- return ExecuteScalarInt64(self, writeLog, null, defaultvalue);
- }
-
- ///
- /// Executes the command and returns the scalar int64 value of the first row as a string.
- ///
- /// The command instance to execute on
- /// The default value to return if no value is found
- /// The scalar int64 value of the first row as a string
- public static long ExecuteScalarInt64(this IDbCommand self, long defaultvalue = -1)
- {
- return ExecuteScalarInt64(self, true, null, defaultvalue);
- }
-
- ///
- /// Executes the command and returns the scalar int64 value of the first row as a string.
- ///
- /// The command instance to execute on
- /// The transaction to use for the command
- /// The default value to return if no value is found
- /// The scalar int64 value of the first row as a string
- public static long ExecuteScalarInt64(this IDbCommand self, IDbTransaction? transaction, long defaultvalue = -1)
- {
- self.Transaction = transaction;
- return ExecuteScalarInt64(self, true, null, defaultvalue);
- }
-
- ///
- /// Executes the command and returns the scalar int64 value of the first row as a string.
- ///
- /// The command instance to execute on
- /// The command string to execute
- /// The default value to return if no value is found
- /// The scalar int64 value of the first row as a string
- public static long ExecuteScalarInt64(this IDbCommand self, string? cmd, long defaultvalue = -1)
- {
- return ExecuteScalarInt64(self, true, cmd, defaultvalue);
- }
-
- ///
- /// Executes the command and returns the scalar int64 value of the first row as a string.
- ///
- /// The command instance to execute on
- /// Whether to write a log entry
- /// The command string to execute
- /// The default value to return if no value is found
- /// The scalar int64 value of the first row as a string
- public static long ExecuteScalarInt64(this IDbCommand self, bool writeLog, string? cmd, long defaultvalue)
- {
- if (cmd != null)
- self.SetCommandAndParameters(cmd);
-
- using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteScalarInt64", string.Format("ExecuteScalarInt64: {0}", self.GetPrintableCommandText())) : null)
- using (var rd = self.ExecuteReader())
- if (rd.Read())
- return ConvertValueToInt64(rd, 0, defaultvalue);
-
- return defaultvalue;
- }
-
- ///
- /// Executes the command and returns a data reader.
- ///
- /// The command instance to execute on
- /// The command string to execute
- /// The values to use as parameters. The parameters must already be added.
- /// A instance
- public static IDataReader ExecuteReader(this IDbCommand self, string cmd, Dictionary? values)
- {
- return ExecuteReader(self, true, cmd, values);
- }
-
-
- ///
- /// Executes the command and returns a data reader.
- ///
- /// The command instance to execute on
- /// The command string to execute
- /// A instance
- public static IDataReader ExecuteReader(this IDbCommand self, string cmd)
- {
- return ExecuteReader(self, true, cmd);
- }
-
- ///
- /// Executes the command and returns a data reader.
- ///
- /// The command instance to execute on
- /// Whether to write a log entry
- /// The command string to execute
- /// A instance
- public static IDataReader ExecuteReader(this IDbCommand self, bool writeLog, string? cmd)
- {
- if (cmd != null)
- self.SetCommandAndParameters(cmd);
-
- using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReader", string.Format("ExecuteReader: {0}", self.GetPrintableCommandText())) : null)
- return self.ExecuteReader();
- }
-
- ///
- /// Executes the command and returns a data reader.
- ///
- /// The command instance to execute on
- /// Whether to write a log entry
- /// The command string to execute
- /// The values to use as parameters. The parameters must already be added.
- /// A instance
- public static IDataReader ExecuteReader(this IDbCommand self, bool writeLog, string? cmd, Dictionary? values)
- {
- if (cmd != null)
- self.SetCommandAndParameters(cmd);
-
- if (values != null && values.Count > 0)
- self.SetParameterValues(values);
-
- using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReader", string.Format("ExecuteReader: {0}", self.GetPrintableCommandText())) : null)
- return self.ExecuteReader();
- }
-
- ///
- /// Executes the given command string `cmd` on the given database command `self` with the given values `values` and returns an enumerable of data readers.
- ///
- /// The database command to execute on.
- /// The command string to execute.
- ///
- public static IEnumerable ExecuteReaderEnumerable(this IDbCommand self, string cmd)
- {
- using var rd = ExecuteReader(self, cmd);
- while (rd.Read())
- yield return rd;
- }
-
- ///
- /// Executes the given command string `cmd` on the given database command `self` with the given values `values` and returns an enumerable of data readers.
- ///
- /// The database command to execute on.
- /// The command string to execute.
- ///
- public static IEnumerable ExecuteReaderEnumerable(this IDbCommand self)
- {
- using var rd = self.ExecuteReader();
- while (rd.Read())
- yield return rd;
- }
-
- ///
- /// Converts the value at the given index of the given data reader to a string.
- ///
- /// The data reader to convert the value from.
- /// The index of the value to convert.
- /// The value at the given index as a string.
- public static string? ConvertValueToString(this IDataReader reader, int index)
- {
- var v = reader.GetValue(index);
- if (v == null || v == DBNull.Value)
- return null;
-
- return v.ToString();
- }
-
- ///
- /// Converts the value at the given index of the given data reader to a long.
- ///
- /// The data reader to convert the value from.
- /// The index of the value to convert.
- /// The default value to return if the value is null or cannot be converted.
- /// The value at the given index as a long.
- public static long ConvertValueToInt64(this IDataReader reader, int index, long defaultvalue = -1)
+ public static long ConvertValueToInt64(this SqliteDataReader reader, int index, long defaultvalue = -1)
{
try
{
@@ -435,101 +51,162 @@ public static class ExtensionMethods
return defaultvalue;
}
- ///
- /// Creates a command with the given transaction.
- ///
- /// The connection to create the command on.
- /// The transaction to use for the command.
- /// The command string to create the command with.
- /// A new command with the given transaction.
- public static IDbCommand CreateCommand(this IDbConnection self, IDbTransaction? transaction, string? cmdtext = null)
+ 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();
+ }
+
+ // public static SqliteCommand CreateCommand(this SqliteConnection self, string cmdtext)
+ // {
+ // var cmd = self.CreateCommand();
+ // cmd.SetCommandAndParameters(cmdtext);
+ // cmd.PrepareAsync().Wait();
+
+ // return cmd;
+ // }
+
+ public static async Task CreateCommandAsync(this SqliteConnection self, string cmdtext)
{
var cmd = self.CreateCommand();
- cmd.Transaction = transaction;
- if (!string.IsNullOrEmpty(cmdtext))
- cmd.SetCommandAndParameters(cmdtext);
- return cmd;
- }
-
- ///
- /// Sets the command text and adds parameters to the command.
- ///
- /// The command to set the command text and add parameters to.
- /// The transaction to use for the command.
- /// The command text to set.
- /// The command with the command text set and parameters added.
- public static IDbCommand SetCommandAndParameters(this IDbCommand cmd, IDbTransaction transaction, string cmdtext)
- {
- cmd.Transaction = transaction;
- return cmd.SetCommandAndParameters(cmdtext);
- }
-
- ///
- /// Sets the command text and adds parameters to the command.
- ///
- /// The command to set the command text and add parameters to.
- /// The command text to set.
- /// The command with the command text set and parameters added.
- public static IDbCommand SetCommandAndParameters(this IDbCommand cmd, string cmdtext)
- {
- cmd.CommandText = cmdtext;
- cmd.Parameters.Clear();
-
-#if DEBUG
- if (cmd.CommandText.Contains("?", StringComparison.OrdinalIgnoreCase))
- throw new ArgumentException("Command text cannot contain '?' as a parameter placeholder, use '@' instead.", nameof(cmdtext));
-#endif
-
- var parameters = Regex.Matches(cmdtext, @"@\w+");
- var found = new HashSet(StringComparer.OrdinalIgnoreCase);
- foreach (Match match in parameters)
- {
- if (found.Contains(match.Value))
- continue;
- found.Add(match.Value);
- cmd.AddNamedParameter(match.Value);
- }
+ cmd.SetCommandAndParameters(Library.Utility.Utility.FormatInvariant(cmdtext));
+ await cmd.PrepareAsync();
return cmd;
}
- ///
- /// 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.
- ///
- /// The connection to create the transaction on
- /// The transaction
- public static IDbTransaction BeginTransactionSafe(this IDbConnection self)
+ public static async Task ExecuteNonQueryAsync(this SqliteCommand self, string cmd)
{
- var transaction = self.BeginTransaction();
- // using (var cmd = self.CreateCommand(transaction, "BEGIN IMMEDIATE;"))
- // try { cmd.ExecuteNonQuery(); }
- // catch { /* ignore */ }
+ self.CommandText = cmd;
+ self.Parameters.Clear();
- return transaction;
+ // TODO "late format string-ing"
+ using (new Logging.Timer(LOGTAG, "ExecuteNonQueryAsync", $"ExecuteNonQueryAsync: {self.CommandText}"))
+ return await self.ExecuteNonQueryAsync().ConfigureAwait(false);
}
+ public static async Task ExecuteNonQueryAsync(this SqliteCommand self, string cmd, Dictionary values)
+ {
+ return await ExecuteNonQueryAsync(self, true, cmd, values);
+ }
- ///
- /// Creates a command with the given command string, and adds parameters to fit the input
- ///
- /// The connection to create the command on
- /// The command string to create the command with
- /// The command with the parameters added
- public static IDbCommand CreateCommand(this IDbConnection self, string cmdtext)
- => CreateCommand(self, null, cmdtext);
+ public static async Task ExecuteNonQueryAsync(this SqliteCommand self, bool writeLog, string? cmd, Dictionary values)
+ {
+ if (cmd != null)
+ self.SetCommandAndParameters(cmd);
- ///
- /// Expands the given parameter name to a list of parameters for an IN clause.
- ///
- /// The type of the command
- /// The type of the values
- /// The command to expand the parameter for
- /// The original parameter name to expand
- /// The values to expand the parameter for
- public static TCommand ExpandInClauseParameter(this TCommand cmd, string originalParamName, IEnumerable values)
- where TCommand : IDbCommand
+ if (values != null && values.Count > 0)
+ self.SetParameterValues(values);
+
+ using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteNonQuery", string.Format("ExecuteNonQuery: {0}", self.GetPrintableCommandText())) : null)
+ return await self.ExecuteNonQueryAsync();
+ }
+
+ //public static SqliteDataReader ExecuteReader(this SqliteCommand self, string cmd)
+ //{
+ // return ExecuteReader(self, true, cmd);
+ //}
+
+ //public static SqliteDataReader ExecuteReader(this SqliteCommand self, bool writeLog, string? cmd)
+ //{
+ // if (cmd != null)
+ // self.SetCommandAndParameters(cmd);
+ // using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReader", string.Format("ExecuteReader: {0}", self.GetPrintableCommandText())) : null)
+ // return self.ExecuteReader();
+ //}
+
+ public static async Task ExecuteReaderAsync(this SqliteCommand self, string cmd)
+ {
+ self.CommandText = cmd;
+ self.Parameters.Clear();
+
+ // TODO "late format string-ing"
+ using (new Logging.Timer(LOGTAG, "ExecuteReaderAsync", $"ExecuteReaderAsync: {self.CommandText}"))
+ return await self.ExecuteReaderAsync().ConfigureAwait(false);
+ }
+
+ public static async Task ExecuteReaderAsync(this SqliteCommand self, string cmd, Dictionary? values)
+ {
+ return await ExecuteReaderAsync(self, true, cmd, values);
+ }
+
+ public static async Task ExecuteReaderAsync(this SqliteCommand self, bool writeLog, string? cmd, Dictionary? values)
+ {
+ if (cmd != null)
+ self.SetCommandAndParameters(cmd);
+
+ if (values != null && values.Count > 0)
+ self.SetParameterValues(values);
+
+ using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReader", string.Format("ExecuteReader: {0}", self.GetPrintableCommandText())) : null)
+ return await self.ExecuteReaderAsync();
+ }
+
+ public static async IAsyncEnumerable ExecuteReaderEnumerableAsync(this SqliteCommand self)
+ {
+ using var rd = await self.ExecuteReaderAsync();
+ while (await rd.ReadAsync())
+ yield return rd;
+ }
+
+ public static IAsyncEnumerable ExecuteReaderEnumerableAsync(this SqliteCommand self, string cmd)
+ {
+ self.CommandText = cmd;
+ return ExecuteReaderEnumerableAsync(self);
+ }
+
+ public static async Task