// 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.Linq; #nullable enable namespace Duplicati.Library.Main.Database; /// /// Extension method for /// public static class ExtensionMethods { /// /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ExtensionMethods)); /// /// Adds and sets the parameters for the given command. /// /// The type of the command /// The command to add and set the parameters for /// The parameters to add and set /// The command with the parameters added and set public static T AddAndSetParameters(this T cmd, params object?[]? parameters) where T : System.Data.IDbCommand { if (parameters != null) for (var i = 0; i < parameters.Length; i++) { var p = cmd.CreateParameter(); p.Value = parameters[i]; cmd.Parameters.Add(p); } return cmd; } /// /// Adds the given number of parameters to the command. /// /// The type of the command /// The command to add the parameters to /// The number of parameters to add /// The command with the parameters added public static T AddParameters(this T self, int count) where T : System.Data.IDbCommand { for (var i = 0; i < count; i++) self.Parameters.Add(self.CreateParameter()); return self; } /// /// Adds a parameter to the command. /// /// The type of the command /// The command to add the parameter to /// The command with the parameter added public static T AddParameter(this T self) where T : System.Data.IDbCommand { self.Parameters.Add(self.CreateParameter()); return self; } /// /// Adds a parameter to the command with the given value. /// /// The type of the command /// The command to add the parameter to /// The value of the parameter /// The optional name of the parameter /// The command with the parameter added public static T AddParameter(this T self, object? value, string? name = null) where T : System.Data.IDbCommand { var p = self.CreateParameter(); p.Value = value; if (!string.IsNullOrEmpty(name)) p.ParameterName = name; 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, params object?[] values) where T : System.Data.IDbCommand { for (var i = 0; i < values.Length; i++) ((System.Data.IDataParameter)self.Parameters[i]!).Value = values[i]; 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 index 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, int index, object? value) where T : System.Data.IDbCommand { ((System.Data.IDataParameter)self.Parameters[index]!).Value = value; 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 System.Data.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 System.Data.IDbCommand self, bool writeLog) { return ExecuteNonQuery(self, writeLog, null, 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 System.Data.IDbCommand self, string cmd, params object?[]? 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 System.Data.IDbCommand self, bool writeLog, string? cmd, params object?[]? values) { if (cmd != null) self.CommandText = cmd; if (values != null && values.Length > 0) { self.Parameters.Clear(); foreach (var n in values) self.AddParameter(n); } using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteNonQuery", string.Format("ExecuteNonQuery: {0}", self.GetPrintableCommandText())) : null) 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 values to use as parameters. The parameters must already be added. /// The scalar value of the first row public static object? ExecuteScalar(this System.Data.IDbCommand self, string cmd, params object[] values) { return ExecuteScalar(self, true, cmd, values); } /// /// 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 values to use as parameters. The parameters must already be added. /// The scalar value of the first row public static object? ExecuteScalar(this System.Data.IDbCommand self, bool writeLog, string cmd, params object[] values) { if (cmd != null) self.CommandText = cmd; if (values != null && values.Length > 0) { self.Parameters.Clear(); foreach (var n in values) self.AddParameter(n); } 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 System.Data.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 System.Data.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 /// 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 System.Data.IDbCommand self, bool writeLog, string? cmd, long defaultvalue = -1) { return ExecuteScalarInt64(self, writeLog, cmd, defaultvalue, null); } /// /// 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 System.Data.IDbCommand self, string? cmd, long defaultvalue = -1) { return ExecuteScalarInt64(self, true, cmd, defaultvalue, null); } /// /// 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 values to use as parameters. The parameters must already be added. /// The scalar int64 value of the first row as a string public static long ExecuteScalarInt64(this System.Data.IDbCommand self, string? cmd, long defaultvalue, params object?[]? values) { return ExecuteScalarInt64(self, true, cmd, defaultvalue, values); } /// /// 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 values to use as parameters. The parameters must already be added. /// The scalar int64 value of the first row as a string public static long ExecuteScalarInt64(this System.Data.IDbCommand self, bool writeLog, string? cmd, long defaultvalue, params object?[]? values) { if (cmd != null) self.CommandText = cmd; if (values != null && values.Length > 0) { self.Parameters.Clear(); foreach (var n in values) self.AddParameter(n); } 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 System.Data.IDataReader ExecuteReader(this System.Data.IDbCommand self, string cmd, params object[] values) { return ExecuteReader(self, true, cmd, values); } /// /// 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 System.Data.IDataReader ExecuteReader(this System.Data.IDbCommand self, bool writeLog, string cmd, params object[] values) { if (cmd != null) self.CommandText = cmd; if (values != null && values.Length > 0) { self.Parameters.Clear(); foreach (var n in values) self.AddParameter(n); } using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReader", string.Format("ExecuteReader: {0}", self.GetPrintableCommandText())) : null) return self.ExecuteReader(); } /// /// Executes the command and returns an enumerable of data readers. /// /// The database command to execute. /// An enumerable of data readers. public static IEnumerable ExecuteReaderEnumerable(this System.Data.IDbCommand self) { using var rd = self.ExecuteReader(); 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. /// The values that the command string should be parameterized with. /// public static IEnumerable ExecuteReaderEnumerable(this System.Data.IDbCommand self, string cmd, params object[] values) { using var rd = ExecuteReader(self, cmd, values); 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 System.Data.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 System.Data.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. /// A new command with the given transaction. public static System.Data.IDbCommand CreateCommand(this System.Data.IDbConnection self, System.Data.IDbTransaction transaction) { var con = self.CreateCommand(); con.Transaction = transaction; return con; } }