// Copyright (C) 2025, 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.Tasks; using Duplicati.Library.Utility; using Microsoft.Data.Sqlite; #nullable enable namespace Duplicati.Library.Main.Database; /// /// Extension method for /// public static partial class ExtensionMethods { // TODO parameter types and sizes public static long ConvertValueToInt64(this SqliteDataReader reader, int index, long defaultvalue = -1) { try { if (!reader.IsDBNull(index)) return reader.GetInt64(index); } catch { } return defaultvalue; } 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) { return CreateCommandAsync(self, cmdtext).Await(); } public static async Task CreateCommandAsync(this SqliteConnection self, string cmdtext) { var cmd = self.CreateCommand(); cmd.SetCommandAndParameters(cmdtext); await cmd.PrepareAsync(); return cmd; } public static SqliteCommand CreateCommand(this SqliteConnection self, SqliteTransaction transaction) { var cmd = self.CreateCommand(); cmd.SetTransaction(transaction); return cmd; } internal static SqliteCommand CreateCommand(this SqliteConnection self, ReusableTransaction rtr) { return self.CreateCommand(rtr.Transaction); } public static async Task ExecuteNonQueryAsync(this SqliteCommand self, bool writeLog) { return await ExecuteNonQueryAsync(self, writeLog, null); } public static async Task ExecuteNonQueryAsync(this SqliteCommand self, bool writeLog, string? cmd) { if (cmd != null) self.SetCommandAndParameters(cmd); using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteNonQueryAsync", string.Format("ExecuteNonQueryAsync: {0}", self.GetPrintableCommandText())) : null) return await self.ExecuteNonQueryAsync(); } public static async Task ExecuteNonQueryAsync(this SqliteCommand self, string cmdtext) { self.SetCommandAndParameters(cmdtext); // 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); } public static async Task ExecuteNonQueryAsync(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, "ExecuteNonQueryAsync", string.Format("ExecuteNonQueryAsync: {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, bool writeLog, string? cmd) { if (cmd != null) self.SetCommandAndParameters(cmd); using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReaderAsync", string.Format("ExecuteReaderAsync: {0}", self.GetPrintableCommandText())) : null) return await self.ExecuteReaderAsync(); } public static async Task ExecuteReaderAsync(this SqliteCommand self, string cmdtext) { self.SetCommandAndParameters(cmdtext); // 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 cmdtext) { self.SetCommandAndParameters(cmdtext); return ExecuteReaderEnumerableAsync(self); } public static async Task ExecuteScalarAsync(this SqliteCommand self) { return await self.ExecuteScalarAsync().ConfigureAwait(false); } public static async Task ExecuteScalarAsync(this SqliteCommand self, bool writeLog, string? cmd) { if (cmd != null) self.SetCommandAndParameters(cmd); using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteScalarAsync", string.Format("ExecuteScalarAsync: {0}", self.GetPrintableCommandText())) : null) return await self.ExecuteScalarAsync(); } public static async Task ExecuteScalarAsync(this SqliteCommand self, string cmdtext) { self.SetCommandAndParameters(cmdtext); return await ExecuteScalarAsync(self).ConfigureAwait(false); } //public static long ExecuteScalarInt64(this SqliteCommand self, long defaultvalue = -1) //{ // return ExecuteScalarInt64(self, true, null, defaultvalue); //} //public static long ExecuteScalarInt64(this SqliteCommand 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; //} public static async Task ExecuteScalarInt64Async(this SqliteCommand self, bool writeLog, long defaultvalue = -1) { return await ExecuteScalarInt64Async(self, writeLog, null, defaultvalue); } public static async Task ExecuteScalarInt64Async(this SqliteCommand self, bool writeLog, string? cmd, long defaultvalue) { if (cmd != null) self.SetCommandAndParameters(cmd); using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteScalarInt64Async", string.Format("ExecuteScalarInt64Async: {0}", self.GetPrintableCommandText())) : null) using (var rd = await self.ExecuteReaderAsync()) if (await rd.ReadAsync()) return ConvertValueToInt64(rd, 0, defaultvalue); return defaultvalue; } public static async Task ExecuteScalarInt64Async(this SqliteCommand self, long defaultvalue = -1) { using (var rd = await self.ExecuteReaderAsync().ConfigureAwait(false)) if (await rd.ReadAsync().ConfigureAwait(false)) return ConvertValueToInt64(rd, 0, defaultvalue); return defaultvalue; } public static async Task ExecuteScalarInt64Async(this SqliteCommand self, string cmdtext, long defaultvalue = -1) { self.SetCommandAndParameters(cmdtext); return await ExecuteScalarInt64Async(self, defaultvalue).ConfigureAwait(false); } public static SqliteCommand ExpandInClauseParameter(this SqliteCommand cmd, string originalParamName, IEnumerable values) { if (string.IsNullOrWhiteSpace(originalParamName) || !originalParamName.StartsWith("@")) throw new ArgumentException("Parameter name must start with '@'", nameof(originalParamName)); foreach (var p in cmd.Parameters) if (p is IDataParameter parameter && parameter.ParameterName.Equals(originalParamName, StringComparison.OrdinalIgnoreCase)) { cmd.Parameters.Remove(parameter); break; } foreach ((var value, var index) in values.Select((value, index) => (value, index))) cmd.AddNamedParameter($"{originalParamName}{index}", value); var inClause = string.Join(", ", values.Select((_, index) => $"{originalParamName}{index}")); if (string.IsNullOrWhiteSpace(inClause) && values.Any()) throw new ArgumentException("IN clause cannot be empty", nameof(values)); #if DEBUG if (!cmd.CommandText.Contains(originalParamName, StringComparison.OrdinalIgnoreCase)) throw new ArgumentException($"Command text does not contain parameter '{originalParamName}'", nameof(originalParamName)); #endif cmd.CommandText = cmd.CommandText.Replace(originalParamName, inClause, StringComparison.OrdinalIgnoreCase); return cmd; } internal static async Task ExpandInClauseParameterAsync(this SqliteCommand cmd, string originalParamName, TemporaryDbValueList values) { if (string.IsNullOrWhiteSpace(originalParamName) || !originalParamName.StartsWith("@")) throw new ArgumentException("Parameter name must start with '@'", nameof(originalParamName)); if (!values.IsTableCreated) return ExpandInClauseParameter(cmd, originalParamName, values.Values); // We have a temporary table, so we need to replace the parameter with the table name cmd.CommandText = cmd.CommandText.Replace(originalParamName, await values.GetInClause(), StringComparison.OrdinalIgnoreCase); return cmd; } public static SqliteCommand SetCommandAndParameters(this SqliteCommand cmd, string cmdtext) { cmd.CommandText = Library.Utility.Utility.FormatInvariant(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 = MyRegex().Matches(cmdtext); 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); } return cmd; } /// /// New edition /// /// /// /// /// public static SqliteCommand SetParameterValue(this SqliteCommand self, string name, object? value) { #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 self.Parameters[name].Value = value; return self; } // Special case for DateTime, as we need to convert it to a long public static SqliteCommand SetParameterValue(this SqliteCommand self, string name, DateTime value) { self.Parameters[name].Value = Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(value); return self; } public static SqliteCommand SetParameterValues(this SqliteCommand self, Dictionary values) { foreach (var kvp in values) self.Parameters[kvp.Key].Value = kvp.Value; return self; } public static SqliteCommand SetTransaction(this SqliteCommand self, SqliteTransaction transaction) { self.Transaction = transaction; return self; } internal static SqliteCommand SetTransaction(this SqliteCommand self, ReusableTransaction rtr) { self.Transaction = rtr.Transaction; return self; } /// /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ExtensionMethods)); [GeneratedRegex(@"@\w+")] private static partial Regex MyRegex(); // TODO commennt from here on /// /// 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 SqliteCommand AddNamedParameter(this SqliteCommand self, string name, object? value = null) { 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 SqliteCommand self) { var txt = self.CommandText; // TODO not adjusted to named parameters - it's using positional parameters. 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 = string.Concat(txt.AsSpan(0, ix), v, txt.AsSpan(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) // { // try // { // if (!reader.IsDBNull(index)) // return reader.GetInt64(index); // } // catch // { // } // 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) // { // 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); // } // 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) // //{ // // var transaction = self.BeginTransaction(); // // // using (var cmd = self.CreateCommand(transaction, "BEGIN IMMEDIATE;")) // // // try { cmd.ExecuteNonQuery(); } // // // catch { // // // ignore // // } // // // // return transaction; // //} // /// // /// 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); // /// // /// 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 (string.IsNullOrWhiteSpace(originalParamName) || !originalParamName.StartsWith("@")) // throw new ArgumentException("Parameter name must start with '@'", nameof(originalParamName)); // foreach (var p in cmd.Parameters) // if (p is IDataParameter parameter && parameter.ParameterName.Equals(originalParamName, StringComparison.OrdinalIgnoreCase)) // { // cmd.Parameters.Remove(parameter); // break; // } // foreach ((var value, var index) in values.Select((value, index) => (value, index))) // cmd.AddNamedParameter($"{originalParamName}{index}", value); // var inClause = string.Join(", ", values.Select((_, index) => $"{originalParamName}{index}")); // if (string.IsNullOrWhiteSpace(inClause) && values.Any()) // throw new ArgumentException("IN clause cannot be empty", nameof(values)); // #if DEBUG // if (!cmd.CommandText.Contains(originalParamName, StringComparison.OrdinalIgnoreCase)) // throw new ArgumentException($"Command text does not contain parameter '{originalParamName}'", nameof(originalParamName)); // #endif // cmd.CommandText = cmd.CommandText.Replace(originalParamName, inClause, StringComparison.OrdinalIgnoreCase); // return cmd; // } // /// // /// Expands the given parameter name to a list of parameters for an IN clause. // /// // /// The type of the command // /// The command to expand the parameter for // /// The original parameter name to expand // /// The values to expand the parameter for // public static T ExpandInClauseParameter(this T cmd, string originalParamName, TemporaryDbValueList values) // where T : IDbCommand // { // if (string.IsNullOrWhiteSpace(originalParamName) || !originalParamName.StartsWith("@")) // throw new ArgumentException("Parameter name must start with '@'", nameof(originalParamName)); // if (!values.IsTableCreated) // return ExpandInClauseParameter(cmd, originalParamName, values.Values); // // We have a temporary table, so we need to replace the parameter with the table name // cmd.CommandText = cmd.CommandText.Replace(originalParamName, values.GetInClause(), StringComparison.OrdinalIgnoreCase); // return cmd; // } // // TODO comment to here }