From 0e4ab595329a54027965712a6bc139d4e996f25d Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Wed, 27 Dec 2023 20:29:01 +0100 Subject: [PATCH 01/11] DatabaseWrapper: First draft of using query parameters instead of converting everything to string. Only DatabaseWrapper.SqlServer has been modified at this point. --- .../DatabaseClientBase.cs | 35 +- .../DatabaseHelperBase.cs | 32 +- .../DatabaseWrapper.Core.xml | 43 +- .../DatabaseClient.cs | 115 ++- .../DatabaseWrapper.SqlServer.xml | 61 +- .../SqlServerHelper.cs | 900 ++++-------------- 6 files changed, 349 insertions(+), 837 deletions(-) diff --git a/src/DatabaseWrapper.Core/DatabaseClientBase.cs b/src/DatabaseWrapper.Core/DatabaseClientBase.cs index 160f6bc..85309b1 100644 --- a/src/DatabaseWrapper.Core/DatabaseClientBase.cs +++ b/src/DatabaseWrapper.Core/DatabaseClientBase.cs @@ -302,12 +302,28 @@ public abstract class DatabaseClientBase /// Cancellation token. public abstract Task TruncateAsync(string tableName, CancellationToken token = default); + /// + /// Execute a query. + /// + /// A tuple of the aatabase query defined outside of the database client and query parameters. + /// A DataTable containing the results. + public abstract DataTable Query((string Query, IEnumerable> Parameters) queryAndParameters); + /// /// Execute a query. /// /// Database query defined outside of the database client. /// A DataTable containing the results. - public abstract DataTable Query(string query); + public DataTable Query(string query) + => Query((query, null)); + + /// + /// Execute a query. + /// + /// A tuple of the aatabase query defined outside of the database client and query parameters. + /// Cancellation token. + /// A DataTable containing the results. + public abstract Task QueryAsync((string Query, IEnumerable> Parameters) queryAndParameters, CancellationToken token = default(CancellationToken)); /// /// Execute a query. @@ -315,7 +331,8 @@ public abstract class DatabaseClientBase /// Database query defined outside of the database client. /// Cancellation token. /// A DataTable containing the results. - public abstract Task QueryAsync(string query, CancellationToken token = default); + public Task QueryAsync(string query, CancellationToken token = default(CancellationToken)) + => QueryAsync((query, null), token); /// /// Determine if records exist by filter. @@ -370,20 +387,6 @@ public abstract class DatabaseClientBase /// The sum of the specified column from the matching rows. public abstract Task SumAsync(string tableName, string fieldName, Expr filter, CancellationToken token = default); - /// - /// Create a string timestamp from the given DateTime. - /// - /// DateTime. - /// A string with formatted timestamp. - public abstract string Timestamp(DateTime ts); - - /// - /// Create a string timestamp with offset from the given DateTimeOffset. - /// - /// DateTimeOffset. - /// A string with formatted timestamp. - public abstract string TimestampOffset(DateTimeOffset ts); - /// /// Sanitize an input string. /// diff --git a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs index 47dbb04..d197d88 100644 --- a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs +++ b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs @@ -5,6 +5,8 @@ namespace DatabaseWrapper.Core { + using QueryAndParameters = System.ValueTuple>>; + /// /// Base implementation of helper properties and methods. /// @@ -102,7 +104,7 @@ public abstract class DatabaseHelperBase /// Expression filter. /// Result order. /// String. - public abstract string SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder); + public abstract QueryAndParameters SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder); /// /// Retrieve a query used for inserting data into a table. @@ -110,7 +112,7 @@ public abstract class DatabaseHelperBase /// The table in which you wish to INSERT. /// The key-value pairs for the row you wish to INSERT. /// String. - public abstract string InsertQuery(string tableName, Dictionary keyValuePairs); + public abstract QueryAndParameters InsertQuery(string tableName, Dictionary keyValuePairs); /// /// Retrieve a query for inserting multiple rows into a table. @@ -118,7 +120,7 @@ public abstract class DatabaseHelperBase /// The table in which you wish to INSERT. /// List of dictionaries containing key-value pairs for the rows you wish to INSERT. /// String. - public abstract string InsertMultipleQuery(string tableName, List> keyValuePairList); + public abstract QueryAndParameters InsertMultipleQuery(string tableName, List> keyValuePairList); /// /// Retrieve a query for updating data in a table. @@ -127,7 +129,7 @@ public abstract class DatabaseHelperBase /// The key-value pairs for the data you wish to UPDATE. /// The expression containing the UPDATE filter (i.e. WHERE clause data). /// String. - public abstract string UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter); + public abstract QueryAndParameters UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter); /// /// Retrieve a query for deleting data from a table. @@ -135,7 +137,7 @@ public abstract class DatabaseHelperBase /// Table name. /// Expression filter. /// String. - public abstract string DeleteQuery(string tableName, Expr filter); + public abstract QueryAndParameters DeleteQuery(string tableName, Expr filter); /// /// Retrieve a query for truncating a table. @@ -150,7 +152,7 @@ public abstract class DatabaseHelperBase /// Table name. /// Expression filter. /// String. - public abstract string ExistsQuery(string tableName, Expr filter); + public abstract QueryAndParameters ExistsQuery(string tableName, Expr filter); /// /// Retrieve a query that returns a count of the number of rows matching the supplied conditions. @@ -159,7 +161,7 @@ public abstract class DatabaseHelperBase /// Column name to use to temporarily store the result. /// Expression filter. /// String. - public abstract string CountQuery(string tableName, string countColumnName, Expr filter); + public abstract QueryAndParameters CountQuery(string tableName, string countColumnName, Expr filter); /// /// Retrieve a query that sums the values found in the specified field. @@ -169,21 +171,7 @@ public abstract class DatabaseHelperBase /// Column name to temporarily store the result. /// Expression filter. /// String. - public abstract string SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter); - - /// - /// Retrieve a timestamp in the database format. - /// - /// DateTime. - /// String. - public abstract string GenerateTimestamp(DateTime ts); - - /// - /// Retrieve a timestamp offset in the database format. - /// - /// DateTimeOffset. - /// String. - public abstract string GenerateTimestampOffset(DateTimeOffset ts); + public abstract QueryAndParameters SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter); #endregion } diff --git a/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml b/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml index 40fa937..dfc6806 100644 --- a/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml +++ b/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml @@ -347,6 +347,13 @@ The table you wish to TRUNCATE. Cancellation token. + + + Execute a query. + + A tuple of the aatabase query defined outside of the database client and query parameters. + A DataTable containing the results. + Execute a query. @@ -354,6 +361,14 @@ Database query defined outside of the database client. A DataTable containing the results. + + + Execute a query. + + A tuple of the aatabase query defined outside of the database client and query parameters. + Cancellation token. + A DataTable containing the results. + Execute a query. @@ -415,20 +430,6 @@ Cancellation token. The sum of the specified column from the matching rows. - - - Create a string timestamp from the given DateTime. - - DateTime. - A string with formatted timestamp. - - - - Create a string timestamp with offset from the given DateTimeOffset. - - DateTimeOffset. - A string with formatted timestamp. - Sanitize an input string. @@ -588,20 +589,6 @@ Expression filter. String. - - - Retrieve a timestamp in the database format. - - DateTime. - String. - - - - Retrieve a timestamp offset in the database format. - - DateTimeOffset. - String. - Database query event arguments. diff --git a/src/DatabaseWrapper.SqlServer/DatabaseClient.cs b/src/DatabaseWrapper.SqlServer/DatabaseClient.cs index 51e20c7..20f8b25 100644 --- a/src/DatabaseWrapper.SqlServer/DatabaseClient.cs +++ b/src/DatabaseWrapper.SqlServer/DatabaseClient.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Data; using System.Data.SqlClient; +using static System.FormattableString; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -747,10 +748,11 @@ public override async Task TruncateAsync(string tableName, CancellationToken tok /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Tuple of a database query defined outside of the database client and of query parameters /// A DataTable containing the results. - public override DataTable Query(string query) + public override DataTable Query((string Query, IEnumerable> Parameters) queryAndParameters) { + (var query, var parameters) = queryAndParameters; if (String.IsNullOrEmpty(query)) throw new ArgumentNullException(query); if (query.Length > MaxStatementLength) throw new ArgumentException("Query exceeds maximum statement length of " + MaxStatementLength + " characters."); @@ -768,11 +770,15 @@ public override DataTable Query(string query) conn.Open(); #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities - using (SqlDataAdapter sda = new SqlDataAdapter(query, conn)) + using (var cmd = new SqlCommand(query, conn)) { -#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities + AddParametersIfAny(cmd, parameters); + using (SqlDataAdapter sda = new SqlDataAdapter(cmd)) + { - sda.Fill(result); +#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities + sda.Fill(result); + } } conn.Dispose(); @@ -809,11 +815,12 @@ public override DataTable Query(string query) /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Tuple of a database query defined outside of the database client and of query parameters /// Cancellation token. /// A DataTable containing the results. - public override async Task QueryAsync(string query, CancellationToken token = default) + public override async Task QueryAsync((string Query, IEnumerable> Parameters) queryAndParameters, CancellationToken token = default) { + (var query, var parameters) = queryAndParameters; if (String.IsNullOrEmpty(query)) throw new ArgumentNullException(query); if (query.Length > MaxStatementLength) throw new ArgumentException("Query exceeds maximum statement length of " + MaxStatementLength + " characters."); @@ -831,11 +838,15 @@ public override async Task QueryAsync(string query, CancellationToken await conn.OpenAsync(token).ConfigureAwait(false); #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities - using (SqlDataAdapter sda = new SqlDataAdapter(query, conn)) + using (var cmd = new SqlCommand(query, conn)) { + AddParametersIfAny(cmd, parameters); + using (SqlDataAdapter sda = new SqlDataAdapter(cmd)) + { #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities4 - sda.Fill(result); + sda.Fill(result); + } } #if NET6_0_OR_GREATER @@ -997,26 +1008,6 @@ public override async Task SumAsync(string tableName, string fieldName, return 0m; } - /// - /// Create a string timestamp from the given DateTime. - /// - /// DateTime. - /// A string with formatted timestamp. - public override string Timestamp(DateTime ts) - { - return _Helper.GenerateTimestamp(ts); - } - - /// - /// Create a string timestamp with offset from the given DateTimeOffset. - /// - /// DateTimeOffset. - /// A string with formatted timestamp. - public override string TimestampOffset(DateTimeOffset ts) - { - return _Helper.GenerateTimestampOffset(ts); - } - /// /// Sanitize an input string. /// @@ -1032,6 +1023,72 @@ public override string SanitizeString(string s) #region Private-Methods + static readonly Dictionary SqlTypeMap = new Dictionary() { + { typeof(Int32), SqlDbType.Int }, + { typeof(Int64), SqlDbType.BigInt }, + { typeof(double), SqlDbType.Float }, + { typeof(Guid), SqlDbType.UniqueIdentifier }, + { typeof(byte[]), SqlDbType.Image }, + { typeof(DateTimeOffset), SqlDbType.DateTimeOffset }, + }; + + private static void AddParametersIfAny(SqlCommand cmd, IEnumerable> parameters) + { + if (parameters==null) + { + return; + } + foreach (var kv in parameters) + { + int param_index = cmd.Parameters.Count; + var param_name = kv.Key; + var p = kv.Value; + if (p == null) + { + cmd.Parameters.Add(new SqlParameter(param_name, SqlDbType.NVarChar)); + cmd.Parameters[param_index].Value = DBNull.Value; + continue; + } + var t = p.GetType(); + SqlDbType sql_type; + if (SqlTypeMap.TryGetValue(t, out sql_type)) + { + cmd.Parameters.Add(param_name, sql_type); + cmd.Parameters[param_index].Value = p; + continue; + } + if (t == typeof(string)) + { + var s_string = p as string; + cmd.Parameters.Add(param_name, s_string.Length > 4000 ? System.Data.SqlDbType.NText : System.Data.SqlDbType.NVarChar); + cmd.Parameters[param_index].Value = s_string; + continue; + } + if (t == typeof(bool)) { + cmd.Parameters.Add(param_name, System.Data.SqlDbType.Bit); + cmd.Parameters[param_index].Value = (bool)p ? 1 : 0; + continue; + } + if (t == typeof(DateTime)) + { + var dt_object = DBNull.Value as object; + var dt_datetime = (DateTime)p; + if (dt_datetime != DateTime.MinValue) + { + var dt = dt_datetime.ToLocalTime(); + if (dt != DateTime.MinValue) + { + dt_object = dt; + } + } + cmd.Parameters.Add(param_name, System.Data.SqlDbType.DateTime); + cmd.Parameters[param_index].Value = dt_object; + continue; + } + throw new ApplicationException(Invariant($"DatabaseClient: Unknown type: {t.Name}")); + } + } + /// /// Dispose of the object. /// diff --git a/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml b/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml index 570a3a5..2e53840 100644 --- a/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml +++ b/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml @@ -309,18 +309,18 @@ The table you wish to TRUNCATE. Cancellation token. - + Execute a query. - Database query defined outside of the database client. + Tuple of a database query defined outside of the database client and of query parameters A DataTable containing the results. - + Execute a query. - Database query defined outside of the database client. + Tuple of a database query defined outside of the database client and of query parameters Cancellation token. A DataTable containing the results. @@ -377,20 +377,6 @@ Cancellation token. The sum of the specified column from the matching rows. - - - Create a string timestamp from the given DateTime. - - DateTime. - A string with formatted timestamp. - - - - Create a string timestamp with offset from the given DateTimeOffset. - - DateTimeOffset. - A string with formatted timestamp. - Sanitize an input string. @@ -409,16 +395,6 @@ SQL Server implementation of helper properties and methods. - - - Timestamp format for use in DateTime.ToString([format]). - - - - - Timestamp offset format for use in DateTimeOffset.ToString([format]). - - Build a connection string from DatabaseSettings. @@ -556,26 +532,33 @@ Expression filter. String. - + - Retrieve a timestamp in the database format. + Extract the table name from an encapsulated name. - DateTime. + String. String. - + - Retrieve a timestamp offset in the database format. + Append object as the parameter to the parameters list. + + Returns the parameter name, that is to be used in the query currently under construction. - DateTimeOffset. - String. + List of the parameters. + Object to be added. + Parameter name. - + - Extract the table name from an encapsulated name. + Append object as the parameter to the parameters list. + Use the object type to apply conversions, if any needed. + + Returns the parameter name, that is to be used in the query currently under construction. - String. - String. + List of the parameters. + Object to be added. + Parameter name. diff --git a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs index e29a4bb..96e97bc 100644 --- a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs +++ b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; +using static System.FormattableString; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +10,8 @@ namespace DatabaseWrapper.SqlServer { + using QueryAndParameters = System.ValueTuple>>; + /// /// SQL Server implementation of helper properties and methods. /// @@ -15,16 +19,6 @@ public class SqlServerHelper : DatabaseHelperBase { #region Public-Members - /// - /// Timestamp format for use in DateTime.ToString([format]). - /// - public new string TimestampFormat { get; set; } = "MM/dd/yyyy hh:mm:ss.fffffff tt"; - - /// - /// Timestamp offset format for use in DateTimeOffset.ToString([format]). - /// - public new string TimestampOffsetFormat { get; set; } = "MM/dd/yyyy hh:mm:ss.fffffff zzz"; - #endregion #region Private-Members @@ -335,7 +329,7 @@ public override string DropTableQuery(string tableName) /// Expression filter. /// Result order. /// String. - public override string SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder) + public override QueryAndParameters SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder) { string query = ""; string whereClause = ""; @@ -374,7 +368,10 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe query += "FROM " + PreparedTableName(tableName) + " "; // expressions - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters_list = new List>(); + if (filter != null) { + whereClause = ExpressionToWhereClause(filter, parameters_list); + } if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; @@ -394,7 +391,7 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe query += "OFFSET " + indexStart + " ROWS "; } - return query; + return (query, parameters_list); } /// @@ -403,22 +400,19 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe /// The table in which you wish to INSERT. /// The key-value pairs for the row you wish to INSERT. /// String. - public override string InsertQuery(string tableName, Dictionary keyValuePairs) + public override QueryAndParameters InsertQuery(string tableName, Dictionary keyValuePairs) { - string ret = - "INSERT INTO " + PreparedTableName(tableName) + " WITH (ROWLOCK) " + - "("; - - string keys = ""; - string vals = ""; - BuildKeysValuesFromDictionary(keyValuePairs, out keys, out vals); - - ret += keys + ") " + + var s_ret = new StringBuilder(); + var o_ret = keyValuePairs.Select(kv => new KeyValuePair("@F_" + kv.Key, kv.Value)); + s_ret.Append("INSERT INTO " + PreparedTableName(tableName) + " WITH (ROWLOCK) " + "("); + s_ret.Append(string.Join(", ", keyValuePairs.Keys.Select(k => PreparedFieldName(k)))); + s_ret.Append(") " + "OUTPUT INSERTED.* " + "VALUES " + - "(" + vals + ") "; - - return ret; + "("); + s_ret.Append(string.Join(", ", o_ret.Select(k => k.Key))); + s_ret.Append(") "); + return (s_ret.ToString(), o_ret); } /// @@ -427,36 +421,41 @@ public override string InsertQuery(string tableName, Dictionary /// The table in which you wish to INSERT. /// List of dictionaries containing key-value pairs for the rows you wish to INSERT. /// String. - public override string InsertMultipleQuery(string tableName, List> keyValuePairList) + public override QueryAndParameters InsertMultipleQuery(string tableName, List> keyValuePairList) { ValidateInputDictionaries(keyValuePairList); - string keys = BuildKeysFromDictionary(keyValuePairList[0]); - List values = BuildValuesFromDictionaries(keyValuePairList); string txn = "txn_" + RandomCharacters(12); - string ret = + var ret = new StringBuilder(); + var ret_values = new List>(); + ret.Append( "BEGIN TRANSACTION [" + txn + "] " + " BEGIN TRY " + " INSERT INTO " + PreparedTableName(tableName) + " WITH (ROWLOCK) " + - " (" + keys + ") " + - " VALUES "; + " (" + string.Join(", ", keyValuePairList[0].Keys.Select(k => PreparedFieldName(k))) + ") " + + " VALUES "); - int added = 0; - foreach (string value in values) + for (int i_dict=0; i_dict 0) ret += ","; - ret += " (" + value + ")"; - added++; + var dict = keyValuePairList[i_dict]; + var prefix = Invariant($"@F{i_dict}_"); + var this_round = dict.Select(kv => new KeyValuePair(prefix + kv.Key, kv.Value)); + if (i_dict>0) { + ret.Append(", "); + } + ret.Append("("); + ret.Append(string.Join(", ", this_round.Select(kv => kv.Key))); + ret.Append(")"); + ret_values.AddRange(this_round); } - ret += + ret.Append( " COMMIT TRANSACTION [" + txn + "] " + " END TRY " + " BEGIN CATCH " + " ROLLBACK TRANSACTION [" + txn + "] " + - " END CATCH "; - - return ret; + " END CATCH "); + return (ret.ToString(), ret_values); } /// @@ -466,18 +465,17 @@ public override string InsertMultipleQuery(string tableName, ListThe key-value pairs for the data you wish to UPDATE. /// The expression containing the UPDATE filter (i.e. WHERE clause data). /// String. - public override string UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter) + public override QueryAndParameters UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter) { - string keyValueClause = BuildKeyValueClauseFromDictionary(keyValuePairs); - + const string FIELD_PREFIX = "@F"; + var parameters = keyValuePairs.Select(kv => new KeyValuePair(FIELD_PREFIX + kv.Key, kv.Value)).ToList(); string ret = "UPDATE " + PreparedTableName(tableName) + " WITH (ROWLOCK) SET " + - keyValueClause + " " + + string.Join(", ", parameters.Select(kv => kv.Key.Substring(FIELD_PREFIX.Length) + "=" + kv.Key)) + " " + "OUTPUT INSERTED.* "; - if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter) + " "; - - return ret; + if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter, parameters) + " "; + return (ret, parameters); } /// @@ -486,14 +484,15 @@ public override string UpdateQuery(string tableName, Dictionary /// Table name. /// Expression filter. /// String. - public override string DeleteQuery(string tableName, Expr filter) + public override QueryAndParameters DeleteQuery(string tableName, Expr filter) { string ret = "DELETE FROM " + PreparedTableName(tableName) + " WITH (ROWLOCK) "; - if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter) + " "; + var parameters = new List>(); + if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter, parameters) + " "; - return ret; + return (ret, parameters); } /// @@ -512,7 +511,7 @@ public override string TruncateQuery(string tableName) /// Table name. /// Expression filter. /// String. - public override string ExistsQuery(string tableName, Expr filter) + public override QueryAndParameters ExistsQuery(string tableName, Expr filter) { string query = ""; string whereClause = ""; @@ -523,13 +522,14 @@ public override string ExistsQuery(string tableName, Expr filter) "FROM " + PreparedTableName(tableName) + " "; // expressions - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } - return query; + return (query, parameters); } /// @@ -539,7 +539,7 @@ public override string ExistsQuery(string tableName, Expr filter) /// Column name to use to temporarily store the result. /// Expression filter. /// String. - public override string CountQuery(string tableName, string countColumnName, Expr filter) + public override QueryAndParameters CountQuery(string tableName, string countColumnName, Expr filter) { string query = ""; string whereClause = ""; @@ -550,13 +550,14 @@ public override string CountQuery(string tableName, string countColumnName, Expr "FROM " + PreparedTableName(tableName) + " "; // expressions - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } - return query; + return (query, parameters); } /// @@ -567,7 +568,7 @@ public override string CountQuery(string tableName, string countColumnName, Expr /// Column name to temporarily store the result. /// Expression filter. /// String. - public override string SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter) + public override QueryAndParameters SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter) { string query = ""; string whereClause = ""; @@ -578,33 +579,14 @@ public override string SumQuery(string tableName, string fieldName, string sumCo "FROM " + PreparedTableName(tableName) + " "; // expressions - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } - return query; - } - - /// - /// Retrieve a timestamp in the database format. - /// - /// DateTime. - /// String. - public override string GenerateTimestamp(DateTime ts) - { - return ts.ToString(TimestampFormat); - } - - /// - /// Retrieve a timestamp offset in the database format. - /// - /// DateTimeOffset. - /// String. - public override string GenerateTimestampOffset(DateTimeOffset ts) - { - return ts.ToString(TimestampOffsetFormat); + return (query, parameters); } /// @@ -632,22 +614,89 @@ public string ExtractTableName(string s) #region Private-Members - private string PreparedUnicodeValue(string s) + private string PreparedFieldName(string fieldName) { - return "N" + PreparedStringValue(s); + return "[" + fieldName + "]"; } - private string PreparedFieldName(string fieldName) + /// + /// Append object as the parameter to the parameters list. + /// + /// Returns the parameter name, that is to be used in the query currently under construction. + /// + /// List of the parameters. + /// Object to be added. + /// Parameter name. + static string AppendParameter(List> parameters, object o) { - return "[" + fieldName + "]"; + var r = Invariant($"@E{parameters.Count}"); + parameters.Add(new KeyValuePair(r, o)); + return r; } - private string PreparedStringValue(string str) + /// + /// Append object as the parameter to the parameters list. + /// Use the object type to apply conversions, if any needed. + /// + /// Returns the parameter name, that is to be used in the query currently under construction. + /// + /// List of the parameters. + /// Object to be added. + /// Parameter name. + static string AppendParameterByType(List> parameters, object untypedObject) { - return "'" + SanitizeString(str) + "'"; + if (untypedObject is DateTime || untypedObject is DateTime?) + { + return AppendParameter(parameters, Convert.ToDateTime(untypedObject)); + } + if (untypedObject is int || untypedObject is long || untypedObject is decimal) + { + return AppendParameter(parameters, untypedObject); + } + if (untypedObject is bool) + { + return (bool)untypedObject ? "1" : "0"; + } + if (untypedObject is byte[]) + { + return AppendParameter(parameters, untypedObject); + } + return AppendParameter(parameters, untypedObject); } - private string ExpressionToWhereClause(Expr expr) + static readonly Dictionary BinaryOperators = new Dictionary() { + { OperatorEnum.And, "AND" }, + { OperatorEnum.Or, "OR" }, + { OperatorEnum.Equals, "=" }, + { OperatorEnum.NotEquals, "<>" }, + { OperatorEnum.GreaterThan, ">" }, + { OperatorEnum.GreaterThanOrEqualTo, ">=" }, + { OperatorEnum.LessThan, "<" }, + { OperatorEnum.LessThanOrEqualTo, "<=" }, + }; + + static readonly Dictionary InListOperators = new Dictionary() { + { OperatorEnum.In, "IN" }, + { OperatorEnum.NotIn, "NOT IN" }, + }; + + static readonly Dictionary ContainsOperators = new Dictionary() { + { OperatorEnum.Contains, ("LIKE", "OR") }, + { OperatorEnum.ContainsNot, ("NOT LIKE", "AND") }, + }; + static readonly Dictionary LikeOperators = new Dictionary() { + { OperatorEnum.StartsWith, ("LIKE", "", "%") }, + { OperatorEnum.StartsWithNot, ("NOT LIKE", "", "%") }, + { OperatorEnum.EndsWith, ("LIKE", "%", "") }, + { OperatorEnum.EndsWithNot, ("NOT LIKE", "%", "") }, + }; + static readonly Dictionary IsNullOperators = new Dictionary() { + { OperatorEnum.IsNull, "IS NULL" }, + { OperatorEnum.IsNotNull, "IS NOT NULL" }, + }; + + + private string ExpressionToWhereClause(Expr expr, List> parameters) { if (expr == null) return null; @@ -659,7 +708,7 @@ private string ExpressionToWhereClause(Expr expr) if (expr.Left is Expr) { - clause += ExpressionToWhereClause((Expr)expr.Left) + " "; + clause += ExpressionToWhereClause((Expr)expr.Left, parameters) + " "; } else { @@ -682,432 +731,68 @@ private string ExpressionToWhereClause(Expr expr) } } - switch (expr.Operator) + string operator_name; + string logic_operator_name; + string prefix; + string suffix; + (string, string) operator_pair; + (string, string, string) operator_triple; + if (BinaryOperators.TryGetValue(expr.Operator, out operator_name)) { - #region Process-By-Operators - - case OperatorEnum.And: - #region And - - if (expr.Right == null) return null; - clause += "AND "; - - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.Or: - #region Or - - if (expr.Right == null) return null; - clause += "OR "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.Equals: - #region Equals - - if (expr.Right == null) return null; - clause += "= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.NotEquals: - #region NotEquals - - if (expr.Right == null) return null; - clause += "<> "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.In: - #region In - - if (expr.Right == null) return null; - int inAdded = 0; - if (!Helper.IsList(expr.Right)) return null; - List inTempList = Helper.ObjectToList(expr.Right); - clause += " IN ("; - foreach (object currObj in inTempList) - { - if (currObj == null) continue; - if (inAdded > 0) clause += ","; - if (currObj is DateTime || currObj is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(currObj)) + "'"; - } - else if (currObj is int || currObj is long || currObj is decimal) - { - clause += currObj.ToString(); - } - else - { - clause += PreparedStringValue(currObj.ToString()); - } - inAdded++; - } - clause += ")"; - break; - - #endregion - - case OperatorEnum.NotIn: - #region NotIn - - if (expr.Right == null) return null; - int notInAdded = 0; - if (!Helper.IsList(expr.Right)) return null; - List notInTempList = Helper.ObjectToList(expr.Right); - clause += " NOT IN ("; - foreach (object currObj in notInTempList) - { - if (currObj == null) continue; - if (notInAdded > 0) clause += ","; - if (currObj is DateTime || currObj is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(currObj)) + "'"; - } - else if (currObj is int || currObj is long || currObj is decimal) - { - clause += currObj.ToString(); - } - else - { - clause += PreparedStringValue(currObj.ToString()); - } - notInAdded++; - } - clause += ")"; - break; - - #endregion - - case OperatorEnum.Contains: - #region Contains - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - "OR " + PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString() + "%") + - "OR " + PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.ContainsNot: - #region ContainsNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - "OR " + PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString() + "%") + - "OR " + PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.StartsWith: - #region StartsWith - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + (PreparedStringValue(expr.Right.ToString() + "%")) + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.StartsWithNot: - #region StartsWithNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + (PreparedStringValue(expr.Right.ToString() + "%")) + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.EndsWith: - #region EndsWith - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - ")"; - } - else - { - return null; - } - break; + if (expr.Right == null) return null; + clause += operator_name + " "; - #endregion - - case OperatorEnum.EndsWithNot: - #region EndsWithNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.GreaterThan: - #region GreaterThan - - if (expr.Right == null) return null; - clause += "> "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.GreaterThanOrEqualTo: - #region GreaterThanOrEqualTo - - if (expr.Right == null) return null; - clause += ">= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.LessThan: - #region LessThan - - if (expr.Right == null) return null; - clause += "< "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.LessThanOrEqualTo: - #region LessThanOrEqualTo - - if (expr.Right == null) return null; - clause += "<= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.IsNull: - #region IsNull - - clause += " IS NULL"; - break; - - #endregion - - case OperatorEnum.IsNotNull: - #region IsNotNull - - clause += " IS NOT NULL"; - break; - - #endregion - - #endregion + if (expr.Right is Expr) + { + clause += ExpressionToWhereClause((Expr)expr.Right, parameters); + } + else + { + clause += AppendParameterByType(parameters, expr.Right); + } + } + else if (InListOperators.TryGetValue(expr.Operator, out operator_name)) + { + if (expr.Right == null) return null; + int inAdded = 0; + if (!Helper.IsList(expr.Right)) return null; + List inTempList = Helper.ObjectToList(expr.Right); + clause += Invariant($" {operator_name} ("); + foreach (object currObj in inTempList) + { + if (currObj == null) continue; + if (inAdded > 0) clause += ","; + clause += AppendParameterByType(parameters, currObj); + inAdded++; + } + clause += ")"; + } + else if (ContainsOperators.TryGetValue(expr.Operator, out operator_pair)) + { + if (expr.Right == null) return null; + if (!(expr.Right is string)) return null; + (operator_name, logic_operator_name) = operator_pair; + var field = PreparedFieldName(expr.Left.ToString()); + var p1_name = AppendParameterByType(parameters, "%" + expr.Right.ToString()); + var p2_name = AppendParameterByType(parameters, "%" + expr.Right.ToString() + "%"); + var p3_name = AppendParameterByType(parameters, expr.Right.ToString() + "%"); + clause += Invariant($"({field} {operator_name} {p1_name} {logic_operator_name} {field} {operator_name} {p2_name} {logic_operator_name} {field} {operator_name} {p3_name})"); + } + else if (LikeOperators.TryGetValue(expr.Operator, out operator_triple)) + { + if (expr.Right == null) return null; + if (!(expr.Right is string)) return null; + (operator_name, prefix, suffix) = operator_triple; + var p_name = AppendParameterByType(parameters, prefix + expr.Right.ToString() + suffix); + clause += Invariant($"({PreparedFieldName(expr.Left.ToString())} {operator_name} {p_name})"); + } + else if (IsNullOperators.TryGetValue(expr.Operator, out operator_name)) + { + clause += " " + operator_name; + } + else + { + throw new ApplicationException(Invariant($"Error in SqlServerHelper.ExpressionToWhereClause: Unknown operator {expr.Operator}")); } clause += ")"; @@ -1191,71 +876,6 @@ private string PreparedTableNameUnenclosed(string s) } } - private void BuildKeysValuesFromDictionary(Dictionary keyValuePairs, out string keys, out string vals) - { - keys = ""; - vals = ""; - int added = 0; - - foreach (KeyValuePair currKvp in keyValuePairs) - { - if (String.IsNullOrEmpty(currKvp.Key)) continue; - - if (added > 0) - { - keys += ","; - vals += ","; - } - - keys += PreparedFieldName(currKvp.Key); - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - vals += "'" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - vals += "'" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - vals += currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - vals += ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - vals += "0x" + BitConverter.ToString((byte[])currKvp.Value).Replace("-", ""); - } - else - { - if (Helper.IsExtendedCharacters(currKvp.Value.ToString())) - { - vals += PreparedUnicodeValue(currKvp.Value.ToString()); - } - else - { - vals += PreparedStringValue(currKvp.Value.ToString()); - } - } - } - else - { - vals += "null"; - } - - added++; - } - } - private void ValidateInputDictionaries(List> dicts) { Dictionary reference = dicts[0]; @@ -1285,132 +905,6 @@ private string BuildKeysFromDictionary(Dictionary reference) return keys; } - - private List BuildValuesFromDictionaries(List> dicts) - { - List values = new List(); - - foreach (Dictionary currDict in dicts) - { - string vals = ""; - int valsAdded = 0; - - foreach (KeyValuePair currKvp in currDict) - { - if (valsAdded > 0) vals += ","; - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - vals += "'" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - vals += "'" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - vals += currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - vals += ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - vals += "0x" + BitConverter.ToString((byte[])currKvp.Value).Replace("-", ""); - } - else - { - if (Helper.IsExtendedCharacters(currKvp.Value.ToString())) - { - vals += PreparedUnicodeValue(currKvp.Value.ToString()); - } - else - { - vals += PreparedStringValue(currKvp.Value.ToString()); - } - } - } - else - { - vals += "null"; - } - - valsAdded++; - } - - values.Add(vals); - } - - return values; - } - - private string BuildKeyValueClauseFromDictionary(Dictionary keyValuePairs) - { - string keyValueClause = ""; - int added = 0; - - foreach (KeyValuePair currKvp in keyValuePairs) - { - if (String.IsNullOrEmpty(currKvp.Key)) continue; - - if (added > 0) keyValueClause += ","; - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "='" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "='" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + "0x" + BitConverter.ToString((byte[])currKvp.Value).Replace("-", ""); - } - else - { - if (Helper.IsExtendedCharacters(currKvp.Value.ToString())) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + PreparedUnicodeValue(currKvp.Value.ToString()); - } - else - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + PreparedStringValue(currKvp.Value.ToString()); - } - } - } - else - { - keyValueClause += PreparedFieldName(currKvp.Key) + "= null"; - } - - added++; - } - - return keyValueClause; - } - #endregion } } From 95e65240a2ad19b409b68f34f518d5dc9f8f2adf Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Thu, 28 Dec 2023 12:19:30 +0100 Subject: [PATCH 02/11] DatabaseWrapper.SqlServer: Problem of handling of booleans in where-clauses corrected. --- src/DatabaseWrapper.SqlServer/SqlServerHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs index 96e97bc..b39a0ec 100644 --- a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs +++ b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs @@ -655,7 +655,7 @@ static string AppendParameterByType(List> parameter } if (untypedObject is bool) { - return (bool)untypedObject ? "1" : "0"; + return AppendParameter(parameters, (bool)untypedObject ? 1 : 0); } if (untypedObject is byte[]) { From ddc44d1a993ce6d813602d379c281804b608e5fb Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Thu, 28 Dec 2023 13:29:21 +0100 Subject: [PATCH 03/11] DatabaseWrapper.Core.DatabaseHelperBase: New public method: ExpresionToWhereClause. Thanks to using sql parameters it is almost generic now. --- .../DatabaseHelperBase.cs | 198 +++++++++++++++++- .../DatabaseWrapper.Core.xml | 26 ++- .../DatabaseClient.cs | 34 --- .../DatabaseWrapper.SqlServer.xml | 33 --- .../SqlServerHelper.cs | 183 +--------------- 5 files changed, 215 insertions(+), 259 deletions(-) diff --git a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs index d197d88..6ccbd39 100644 --- a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs +++ b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using static System.FormattableString; using System.Text; using ExpressionTree; @@ -13,20 +14,86 @@ namespace DatabaseWrapper.Core public abstract class DatabaseHelperBase { #region Public-Members + #endregion + + #region Private-Members /// - /// Timestamp format for use in DateTime.ToString([format]). + /// Append object as the parameter to the parameters list. + /// + /// Returns the parameter name, that is to be used in the query currently under construction. /// - public string TimestampFormat; + /// List of the parameters. + /// Object to be added. + /// Parameter name. + static string AppendParameter(List> parameters, object o) + { + var r = Invariant($"@E{parameters.Count}"); + parameters.Add(new KeyValuePair(r, o)); + return r; + } /// - /// Timestamp offset format for use in DateTimeOffset.ToString([format]). + /// Append object as the parameter to the parameters list. + /// Use the object type to apply conversions, if any needed. + /// + /// Returns the parameter name, that is to be used in the query currently under construction. /// - public string TimestampOffsetFormat; + /// List of the parameters. + /// Object to be added. + /// Parameter name. + static string AppendParameterByType(List> parameters, object untypedObject) + { + if (untypedObject is DateTime || untypedObject is DateTime?) + { + return AppendParameter(parameters, Convert.ToDateTime(untypedObject)); + } + if (untypedObject is int || untypedObject is long || untypedObject is decimal) + { + return AppendParameter(parameters, untypedObject); + } + if (untypedObject is bool) + { + return AppendParameter(parameters, (bool)untypedObject ? 1 : 0); + } + if (untypedObject is byte[]) + { + return AppendParameter(parameters, untypedObject); + } + return AppendParameter(parameters, untypedObject); + } - #endregion + static readonly Dictionary BinaryOperators = new Dictionary() { + { OperatorEnum.And, "AND" }, + { OperatorEnum.Or, "OR" }, + { OperatorEnum.Equals, "=" }, + { OperatorEnum.NotEquals, "<>" }, + { OperatorEnum.GreaterThan, ">" }, + { OperatorEnum.GreaterThanOrEqualTo, ">=" }, + { OperatorEnum.LessThan, "<" }, + { OperatorEnum.LessThanOrEqualTo, "<=" }, + }; + + static readonly Dictionary InListOperators = new Dictionary() { + { OperatorEnum.In, "IN" }, + { OperatorEnum.NotIn, "NOT IN" }, + }; + + static readonly Dictionary ContainsOperators = new Dictionary() { + { OperatorEnum.Contains, ("LIKE", "OR") }, + { OperatorEnum.ContainsNot, ("NOT LIKE", "AND") }, + }; + static readonly Dictionary LikeOperators = new Dictionary() { + { OperatorEnum.StartsWith, ("LIKE", "", "%") }, + { OperatorEnum.StartsWithNot, ("NOT LIKE", "", "%") }, + { OperatorEnum.EndsWith, ("LIKE", "%", "") }, + { OperatorEnum.EndsWithNot, ("NOT LIKE", "%", "") }, + }; + static readonly Dictionary IsNullOperators = new Dictionary() { + { OperatorEnum.IsNull, "IS NULL" }, + { OperatorEnum.IsNotNull, "IS NOT NULL" }, + }; - #region Private-Members #endregion @@ -35,6 +102,118 @@ public abstract class DatabaseHelperBase #endregion #region Public-Methods + /// + /// Compose a where-cluase corresponding to the tree expression. + /// + /// Expression to be converted. + /// Parameters to append SQL query parameters to. + /// Where-clause. + /// + /// + public string ExpressionToWhereClause(Expr expr, List> parameters) + { + if (expr == null) return null; + + string clause = ""; + + if (expr.Left == null) return null; + + clause += "("; + + if (expr.Left is Expr) + { + clause += ExpressionToWhereClause((Expr)expr.Left, parameters) + " "; + } + else + { + if (!(expr.Left is string)) + { + throw new ArgumentException("Left term must be of type Expression or String"); + } + + if (expr.Operator != OperatorEnum.Contains + && expr.Operator != OperatorEnum.ContainsNot + && expr.Operator != OperatorEnum.StartsWith + && expr.Operator != OperatorEnum.StartsWithNot + && expr.Operator != OperatorEnum.EndsWith + && expr.Operator != OperatorEnum.EndsWithNot) + { + // + // These operators will add the left term + // + clause += PreparedFieldName(expr.Left.ToString()) + " "; + } + } + + string operator_name; + string logic_operator_name; + string prefix; + string suffix; + (string, string) operator_pair; + (string, string, string) operator_triple; + if (BinaryOperators.TryGetValue(expr.Operator, out operator_name)) + { + if (expr.Right == null) return null; + clause += operator_name + " "; + + if (expr.Right is Expr) + { + clause += ExpressionToWhereClause((Expr)expr.Right, parameters); + } + else + { + clause += AppendParameterByType(parameters, expr.Right); + } + } + else if (InListOperators.TryGetValue(expr.Operator, out operator_name)) + { + if (expr.Right == null) return null; + int inAdded = 0; + if (!Helper.IsList(expr.Right)) return null; + List inTempList = Helper.ObjectToList(expr.Right); + clause += Invariant($" {operator_name} ("); + foreach (object currObj in inTempList) + { + if (currObj == null) continue; + if (inAdded > 0) clause += ","; + clause += AppendParameterByType(parameters, currObj); + inAdded++; + } + clause += ")"; + } + else if (ContainsOperators.TryGetValue(expr.Operator, out operator_pair)) + { + if (expr.Right == null) return null; + if (!(expr.Right is string)) return null; + (operator_name, logic_operator_name) = operator_pair; + var field = PreparedFieldName(expr.Left.ToString()); + var p1_name = AppendParameterByType(parameters, "%" + expr.Right.ToString()); + var p2_name = AppendParameterByType(parameters, "%" + expr.Right.ToString() + "%"); + var p3_name = AppendParameterByType(parameters, expr.Right.ToString() + "%"); + clause += Invariant($"({field} {operator_name} {p1_name} {logic_operator_name} {field} {operator_name} {p2_name} {logic_operator_name} {field} {operator_name} {p3_name})"); + } + else if (LikeOperators.TryGetValue(expr.Operator, out operator_triple)) + { + if (expr.Right == null) return null; + if (!(expr.Right is string)) return null; + (operator_name, prefix, suffix) = operator_triple; + var p_name = AppendParameterByType(parameters, prefix + expr.Right.ToString() + suffix); + clause += Invariant($"({PreparedFieldName(expr.Left.ToString())} {operator_name} {p_name})"); + } + else if (IsNullOperators.TryGetValue(expr.Operator, out operator_name)) + { + clause += " " + operator_name; + } + else + { + throw new ApplicationException(Invariant($"Error in SqlServerHelper.ExpressionToWhereClause: Unknown operator {expr.Operator}")); + } + + clause += ")"; + + return clause; + } + /// /// Build a connection string from DatabaseSettings. @@ -65,6 +244,13 @@ public abstract class DatabaseHelperBase /// String. public abstract string SanitizeString(string val); + /// + /// Prepare a field name for use in a SQL query. + /// + /// Name of the field to be prepared. + /// Field name for use in a SQL query. + public abstract string PreparedFieldName(string fieldName); + /// /// Method to convert a Column object to the values used in a table create statement. /// diff --git a/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml b/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml index dfc6806..b23db30 100644 --- a/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml +++ b/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml @@ -442,15 +442,26 @@ Base implementation of helper properties and methods. - + - Timestamp format for use in DateTime.ToString([format]). + Append object as the parameter to the parameters list. + + Returns the parameter name, that is to be used in the query currently under construction. + List of the parameters. + Object to be added. + Parameter name. - + - Timestamp offset format for use in DateTimeOffset.ToString([format]). + Append object as the parameter to the parameters list. + Use the object type to apply conversions, if any needed. + + Returns the parameter name, that is to be used in the query currently under construction. + List of the parameters. + Object to be added. + Parameter name. @@ -481,6 +492,13 @@ String. String. + + + Prepare a field name for use in a SQL query. + + Name of the field to be prepared. + Field name for use in a SQL query. + Method to convert a Column object to the values used in a table create statement. diff --git a/src/DatabaseWrapper.SqlServer/DatabaseClient.cs b/src/DatabaseWrapper.SqlServer/DatabaseClient.cs index 20f8b25..b36e966 100644 --- a/src/DatabaseWrapper.SqlServer/DatabaseClient.cs +++ b/src/DatabaseWrapper.SqlServer/DatabaseClient.cs @@ -35,40 +35,6 @@ private set } } - /// - /// Timestamp format. - /// Default is MM/dd/yyyy hh:mm:ss.fffffff tt. - /// - public new string TimestampFormat - { - get - { - return _Helper.TimestampFormat; - } - set - { - if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(TimestampFormat)); - _Helper.TimestampFormat = value; - } - } - - /// - /// Timestamp format with offset. - /// Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. - /// - public new string TimestampOffsetFormat - { - get - { - return _Helper.TimestampOffsetFormat; - } - set - { - if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(TimestampOffsetFormat)); - _Helper.TimestampOffsetFormat = value; - } - } - /// /// Maximum supported statement length. /// diff --git a/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml b/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml index 2e53840..cf40162 100644 --- a/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml +++ b/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml @@ -14,18 +14,6 @@ The connection string used to connect to the database. - - - Timestamp format. - Default is MM/dd/yyyy hh:mm:ss.fffffff tt. - - - - - Timestamp format with offset. - Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. - - Maximum supported statement length. @@ -539,26 +527,5 @@ String. String. - - - Append object as the parameter to the parameters list. - - Returns the parameter name, that is to be used in the query currently under construction. - - List of the parameters. - Object to be added. - Parameter name. - - - - Append object as the parameter to the parameters list. - Use the object type to apply conversions, if any needed. - - Returns the parameter name, that is to be used in the query currently under construction. - - List of the parameters. - Object to be added. - Parameter name. - diff --git a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs index b39a0ec..1ed7de1 100644 --- a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs +++ b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs @@ -614,192 +614,11 @@ public string ExtractTableName(string s) #region Private-Members - private string PreparedFieldName(string fieldName) + public override string PreparedFieldName(string fieldName) { return "[" + fieldName + "]"; } - /// - /// Append object as the parameter to the parameters list. - /// - /// Returns the parameter name, that is to be used in the query currently under construction. - /// - /// List of the parameters. - /// Object to be added. - /// Parameter name. - static string AppendParameter(List> parameters, object o) - { - var r = Invariant($"@E{parameters.Count}"); - parameters.Add(new KeyValuePair(r, o)); - return r; - } - - /// - /// Append object as the parameter to the parameters list. - /// Use the object type to apply conversions, if any needed. - /// - /// Returns the parameter name, that is to be used in the query currently under construction. - /// - /// List of the parameters. - /// Object to be added. - /// Parameter name. - static string AppendParameterByType(List> parameters, object untypedObject) - { - if (untypedObject is DateTime || untypedObject is DateTime?) - { - return AppendParameter(parameters, Convert.ToDateTime(untypedObject)); - } - if (untypedObject is int || untypedObject is long || untypedObject is decimal) - { - return AppendParameter(parameters, untypedObject); - } - if (untypedObject is bool) - { - return AppendParameter(parameters, (bool)untypedObject ? 1 : 0); - } - if (untypedObject is byte[]) - { - return AppendParameter(parameters, untypedObject); - } - return AppendParameter(parameters, untypedObject); - } - - static readonly Dictionary BinaryOperators = new Dictionary() { - { OperatorEnum.And, "AND" }, - { OperatorEnum.Or, "OR" }, - { OperatorEnum.Equals, "=" }, - { OperatorEnum.NotEquals, "<>" }, - { OperatorEnum.GreaterThan, ">" }, - { OperatorEnum.GreaterThanOrEqualTo, ">=" }, - { OperatorEnum.LessThan, "<" }, - { OperatorEnum.LessThanOrEqualTo, "<=" }, - }; - - static readonly Dictionary InListOperators = new Dictionary() { - { OperatorEnum.In, "IN" }, - { OperatorEnum.NotIn, "NOT IN" }, - }; - - static readonly Dictionary ContainsOperators = new Dictionary() { - { OperatorEnum.Contains, ("LIKE", "OR") }, - { OperatorEnum.ContainsNot, ("NOT LIKE", "AND") }, - }; - static readonly Dictionary LikeOperators = new Dictionary() { - { OperatorEnum.StartsWith, ("LIKE", "", "%") }, - { OperatorEnum.StartsWithNot, ("NOT LIKE", "", "%") }, - { OperatorEnum.EndsWith, ("LIKE", "%", "") }, - { OperatorEnum.EndsWithNot, ("NOT LIKE", "%", "") }, - }; - static readonly Dictionary IsNullOperators = new Dictionary() { - { OperatorEnum.IsNull, "IS NULL" }, - { OperatorEnum.IsNotNull, "IS NOT NULL" }, - }; - - - private string ExpressionToWhereClause(Expr expr, List> parameters) - { - if (expr == null) return null; - - string clause = ""; - - if (expr.Left == null) return null; - - clause += "("; - - if (expr.Left is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Left, parameters) + " "; - } - else - { - if (!(expr.Left is string)) - { - throw new ArgumentException("Left term must be of type Expression or String"); - } - - if (expr.Operator != OperatorEnum.Contains - && expr.Operator != OperatorEnum.ContainsNot - && expr.Operator != OperatorEnum.StartsWith - && expr.Operator != OperatorEnum.StartsWithNot - && expr.Operator != OperatorEnum.EndsWith - && expr.Operator != OperatorEnum.EndsWithNot) - { - // - // These operators will add the left term - // - clause += PreparedFieldName(expr.Left.ToString()) + " "; - } - } - - string operator_name; - string logic_operator_name; - string prefix; - string suffix; - (string, string) operator_pair; - (string, string, string) operator_triple; - if (BinaryOperators.TryGetValue(expr.Operator, out operator_name)) - { - if (expr.Right == null) return null; - clause += operator_name + " "; - - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right, parameters); - } - else - { - clause += AppendParameterByType(parameters, expr.Right); - } - } - else if (InListOperators.TryGetValue(expr.Operator, out operator_name)) - { - if (expr.Right == null) return null; - int inAdded = 0; - if (!Helper.IsList(expr.Right)) return null; - List inTempList = Helper.ObjectToList(expr.Right); - clause += Invariant($" {operator_name} ("); - foreach (object currObj in inTempList) - { - if (currObj == null) continue; - if (inAdded > 0) clause += ","; - clause += AppendParameterByType(parameters, currObj); - inAdded++; - } - clause += ")"; - } - else if (ContainsOperators.TryGetValue(expr.Operator, out operator_pair)) - { - if (expr.Right == null) return null; - if (!(expr.Right is string)) return null; - (operator_name, logic_operator_name) = operator_pair; - var field = PreparedFieldName(expr.Left.ToString()); - var p1_name = AppendParameterByType(parameters, "%" + expr.Right.ToString()); - var p2_name = AppendParameterByType(parameters, "%" + expr.Right.ToString() + "%"); - var p3_name = AppendParameterByType(parameters, expr.Right.ToString() + "%"); - clause += Invariant($"({field} {operator_name} {p1_name} {logic_operator_name} {field} {operator_name} {p2_name} {logic_operator_name} {field} {operator_name} {p3_name})"); - } - else if (LikeOperators.TryGetValue(expr.Operator, out operator_triple)) - { - if (expr.Right == null) return null; - if (!(expr.Right is string)) return null; - (operator_name, prefix, suffix) = operator_triple; - var p_name = AppendParameterByType(parameters, prefix + expr.Right.ToString() + suffix); - clause += Invariant($"({PreparedFieldName(expr.Left.ToString())} {operator_name} {p_name})"); - } - else if (IsNullOperators.TryGetValue(expr.Operator, out operator_name)) - { - clause += " " + operator_name; - } - else - { - throw new ApplicationException(Invariant($"Error in SqlServerHelper.ExpressionToWhereClause: Unknown operator {expr.Operator}")); - } - - clause += ")"; - - return clause; - } - private string BuildOrderByClause(ResultOrder[] resultOrder) { if (resultOrder == null || resultOrder.Length < 0) return null; From d4185db65c7997f19c6644a0323406c1effe7b1d Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Thu, 28 Dec 2023 13:34:33 +0100 Subject: [PATCH 04/11] DatabaseWrapper.Sqlite: Converted to use parametrized SQL queries. --- src/DatabaseWrapper.Sqlite/DatabaseClient.cs | 135 +-- .../DatabaseWrapper.Sqlite.xml | 65 +- src/DatabaseWrapper.Sqlite/SqliteHelper.cs | 767 ++---------------- 3 files changed, 143 insertions(+), 824 deletions(-) diff --git a/src/DatabaseWrapper.Sqlite/DatabaseClient.cs b/src/DatabaseWrapper.Sqlite/DatabaseClient.cs index 0aa09b2..d381f2e 100644 --- a/src/DatabaseWrapper.Sqlite/DatabaseClient.cs +++ b/src/DatabaseWrapper.Sqlite/DatabaseClient.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.Collections.Concurrent; -using System.Data; +using System.Data; +using static System.FormattableString; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -34,40 +35,6 @@ private set } } - /// - /// Timestamp format. - /// Default is yyyy-MM-dd HH:mm:ss.ffffff. - /// - public new string TimestampFormat - { - get - { - return _Helper.TimestampFormat; - } - set - { - if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(TimestampFormat)); - _Helper.TimestampFormat = value; - } - } - - /// - /// Timestamp format with offset. - /// Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. - /// - public new string TimestampOffsetFormat - { - get - { - return _Helper.TimestampOffsetFormat; - } - set - { - if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(TimestampOffsetFormat)); - _Helper.TimestampOffsetFormat = value; - } - } - /// /// Maximum supported statement length. /// @@ -794,10 +761,11 @@ public override async Task TruncateAsync(string tableName, CancellationToken tok /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Database query defined outside of the database client, and, the corresponding parameters. /// A DataTable containing the results. - public override DataTable Query(string query) + public override DataTable Query((string Query, IEnumerable> Parameters) queryAndParameters) { + (var query, var parameters) = queryAndParameters; if (String.IsNullOrEmpty(query)) throw new ArgumentNullException(query); if (query.Length > MaxStatementLength) throw new ArgumentException("Query exceeds maximum statement length of " + MaxStatementLength + " characters."); @@ -818,6 +786,7 @@ public override DataTable Query(string query) using (SqliteCommand cmd = new SqliteCommand(query, conn)) #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities { + AddParametersIfAny(cmd, parameters); using (SqliteDataReader rdr = cmd.ExecuteReader()) { result.Load(rdr); @@ -857,11 +826,12 @@ public override DataTable Query(string query) /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Tuple of a database query defined outside of the database client and of query parameters /// Cancellation token. /// A DataTable containing the results. - public override async Task QueryAsync(string query, CancellationToken token = default) + public override async Task QueryAsync((string Query, IEnumerable> Parameters) queryAndParameters, CancellationToken token = default) { + (var query, var parameters) = queryAndParameters; if (String.IsNullOrEmpty(query)) throw new ArgumentNullException(query); if (query.Length > MaxStatementLength) throw new ArgumentException("Query exceeds maximum statement length of " + MaxStatementLength + " characters."); @@ -882,6 +852,7 @@ public override async Task QueryAsync(string query, CancellationToken using (SqliteCommand cmd = new SqliteCommand(query, conn)) #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities { + AddParametersIfAny(cmd, parameters); using (SqliteDataReader rdr = await cmd.ExecuteReaderAsync(token).ConfigureAwait(false)) { result.Load(rdr); @@ -1037,26 +1008,6 @@ public override async Task SumAsync(string tableName, string fieldName, return 0m; } - /// - /// Create a string timestamp from the given DateTime. - /// - /// DateTime. - /// A string with formatted timestamp. - public override string Timestamp(DateTime ts) - { - return _Helper.GenerateTimestamp(ts); - } - - /// - /// Create a string timestamp with offset from the given DateTimeOffset. - /// - /// DateTimeOffset. - /// A string with formatted timestamp. - public override string TimestampOffset(DateTimeOffset ts) - { - return _Helper.GenerateTimestampOffset(ts); - } - /// /// Sanitize an input string. /// @@ -1072,6 +1023,72 @@ public override string SanitizeString(string s) #region Private-Methods + static readonly Dictionary SqlTypeMap = new Dictionary() { + { typeof(Int32), SqlDbType.Int }, + { typeof(Int64), SqlDbType.BigInt }, + { typeof(double), SqlDbType.Float }, + { typeof(Guid), SqlDbType.UniqueIdentifier }, + { typeof(byte[]), SqlDbType.Image }, + { typeof(DateTimeOffset), SqlDbType.DateTimeOffset }, + }; + + private static void AddParametersIfAny(SqliteCommand cmd, IEnumerable> parameters) + { + if (parameters==null) + { + return; + } + foreach (var kv in parameters) + { + int param_index = cmd.Parameters.Count; + var param_name = kv.Key; + var p = kv.Value; + if (p == null) + { + cmd.Parameters.Add(new SqliteParameter(param_name, SqlDbType.NVarChar)); + cmd.Parameters[param_index].Value = DBNull.Value; + continue; + } + var t = p.GetType(); + SqlDbType sql_type; + if (SqlTypeMap.TryGetValue(t, out sql_type)) + { + cmd.Parameters.Add(new SqliteParameter(param_name, sql_type)); + cmd.Parameters[param_index].Value = p; + continue; + } + if (t == typeof(string)) + { + var s_string = p as string; + cmd.Parameters.Add(new SqliteParameter(param_name, s_string.Length > 4000 ? System.Data.SqlDbType.NText : System.Data.SqlDbType.NVarChar)); + cmd.Parameters[param_index].Value = s_string; + continue; + } + if (t == typeof(bool)) { + cmd.Parameters.Add(new SqliteParameter(param_name, System.Data.SqlDbType.Bit)); + cmd.Parameters[param_index].Value = (bool)p ? 1 : 0; + continue; + } + if (t == typeof(DateTime)) + { + var dt_object = DBNull.Value as object; + var dt_datetime = (DateTime)p; + if (dt_datetime != DateTime.MinValue) + { + var dt = dt_datetime.ToLocalTime(); + if (dt != DateTime.MinValue) + { + dt_object = dt; + } + } + cmd.Parameters.Add(new SqliteParameter(param_name, System.Data.SqlDbType.DateTime)); + cmd.Parameters[param_index].Value = dt_object; + continue; + } + throw new ApplicationException(Invariant($"DatabaseClient: Unknown type: {t.Name}")); + } + } + /// /// Dispose of the object. /// diff --git a/src/DatabaseWrapper.Sqlite/DatabaseWrapper.Sqlite.xml b/src/DatabaseWrapper.Sqlite/DatabaseWrapper.Sqlite.xml index abf3e21..2e7afd1 100644 --- a/src/DatabaseWrapper.Sqlite/DatabaseWrapper.Sqlite.xml +++ b/src/DatabaseWrapper.Sqlite/DatabaseWrapper.Sqlite.xml @@ -14,18 +14,6 @@ The connection string used to connect to the database. - - - Timestamp format. - Default is yyyy-MM-dd HH:mm:ss.ffffff. - - - - - Timestamp format with offset. - Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. - - Maximum supported statement length. @@ -300,18 +288,18 @@ The table you wish to TRUNCATE. Cancellation token. - + Execute a query. - Database query defined outside of the database client. + Database query defined outside of the database client, and, the corresponding parameters. A DataTable containing the results. - + Execute a query. - Database query defined outside of the database client. + Tuple of a database query defined outside of the database client and of query parameters Cancellation token. A DataTable containing the results. @@ -368,20 +356,6 @@ Cancellation token. The sum of the specified column from the matching rows. - - - Create a string timestamp from the given DateTime. - - DateTime. - A string with formatted timestamp. - - - - Create a string timestamp with offset from the given DateTimeOffset. - - DateTimeOffset. - A string with formatted timestamp. - Sanitize an input string. @@ -400,16 +374,6 @@ Sqlite implementation of helper properties and methods. - - - Timestamp format for use in DateTime.ToString([format]). - - - - - Timestamp offset format for use in DateTimeOffset.ToString([format]). - - Build a connection string from DatabaseSettings. @@ -439,6 +403,13 @@ String. String. + + + Prepare a field name for use in a SQL query by surrounding it with backticks. + + Name of the field to be prepared. + Field name for use in a SQL query. + Method to convert a Column object to the values used in a table create statement. @@ -547,19 +518,5 @@ Expression filter. String. - - - Retrieve a timestamp in the database format. - - DateTime. - String. - - - - Retrieve a timestamp offset in the database format. - - DateTimeOffset. - String. - diff --git a/src/DatabaseWrapper.Sqlite/SqliteHelper.cs b/src/DatabaseWrapper.Sqlite/SqliteHelper.cs index 4c55107..e69899b 100644 --- a/src/DatabaseWrapper.Sqlite/SqliteHelper.cs +++ b/src/DatabaseWrapper.Sqlite/SqliteHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using static System.FormattableString; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +9,8 @@ namespace DatabaseWrapper.Sqlite { + using QueryAndParameters = System.ValueTuple>>; + /// /// Sqlite implementation of helper properties and methods. /// @@ -15,16 +18,6 @@ public class SqliteHelper : DatabaseHelperBase { #region Public-Members - /// - /// Timestamp format for use in DateTime.ToString([format]). - /// - public new string TimestampFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.ffffff"; - - /// - /// Timestamp offset format for use in DateTimeOffset.ToString([format]). - /// - public new string TimestampOffsetFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.fffffff zzz"; - #endregion #region Private-Members @@ -176,6 +169,16 @@ public override string SanitizeString(string val) return ret; } + /// + /// Prepare a field name for use in a SQL query by surrounding it with backticks. + /// + /// Name of the field to be prepared. + /// Field name for use in a SQL query. + public override string PreparedFieldName(string fieldName) + { + return "`" + fieldName + "`"; + } + /// /// Method to convert a Column object to the values used in a table create statement. /// @@ -300,7 +303,7 @@ public override string DropTableQuery(string tableName) /// Expression filter. /// Result order. /// String. - public override string SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder) + public override QueryAndParameters SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder) { string query = ""; string whereClause = ""; @@ -341,7 +344,8 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe // // expressions // - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters_list = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters_list); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; @@ -365,7 +369,7 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe query += "LIMIT " + maxResults + " "; } - return query; + return (query, parameters_list); } /// @@ -374,24 +378,21 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe /// The table in which you wish to INSERT. /// The key-value pairs for the row you wish to INSERT. /// String. - public override string InsertQuery(string tableName, Dictionary keyValuePairs) + public override QueryAndParameters InsertQuery(string tableName, Dictionary keyValuePairs) { + var o_ret = keyValuePairs.Select(kv => new KeyValuePair("@F_" + kv.Key, kv.Value)); string ret = "BEGIN TRANSACTION; " + "INSERT INTO " + PreparedFieldName(SanitizeString(tableName)) + " " + "("; - string keys = ""; - string vals = ""; - BuildKeysValuesFromDictionary(keyValuePairs, out keys, out vals); - - ret += keys + ") " + + ret += string.Join(", ", keyValuePairs.Keys.Select(k => PreparedFieldName(k))) + ") " + "VALUES " + - "(" + vals + "); " + + "(" + string.Join(", ", o_ret.Select(k => k.Key)) + "); " + "SELECT last_insert_rowid() AS id; " + ";COMMIT;"; - return ret; + return (ret, o_ret); } /// @@ -400,11 +401,11 @@ public override string InsertQuery(string tableName, Dictionary /// The table in which you wish to INSERT. /// List of dictionaries containing key-value pairs for the rows you wish to INSERT. /// String. - public override string InsertMultipleQuery(string tableName, List> keyValuePairList) + public override QueryAndParameters InsertMultipleQuery(string tableName, List> keyValuePairList) { ValidateInputDictionaries(keyValuePairList); string keys = BuildKeysFromDictionary(keyValuePairList[0]); - List values = BuildValuesFromDictionaries(keyValuePairList); + var ret_values = new List>(); string ret = "BEGIN TRANSACTION; " + @@ -412,18 +413,22 @@ public override string InsertMultipleQuery(string tableName, List 0) ret += ","; - ret += " (" + value + ")"; - added++; + var dict = keyValuePairList[i_dict]; + var prefix = Invariant($"@F{i_dict}_"); + var this_round = dict.Select(kv => new KeyValuePair(prefix + kv.Key, kv.Value)); + if (i_dict>0) { + ret += ", "; + } + ret += "(" + string.Join(", ", this_round.Select(kv => kv.Key)) + ")"; + ret_values.AddRange(this_round); } ret += ";COMMIT;"; - return ret; + return (ret, ret_values); } /// @@ -433,21 +438,22 @@ public override string InsertMultipleQuery(string tableName, ListThe key-value pairs for the data you wish to UPDATE. /// The expression containing the UPDATE filter (i.e. WHERE clause data). /// String. - public override string UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter) + public override QueryAndParameters UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter) { - string keyValueClause = BuildKeyValueClauseFromDictionary(keyValuePairs); + const string FIELD_PREFIX = "@F"; + var parameters = keyValuePairs.Select(kv => new KeyValuePair(FIELD_PREFIX + kv.Key, kv.Value)).ToList(); string ret = "BEGIN TRANSACTION; " + "UPDATE " + PreparedFieldName(SanitizeString(tableName)) + " SET " + - keyValueClause + " "; + string.Join(", ", parameters.Select(kv => kv.Key.Substring(FIELD_PREFIX.Length) + "=" + kv.Key)) + " "; - if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter) + " "; + if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter, parameters) + " "; ret += ";COMMIT;"; - return ret; + return (ret, parameters); } /// @@ -456,14 +462,15 @@ public override string UpdateQuery(string tableName, Dictionary /// Table name. /// Expression filter. /// String. - public override string DeleteQuery(string tableName, Expr filter) + public override QueryAndParameters DeleteQuery(string tableName, Expr filter) { string ret = "DELETE FROM " + PreparedFieldName(SanitizeString(tableName)) + " "; - if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter) + " "; + var parameters = new List>(); + if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter, parameters) + " "; - return ret; + return (ret, parameters); } /// @@ -482,7 +489,7 @@ public override string TruncateQuery(string tableName) /// Table name. /// Expression filter. /// String. - public override string ExistsQuery(string tableName, Expr filter) + public override QueryAndParameters ExistsQuery(string tableName, Expr filter) { string query = ""; string whereClause = ""; @@ -497,14 +504,15 @@ public override string ExistsQuery(string tableName, Expr filter) // // expressions // - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } query += "LIMIT 1"; - return query; + return (query, parameters); } /// @@ -514,7 +522,7 @@ public override string ExistsQuery(string tableName, Expr filter) /// Column name to use to temporarily store the result. /// Expression filter. /// String. - public override string CountQuery(string tableName, string countColumnName, Expr filter) + public override QueryAndParameters CountQuery(string tableName, string countColumnName, Expr filter) { string query = ""; string whereClause = ""; @@ -529,13 +537,14 @@ public override string CountQuery(string tableName, string countColumnName, Expr // // expressions // - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } - return query; + return (query, parameters); } /// @@ -546,7 +555,7 @@ public override string CountQuery(string tableName, string countColumnName, Expr /// Column name to temporarily store the result. /// Expression filter. /// String. - public override string SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter) + public override QueryAndParameters SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter) { string query = ""; string whereClause = ""; @@ -561,517 +570,24 @@ public override string SumQuery(string tableName, string fieldName, string sumCo // // expressions // - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } - return query; - } - - /// - /// Retrieve a timestamp in the database format. - /// - /// DateTime. - /// String. - public override string GenerateTimestamp(DateTime ts) - { - return ts.ToString(TimestampFormat); - } - - /// - /// Retrieve a timestamp offset in the database format. - /// - /// DateTimeOffset. - /// String. - public override string GenerateTimestampOffset(DateTimeOffset ts) - { - return ts.ToString(TimestampOffsetFormat); + return (query, parameters); } #endregion #region Private-Methods - - private string PreparedFieldName(string fieldName) - { - return "`" + fieldName + "`"; - } - private string PreparedStringValue(string str) { return "'" + SanitizeString(str) + "'"; } - private string ExpressionToWhereClause(Expr expr) - { - if (expr == null) return null; - - string clause = ""; - - if (expr.Left == null) return null; - - clause += "("; - - if (expr.Left is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Left) + " "; - } - else - { - if (!(expr.Left is string)) - { - throw new ArgumentException("Left term must be of type Expression or String"); - } - - if (expr.Operator != OperatorEnum.Contains - && expr.Operator != OperatorEnum.ContainsNot - && expr.Operator != OperatorEnum.StartsWith - && expr.Operator != OperatorEnum.StartsWithNot - && expr.Operator != OperatorEnum.EndsWith - && expr.Operator != OperatorEnum.EndsWithNot) - { - // - // These operators will add the left term - // - clause += PreparedFieldName(expr.Left.ToString()) + " "; - } - } - - switch (expr.Operator) - { - #region Process-By-Operators - - case OperatorEnum.And: - #region And - - if (expr.Right == null) return null; - clause += "AND "; - - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.Or: - #region Or - - if (expr.Right == null) return null; - clause += "OR "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.Equals: - #region Equals - - if (expr.Right == null) return null; - clause += "= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.NotEquals: - #region NotEquals - - if (expr.Right == null) return null; - clause += "<> "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.In: - #region In - - if (expr.Right == null) return null; - int inAdded = 0; - if (!Helper.IsList(expr.Right)) return null; - List inTempList = Helper.ObjectToList(expr.Right); - clause += " IN ("; - foreach (object currObj in inTempList) - { - if (currObj == null) continue; - if (inAdded > 0) clause += ","; - if (currObj is DateTime || currObj is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(currObj)) + "'"; - } - else if (currObj is int || currObj is long || currObj is decimal) - { - clause += currObj.ToString(); - } - else - { - clause += PreparedStringValue(currObj.ToString()); - } - inAdded++; - } - clause += ")"; - break; - - #endregion - - case OperatorEnum.NotIn: - #region NotIn - - if (expr.Right == null) return null; - int notInAdded = 0; - if (!Helper.IsList(expr.Right)) return null; - List notInTempList = Helper.ObjectToList(expr.Right); - clause += " NOT IN ("; - foreach (object currObj in notInTempList) - { - if (currObj == null) continue; - if (notInAdded > 0) clause += ","; - if (currObj is DateTime || currObj is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(currObj)) + "'"; - } - else if (currObj is int || currObj is long || currObj is decimal) - { - clause += currObj.ToString(); - } - else - { - clause += PreparedStringValue(currObj.ToString()); - } - notInAdded++; - } - clause += ")"; - break; - - #endregion - - case OperatorEnum.Contains: - #region Contains - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - "OR " + PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString() + "%") + - "OR " + PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.ContainsNot: - #region ContainsNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - "OR " + PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString() + "%") + - "OR " + PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.StartsWith: - #region StartsWith - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.StartsWithNot: - #region StartsWithNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.EndsWith: - #region EndsWith - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.EndsWithNot: - #region EndsWith - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.GreaterThan: - #region GreaterThan - - if (expr.Right == null) return null; - clause += "> "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.GreaterThanOrEqualTo: - #region GreaterThanOrEqualTo - - if (expr.Right == null) return null; - clause += ">= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.LessThan: - #region LessThan - - if (expr.Right == null) return null; - clause += "< "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.LessThanOrEqualTo: - #region LessThanOrEqualTo - - if (expr.Right == null) return null; - clause += "<= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.IsNull: - #region IsNull - - clause += " IS NULL"; - break; - - #endregion - - case OperatorEnum.IsNotNull: - #region IsNotNull - - clause += " IS NOT NULL"; - break; - - #endregion - - #endregion - } - - clause += ")"; - - return clause; - } - private string BuildOrderByClause(ResultOrder[] resultOrder) { if (resultOrder == null || resultOrder.Length < 0) return null; @@ -1090,64 +606,6 @@ private string BuildOrderByClause(ResultOrder[] resultOrder) return ret; } - private void BuildKeysValuesFromDictionary(Dictionary keyValuePairs, out string keys, out string vals) - { - keys = ""; - vals = ""; - int added = 0; - - foreach (KeyValuePair currKvp in keyValuePairs) - { - if (String.IsNullOrEmpty(currKvp.Key)) continue; - - if (added > 0) - { - keys += ","; - vals += ","; - } - - keys += PreparedFieldName(currKvp.Key); - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - vals += "'" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - vals += "'" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - vals += currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - vals += ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - vals += "X'" + BitConverter.ToString((byte[])currKvp.Value).Replace("-", "") + "'"; - } - else - { - vals += PreparedStringValue(currKvp.Value.ToString()); - } - } - else - { - vals += "null"; - } - - added++; - } - } - private void ValidateInputDictionaries(List> keyValuePairList) { Dictionary reference = keyValuePairList[0]; @@ -1177,119 +635,6 @@ private string BuildKeysFromDictionary(Dictionary reference) return keys; } - - private List BuildValuesFromDictionaries(List> dicts) - { - List values = new List(); - - foreach (Dictionary currDict in dicts) - { - string vals = ""; - int valsAdded = 0; - - foreach (KeyValuePair currKvp in currDict) - { - if (valsAdded > 0) vals += ","; - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - vals += "'" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - vals += "'" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - vals += currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - vals += ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - vals += "X'" + BitConverter.ToString((byte[])currKvp.Value).Replace("-", "") + "'"; - } - else - { - vals += PreparedStringValue(currKvp.Value.ToString()); - } - - } - else - { - vals += "null"; - } - - valsAdded++; - } - - values.Add(vals); - } - - return values; - } - - private string BuildKeyValueClauseFromDictionary(Dictionary keyValuePairs) - { - string keyValueClause = ""; - int added = 0; - - foreach (KeyValuePair currKvp in keyValuePairs) - { - if (String.IsNullOrEmpty(currKvp.Key)) continue; - - if (added > 0) keyValueClause += ","; - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "='" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "='" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + "X'" + BitConverter.ToString((byte[])currKvp.Value).Replace("-", "") + "'"; - } - else - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + PreparedStringValue(currKvp.Value.ToString()); - } - } - else - { - keyValueClause += PreparedFieldName(currKvp.Key) + "= null"; - } - - added++; - } - - return keyValueClause; - } - #endregion } } From 93831638eea90da4d067bb1d64996be539f72a38 Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Thu, 28 Dec 2023 18:08:36 +0100 Subject: [PATCH 05/11] Added missing XML-Documentation. --- .../DatabaseWrapper.SqlServer.xml | 7 +++++++ src/DatabaseWrapper.SqlServer/SqlServerHelper.cs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml b/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml index cf40162..0e28893 100644 --- a/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml +++ b/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml @@ -527,5 +527,12 @@ String. String. + + + Prepare a field name for use in a SQL query. + + Name of the field to be prepared. + Field name for use in a SQL query. + diff --git a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs index 1ed7de1..ee96354 100644 --- a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs +++ b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs @@ -614,6 +614,11 @@ public string ExtractTableName(string s) #region Private-Members + /// + /// Prepare a field name for use in a SQL query. + /// + /// Name of the field to be prepared. + /// Field name for use in a SQL query. public override string PreparedFieldName(string fieldName) { return "[" + fieldName + "]"; From c4571de5e5d610f5c119b274a78e645b5e51eb09 Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Thu, 28 Dec 2023 18:09:04 +0100 Subject: [PATCH 06/11] DatabaseWrapper.Core.DatabaseHelperBase: New method: AddParameters. --- .../DatabaseClientBase.cs | 3 + .../DatabaseHelperBase.cs | 76 +++++++++++++++++++ .../DatabaseWrapper.Core.xml | 49 ++++++++---- .../DatabaseClient.cs | 70 +---------------- src/DatabaseWrapper.Sqlite/DatabaseClient.cs | 71 +---------------- 5 files changed, 118 insertions(+), 151 deletions(-) diff --git a/src/DatabaseWrapper.Core/DatabaseClientBase.cs b/src/DatabaseWrapper.Core/DatabaseClientBase.cs index 85309b1..d1eb491 100644 --- a/src/DatabaseWrapper.Core/DatabaseClientBase.cs +++ b/src/DatabaseWrapper.Core/DatabaseClientBase.cs @@ -2,12 +2,15 @@ using System.Collections.Generic; using System.Collections.Concurrent; using System.Data; +using static System.FormattableString; using System.Linq; using System.Text; using System.Threading.Tasks; using DatabaseWrapper.Core; using ExpressionTree; using System.Threading; +using System.Data.Common; +using System.Runtime.InteropServices; namespace DatabaseWrapper.Core { diff --git a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs index 6ccbd39..fc9b704 100644 --- a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs +++ b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data; using static System.FormattableString; using System.Text; using ExpressionTree; @@ -94,6 +95,14 @@ static string AppendParameterByType(List> parameter { OperatorEnum.IsNotNull, "IS NOT NULL" }, }; + static readonly Dictionary SqlTypeMap = new Dictionary() { + { typeof(Int32), SqlDbType.Int }, + { typeof(Int64), SqlDbType.BigInt }, + { typeof(double), SqlDbType.Float }, + { typeof(Guid), SqlDbType.UniqueIdentifier }, + { typeof(byte[]), SqlDbType.Image }, + { typeof(DateTimeOffset), SqlDbType.DateTimeOffset }, + }; #endregion @@ -214,6 +223,73 @@ public string ExpressionToWhereClause(Expr expr, List + /// Add parameters to the SQL command. + /// + /// Subtype of DbCommand + /// Subtype of DbPaameter + /// Command to add parameters to. + /// Parameter constructor. + /// Parameters to be added. + /// + public static void AddParameters(TC cmd, Func createParameter, IEnumerable> parameters) + where TC : System.Data.Common.DbCommand + where TP : System.Data.Common.DbParameter + { + if (parameters==null) + { + return; + } + foreach (var kv in parameters) + { + int param_index = cmd.Parameters.Count; + var param_name = kv.Key; + var p = kv.Value; + if (p == null) + { + cmd.Parameters.Add(createParameter(param_name, SqlDbType.NVarChar)); + cmd.Parameters[param_index].Value = DBNull.Value; + continue; + } + var t = p.GetType(); + SqlDbType sql_type; + if (SqlTypeMap.TryGetValue(t, out sql_type)) + { + cmd.Parameters.Add(createParameter(param_name, sql_type)); + cmd.Parameters[param_index].Value = p; + continue; + } + if (t == typeof(string)) + { + var s_string = p as string; + cmd.Parameters.Add(createParameter(param_name, s_string.Length > 4000 ? System.Data.SqlDbType.NText : System.Data.SqlDbType.NVarChar)); + cmd.Parameters[param_index].Value = s_string; + continue; + } + if (t == typeof(bool)) { + cmd.Parameters.Add(createParameter(param_name, System.Data.SqlDbType.Bit)); + cmd.Parameters[param_index].Value = (bool)p ? 1 : 0; + continue; + } + if (t == typeof(DateTime)) + { + var dt_object = DBNull.Value as object; + var dt_datetime = (DateTime)p; + if (dt_datetime != DateTime.MinValue) + { + var dt = dt_datetime.ToLocalTime(); + if (dt != DateTime.MinValue) + { + dt_object = dt; + } + } + cmd.Parameters.Add(createParameter(param_name, System.Data.SqlDbType.DateTime)); + cmd.Parameters[param_index].Value = dt_object; + continue; + } + throw new ApplicationException(Invariant($"{nameof(AddParameters)}: Unknown type: {t.Name}")); + } + } /// /// Build a connection string from DatabaseSettings. diff --git a/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml b/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml index b23db30..a773c22 100644 --- a/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml +++ b/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml @@ -443,25 +443,46 @@ - - Append object as the parameter to the parameters list. + + Append object as the parameter to the parameters list. - Returns the parameter name, that is to be used in the query currently under construction. - - List of the parameters. - Object to be added. - Parameter name. + Returns the parameter name, that is to be used in the query currently under construction. + + List of the parameters. + Object to be added. + Parameter name. - - Append object as the parameter to the parameters list. - Use the object type to apply conversions, if any needed. + + Append object as the parameter to the parameters list. + Use the object type to apply conversions, if any needed. - Returns the parameter name, that is to be used in the query currently under construction. + Returns the parameter name, that is to be used in the query currently under construction. + + List of the parameters. + Object to be added. + Parameter name. + + + + Compose a where-cluase corresponding to the tree expression. + + Expression to be converted. + Parameters to append SQL query parameters to. + Where-clause. + + + + + + Add parameters to the SQL command. - List of the parameters. - Object to be added. - Parameter name. + Subtype of DbCommand + Subtype of DbPaameter + Command to add parameters to. + Parameter constructor. + Parameters to be added. + diff --git a/src/DatabaseWrapper.SqlServer/DatabaseClient.cs b/src/DatabaseWrapper.SqlServer/DatabaseClient.cs index b36e966..75533dc 100644 --- a/src/DatabaseWrapper.SqlServer/DatabaseClient.cs +++ b/src/DatabaseWrapper.SqlServer/DatabaseClient.cs @@ -738,7 +738,7 @@ public override DataTable Query((string Query, IEnumerable new SqlParameter(name,type), parameters); using (SqlDataAdapter sda = new SqlDataAdapter(cmd)) { @@ -806,7 +806,7 @@ public override async Task QueryAsync((string Query, IEnumerable new SqlParameter(name,type), parameters); using (SqlDataAdapter sda = new SqlDataAdapter(cmd)) { #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities4 @@ -989,72 +989,6 @@ public override string SanitizeString(string s) #region Private-Methods - static readonly Dictionary SqlTypeMap = new Dictionary() { - { typeof(Int32), SqlDbType.Int }, - { typeof(Int64), SqlDbType.BigInt }, - { typeof(double), SqlDbType.Float }, - { typeof(Guid), SqlDbType.UniqueIdentifier }, - { typeof(byte[]), SqlDbType.Image }, - { typeof(DateTimeOffset), SqlDbType.DateTimeOffset }, - }; - - private static void AddParametersIfAny(SqlCommand cmd, IEnumerable> parameters) - { - if (parameters==null) - { - return; - } - foreach (var kv in parameters) - { - int param_index = cmd.Parameters.Count; - var param_name = kv.Key; - var p = kv.Value; - if (p == null) - { - cmd.Parameters.Add(new SqlParameter(param_name, SqlDbType.NVarChar)); - cmd.Parameters[param_index].Value = DBNull.Value; - continue; - } - var t = p.GetType(); - SqlDbType sql_type; - if (SqlTypeMap.TryGetValue(t, out sql_type)) - { - cmd.Parameters.Add(param_name, sql_type); - cmd.Parameters[param_index].Value = p; - continue; - } - if (t == typeof(string)) - { - var s_string = p as string; - cmd.Parameters.Add(param_name, s_string.Length > 4000 ? System.Data.SqlDbType.NText : System.Data.SqlDbType.NVarChar); - cmd.Parameters[param_index].Value = s_string; - continue; - } - if (t == typeof(bool)) { - cmd.Parameters.Add(param_name, System.Data.SqlDbType.Bit); - cmd.Parameters[param_index].Value = (bool)p ? 1 : 0; - continue; - } - if (t == typeof(DateTime)) - { - var dt_object = DBNull.Value as object; - var dt_datetime = (DateTime)p; - if (dt_datetime != DateTime.MinValue) - { - var dt = dt_datetime.ToLocalTime(); - if (dt != DateTime.MinValue) - { - dt_object = dt; - } - } - cmd.Parameters.Add(param_name, System.Data.SqlDbType.DateTime); - cmd.Parameters[param_index].Value = dt_object; - continue; - } - throw new ApplicationException(Invariant($"DatabaseClient: Unknown type: {t.Name}")); - } - } - /// /// Dispose of the object. /// diff --git a/src/DatabaseWrapper.Sqlite/DatabaseClient.cs b/src/DatabaseWrapper.Sqlite/DatabaseClient.cs index d381f2e..e798e7a 100644 --- a/src/DatabaseWrapper.Sqlite/DatabaseClient.cs +++ b/src/DatabaseWrapper.Sqlite/DatabaseClient.cs @@ -786,7 +786,7 @@ public override DataTable Query((string Query, IEnumerable new SqliteParameter(name, type), parameters); using (SqliteDataReader rdr = cmd.ExecuteReader()) { result.Load(rdr); @@ -852,7 +852,7 @@ public override async Task QueryAsync((string Query, IEnumerable new SqliteParameter(name, type), parameters); using (SqliteDataReader rdr = await cmd.ExecuteReaderAsync(token).ConfigureAwait(false)) { result.Load(rdr); @@ -1022,73 +1022,6 @@ public override string SanitizeString(string s) #endregion #region Private-Methods - - static readonly Dictionary SqlTypeMap = new Dictionary() { - { typeof(Int32), SqlDbType.Int }, - { typeof(Int64), SqlDbType.BigInt }, - { typeof(double), SqlDbType.Float }, - { typeof(Guid), SqlDbType.UniqueIdentifier }, - { typeof(byte[]), SqlDbType.Image }, - { typeof(DateTimeOffset), SqlDbType.DateTimeOffset }, - }; - - private static void AddParametersIfAny(SqliteCommand cmd, IEnumerable> parameters) - { - if (parameters==null) - { - return; - } - foreach (var kv in parameters) - { - int param_index = cmd.Parameters.Count; - var param_name = kv.Key; - var p = kv.Value; - if (p == null) - { - cmd.Parameters.Add(new SqliteParameter(param_name, SqlDbType.NVarChar)); - cmd.Parameters[param_index].Value = DBNull.Value; - continue; - } - var t = p.GetType(); - SqlDbType sql_type; - if (SqlTypeMap.TryGetValue(t, out sql_type)) - { - cmd.Parameters.Add(new SqliteParameter(param_name, sql_type)); - cmd.Parameters[param_index].Value = p; - continue; - } - if (t == typeof(string)) - { - var s_string = p as string; - cmd.Parameters.Add(new SqliteParameter(param_name, s_string.Length > 4000 ? System.Data.SqlDbType.NText : System.Data.SqlDbType.NVarChar)); - cmd.Parameters[param_index].Value = s_string; - continue; - } - if (t == typeof(bool)) { - cmd.Parameters.Add(new SqliteParameter(param_name, System.Data.SqlDbType.Bit)); - cmd.Parameters[param_index].Value = (bool)p ? 1 : 0; - continue; - } - if (t == typeof(DateTime)) - { - var dt_object = DBNull.Value as object; - var dt_datetime = (DateTime)p; - if (dt_datetime != DateTime.MinValue) - { - var dt = dt_datetime.ToLocalTime(); - if (dt != DateTime.MinValue) - { - dt_object = dt; - } - } - cmd.Parameters.Add(new SqliteParameter(param_name, System.Data.SqlDbType.DateTime)); - cmd.Parameters[param_index].Value = dt_object; - continue; - } - throw new ApplicationException(Invariant($"DatabaseClient: Unknown type: {t.Name}")); - } - } - /// /// Dispose of the object. /// From 2841558db159464a05140f23848c63a1cfc53d44 Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Thu, 28 Dec 2023 18:10:57 +0100 Subject: [PATCH 07/11] DatabaseWrapper.Postgresql: Changed to use parametrized SQL queries. --- .../DatabaseClient.cs | 59 +- .../DatabaseWrapper.Postgresql.xml | 65 +- .../PostgresqlHelper.cs | 763 ++---------------- 3 files changed, 114 insertions(+), 773 deletions(-) diff --git a/src/DatabaseWrapper.Postgresql/DatabaseClient.cs b/src/DatabaseWrapper.Postgresql/DatabaseClient.cs index 208e997..7226844 100644 --- a/src/DatabaseWrapper.Postgresql/DatabaseClient.cs +++ b/src/DatabaseWrapper.Postgresql/DatabaseClient.cs @@ -9,6 +9,7 @@ using ExpressionTree; using DatabaseWrapper.Core; using System.Threading; +using System.Runtime.CompilerServices; namespace DatabaseWrapper.Postgresql { @@ -34,6 +35,7 @@ private set } } +#if (false) /// /// Timestamp format. /// Default is MM/dd/yyyy hh:mm:ss.fffffff tt. @@ -67,6 +69,7 @@ private set _Helper.TimestampOffsetFormat = value; } } +#endif /// /// Maximum supported statement length. @@ -116,6 +119,16 @@ private set private string _SumColumnName = "__sum__"; private PostgresqlHelper _Helper = new PostgresqlHelper(); + private static IEnumerable> CorrectDateTimeOffsets(IEnumerable> parameters) + => parameters?.Select(kv => { + // TODO: is it correct to drop the offset on DateTimeOffset parameters? + if (kv.Value != null && kv.Value.GetType()==typeof(DateTimeOffset)) + { + var dto = (DateTimeOffset)kv.Value; + if (dto.Offset != TimeSpan.Zero) return new KeyValuePair(kv.Key, new DateTimeOffset(dto.UtcDateTime)); + } + return kv; + }); #endregion #region Constructors-and-Factories @@ -733,10 +746,11 @@ public override async Task TruncateAsync(string tableName, CancellationToken tok /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Database query defined outside of the database client, and, the corresponding parameters. /// A DataTable containing the results. - public override DataTable Query(string query) + public override DataTable Query((string Query, IEnumerable> Parameters) queryAndParameters) { + (var query, var parameters) = queryAndParameters; if (String.IsNullOrEmpty(query)) throw new ArgumentNullException(query); if (query.Length > MaxStatementLength) throw new ArgumentException("Query exceeds maximum statement length of " + MaxStatementLength + " characters."); @@ -754,16 +768,20 @@ public override DataTable Query(string query) conn.Open(); #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities - using (NpgsqlDataAdapter da = new NpgsqlDataAdapter(query, conn)) + using (var cmd = new NpgsqlCommand(query, conn)) { + DatabaseHelperBase.AddParameters(cmd, (name,type) => new NpgsqlParameter(name,type), CorrectDateTimeOffsets(parameters)); + using (NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd)) + { #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities - DataSet ds = new DataSet(); - da.Fill(ds); + DataSet ds = new DataSet(); + da.Fill(ds); - if (ds != null && ds.Tables != null && ds.Tables.Count > 0) - { - result = ds.Tables[0]; + if (ds != null && ds.Tables != null && ds.Tables.Count > 0) + { + result = ds.Tables[0]; + } } } @@ -800,11 +818,12 @@ public override DataTable Query(string query) /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Database query defined outside of the database client, and, the corresponding parameters. /// Cancellation token. /// A DataTable containing the results. - public override async Task QueryAsync(string query, CancellationToken token = default) + public override async Task QueryAsync((string Query, IEnumerable> Parameters) queryAndParameters, CancellationToken token = default) { + (var query, var parameters) = queryAndParameters; if (String.IsNullOrEmpty(query)) throw new ArgumentNullException(query); if (query.Length > MaxStatementLength) throw new ArgumentException("Query exceeds maximum statement length of " + MaxStatementLength + " characters."); @@ -821,17 +840,21 @@ public override async Task QueryAsync(string query, CancellationToken { await conn.OpenAsync(token).ConfigureAwait(false); -#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities - using (NpgsqlDataAdapter da = new NpgsqlDataAdapter(query, conn)) +#pragma warning disable CA2100 // Review SQL queries for security + using (var cmd = new NpgsqlCommand(query, conn)) { + DatabaseHelperBase.AddParameters(cmd, (name,type) => new NpgsqlParameter(name,type), CorrectDateTimeOffsets(parameters)); + using (NpgsqlDataAdapter da = new NpgsqlDataAdapter(cmd)) + { #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities - DataSet ds = new DataSet(); - da.Fill(ds); + DataSet ds = new DataSet(); + da.Fill(ds); - if (ds != null && ds.Tables != null && ds.Tables.Count > 0) - { - result = ds.Tables[0]; + if (ds != null && ds.Tables != null && ds.Tables.Count > 0) + { + result = ds.Tables[0]; + } } } @@ -984,6 +1007,7 @@ public override async Task SumAsync(string tableName, string fieldName, return 0m; } +#if (false) /// /// Create a string timestamp from the given DateTime. /// @@ -1003,6 +1027,7 @@ public override string TimestampOffset(DateTimeOffset ts) { return _Helper.GenerateTimestampOffset(ts); } +#endif /// /// Sanitize an input string. diff --git a/src/DatabaseWrapper.Postgresql/DatabaseWrapper.Postgresql.xml b/src/DatabaseWrapper.Postgresql/DatabaseWrapper.Postgresql.xml index 7682e4b..1c057e3 100644 --- a/src/DatabaseWrapper.Postgresql/DatabaseWrapper.Postgresql.xml +++ b/src/DatabaseWrapper.Postgresql/DatabaseWrapper.Postgresql.xml @@ -14,18 +14,6 @@ The connection string used to connect to the database. - - - Timestamp format. - Default is MM/dd/yyyy hh:mm:ss.fffffff tt. - - - - - Timestamp format with offset. - Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. - - Maximum supported statement length. @@ -308,18 +296,18 @@ The table you wish to TRUNCATE. Cancellation token. - + Execute a query. - Database query defined outside of the database client. + Database query defined outside of the database client, and, the corresponding parameters. A DataTable containing the results. - + Execute a query. - Database query defined outside of the database client. + Database query defined outside of the database client, and, the corresponding parameters. Cancellation token. A DataTable containing the results. @@ -376,20 +364,6 @@ Cancellation token. The sum of the specified column from the matching rows. - - - Create a string timestamp from the given DateTime. - - DateTime. - A string with formatted timestamp. - - - - Create a string timestamp with offset from the given DateTimeOffset. - - DateTimeOffset. - A string with formatted timestamp. - Sanitize an input string. @@ -408,16 +382,6 @@ PostgreSQL implementation of helper properties and methods. - - - Timestamp format for use in DateTime.ToString([format]). - - - - - Timestamp offset format for use in DateTimeOffset.ToString([format]). - - Build a connection string from DatabaseSettings. @@ -555,20 +519,6 @@ Expression filter. String. - - - Retrieve a timestamp in the database format. - - DateTime. - String. - - - - Retrieve a timestamp offset in the database format. - - DateTimeOffset. - String. - Extract the table name from an encapsulated name. @@ -576,5 +526,12 @@ String. String. + + + Prepare a field name for use in a SQL query. + + Name of the field to be prepared. + Field name for use in a SQL query. + diff --git a/src/DatabaseWrapper.Postgresql/PostgresqlHelper.cs b/src/DatabaseWrapper.Postgresql/PostgresqlHelper.cs index b18a934..d575a3e 100644 --- a/src/DatabaseWrapper.Postgresql/PostgresqlHelper.cs +++ b/src/DatabaseWrapper.Postgresql/PostgresqlHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using static System.FormattableString; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -7,15 +8,18 @@ using DatabaseWrapper.Core; using ExpressionTree; + namespace DatabaseWrapper.Postgresql { + using QueryAndParameters = System.ValueTuple>>; + /// /// PostgreSQL implementation of helper properties and methods. /// public class PostgresqlHelper : DatabaseHelperBase { #region Public-Members - +#if (false) /// /// Timestamp format for use in DateTime.ToString([format]). /// @@ -25,7 +29,7 @@ public class PostgresqlHelper : DatabaseHelperBase /// Timestamp offset format for use in DateTimeOffset.ToString([format]). /// public new string TimestampOffsetFormat { get; set; } = "MM/dd/yyyy hh:mm:ss.fffffff zzz"; - +#endif #endregion #region Private-Members @@ -226,7 +230,7 @@ public override string DropTableQuery(string tableName) /// Expression filter. /// Result order. /// String. - public override string SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder) + public override QueryAndParameters SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder) { string query = ""; string whereClause = ""; @@ -259,7 +263,8 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe query += "FROM " + PreparedTableName(tableName) + " "; // expressions - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters_list = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters_list); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; @@ -281,7 +286,7 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe } } - return query; + return (query, parameters_list); } /// @@ -290,22 +295,19 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe /// The table in which you wish to INSERT. /// The key-value pairs for the row you wish to INSERT. /// String. - public override string InsertQuery(string tableName, Dictionary keyValuePairs) + public override QueryAndParameters InsertQuery(string tableName, Dictionary keyValuePairs) { + var o_ret = keyValuePairs.Select(kv => new KeyValuePair("@F_" + kv.Key, kv.Value)); string ret = "INSERT INTO " + PreparedTableName(tableName) + " " + "("; - string keys = ""; - string vals = ""; - BuildKeysValuesFromDictionary(keyValuePairs, out keys, out vals); - - ret += keys + ") " + + ret += string.Join(", ", keyValuePairs.Keys.Select(k => PreparedFieldName(k))) + ") " + "VALUES " + - "(" + vals + ") " + + "(" + string.Join(", ", o_ret.Select(k => k.Key)) + ") " + "RETURNING *;"; - return ret; + return (ret, o_ret); } /// @@ -314,11 +316,11 @@ public override string InsertQuery(string tableName, Dictionary /// The table in which you wish to INSERT. /// List of dictionaries containing key-value pairs for the rows you wish to INSERT. /// String. - public override string InsertMultipleQuery(string tableName, List> keyValuePairList) + public override QueryAndParameters InsertMultipleQuery(string tableName, List> keyValuePairList) { ValidateInputDictionaries(keyValuePairList); string keys = BuildKeysFromDictionary(keyValuePairList[0]); - List values = BuildValuesFromDictionaries(keyValuePairList); + var ret_values = new List>(); string ret = "BEGIN TRANSACTION;" + @@ -326,18 +328,22 @@ public override string InsertMultipleQuery(string tableName, List 0) ret += ","; - ret += " (" + value + ")"; - added++; + var dict = keyValuePairList[i_dict]; + var prefix = Invariant($"@F{i_dict}_"); + var this_round = dict.Select(kv => new KeyValuePair(prefix + kv.Key, kv.Value)); + if (i_dict>0) { + ret += ", "; + } + ret += "(" + string.Join(", ", this_round.Select(kv => kv.Key)) + ")"; + ret_values.AddRange(this_round); } ret += "; COMMIT; "; - return ret; + return (ret, ret_values); } /// @@ -347,18 +353,19 @@ public override string InsertMultipleQuery(string tableName, ListThe key-value pairs for the data you wish to UPDATE. /// The expression containing the UPDATE filter (i.e. WHERE clause data). /// String. - public override string UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter) + public override QueryAndParameters UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter) { - string keyValueClause = BuildKeyValueClauseFromDictionary(keyValuePairs); + const string FIELD_PREFIX = "@F"; + var parameters = keyValuePairs.Select(kv => new KeyValuePair(FIELD_PREFIX + kv.Key, kv.Value)).ToList(); string ret = "UPDATE " + PreparedTableName(tableName) + " SET " + - keyValueClause + " "; + string.Join(", ", parameters.Select(kv => kv.Key.Substring(FIELD_PREFIX.Length) + "=" + kv.Key)) + " "; - if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter) + " "; + if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter, parameters) + " "; ret += "RETURNING *"; - return ret; + return (ret, parameters); } /// @@ -367,14 +374,15 @@ public override string UpdateQuery(string tableName, Dictionary /// Table name. /// Expression filter. /// String. - public override string DeleteQuery(string tableName, Expr filter) + public override QueryAndParameters DeleteQuery(string tableName, Expr filter) { string ret = "DELETE FROM " + PreparedTableName(tableName) + " "; - if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter) + " "; + var parameters = new List>(); + if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter, parameters) + " "; - return ret; + return (ret, parameters); } /// @@ -393,7 +401,7 @@ public override string TruncateQuery(string tableName) /// Table name. /// Expression filter. /// String. - public override string ExistsQuery(string tableName, Expr filter) + public override QueryAndParameters ExistsQuery(string tableName, Expr filter) { string query = ""; string whereClause = ""; @@ -404,14 +412,15 @@ public override string ExistsQuery(string tableName, Expr filter) "FROM " + PreparedTableName(tableName) + " "; // expressions - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } query += "LIMIT 1"; - return query; + return (query, parameters); } /// @@ -421,7 +430,7 @@ public override string ExistsQuery(string tableName, Expr filter) /// Column name to use to temporarily store the result. /// Expression filter. /// String. - public override string CountQuery(string tableName, string countColumnName, Expr filter) + public override QueryAndParameters CountQuery(string tableName, string countColumnName, Expr filter) { string query = ""; string whereClause = ""; @@ -432,13 +441,14 @@ public override string CountQuery(string tableName, string countColumnName, Expr "FROM " + PreparedTableName(tableName) + " "; // expressions - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } - return query; + return (query, parameters); } /// @@ -449,7 +459,7 @@ public override string CountQuery(string tableName, string countColumnName, Expr /// Column name to temporarily store the result. /// Expression filter. /// String. - public override string SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter) + public override QueryAndParameters SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter) { string whereClause = ""; @@ -459,15 +469,17 @@ public override string SumQuery(string tableName, string fieldName, string sumCo "FROM " + PreparedTableName(tableName) + " "; // expressions - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } - return query; + return (query, parameters); } +#if (false) /// /// Retrieve a timestamp in the database format. /// @@ -487,6 +499,7 @@ public override string GenerateTimestampOffset(DateTimeOffset ts) { return ts.ToString(TimestampOffsetFormat); } +#endif /// /// Extract the table name from an encapsulated name. @@ -509,15 +522,20 @@ public string ExtractTableName(string s) } } - #endregion - - #region Private-Methods - - private string PreparedFieldName(string fieldName) + /// + /// Prepare a field name for use in a SQL query. + /// + /// Name of the field to be prepared. + /// Field name for use in a SQL query. + public override string PreparedFieldName(string fieldName) { return "\"" + fieldName + "\""; } + #endregion + + #region Private-Methods + private string PreparedStringValue(string str) { // uses $xx$ escaping @@ -530,474 +548,6 @@ private string PreparedUnicodeValue(string s) return PreparedStringValue(s); } - private string ExpressionToWhereClause(Expr expr) - { - if (expr == null) return null; - - string clause = ""; - - if (expr.Left == null) return null; - - clause += "("; - - if (expr.Left is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Left) + " "; - } - else - { - if (!(expr.Left is string)) - { - throw new ArgumentException("Left term must be of type Expression or String"); - } - - if (expr.Operator != OperatorEnum.Contains - && expr.Operator != OperatorEnum.ContainsNot - && expr.Operator != OperatorEnum.StartsWith - && expr.Operator != OperatorEnum.StartsWithNot - && expr.Operator != OperatorEnum.EndsWith - && expr.Operator != OperatorEnum.EndsWithNot) - { - // - // These operators will add the left term - // - clause += PreparedFieldName(expr.Left.ToString()) + " "; - } - } - - switch (expr.Operator) - { - #region Process-By-Operators - - case OperatorEnum.And: - #region And - - if (expr.Right == null) return null; - clause += "AND "; - - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.Or: - #region Or - - if (expr.Right == null) return null; - clause += "OR "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.Equals: - #region Equals - - if (expr.Right == null) return null; - clause += "= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.NotEquals: - #region NotEquals - - if (expr.Right == null) return null; - clause += "<> "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.In: - #region In - - if (expr.Right == null) return null; - int inAdded = 0; - if (!Helper.IsList(expr.Right)) return null; - List inTempList = Helper.ObjectToList(expr.Right); - clause += " IN ("; - foreach (object currObj in inTempList) - { - if (currObj == null) continue; - if (inAdded > 0) clause += ","; - if (currObj is DateTime || currObj is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(currObj)) + "'"; - } - else if (currObj is int || currObj is long || currObj is decimal) - { - clause += currObj.ToString(); - } - else - { - clause += PreparedStringValue(currObj.ToString()); - } - inAdded++; - } - clause += ")"; - break; - - #endregion - - case OperatorEnum.NotIn: - #region NotIn - - if (expr.Right == null) return null; - int notInAdded = 0; - if (!Helper.IsList(expr.Right)) return null; - List notInTempList = Helper.ObjectToList(expr.Right); - clause += " NOT IN ("; - foreach (object currObj in notInTempList) - { - if (currObj == null) continue; - if (notInAdded > 0) clause += ","; - if (currObj is DateTime || currObj is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(currObj)) + "'"; - } - else if (currObj is int || currObj is long || currObj is decimal) - { - clause += currObj.ToString(); - } - else - { - clause += PreparedStringValue(currObj.ToString()); - } - notInAdded++; - } - clause += ")"; - break; - - #endregion - - case OperatorEnum.Contains: - #region Contains - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - "OR " + PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString() + "%") + - "OR " + PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.ContainsNot: - #region ContainsNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - "OR " + PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString() + "%") + - "OR " + PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.StartsWith: - #region StartsWith - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.StartsWithNot: - #region StartsWithNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.EndsWith: - #region EndsWith - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.EndsWithNot: - #region EndsWithNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.GreaterThan: - #region GreaterThan - - if (expr.Right == null) return null; - clause += "> "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.GreaterThanOrEqualTo: - #region GreaterThanOrEqualTo - - if (expr.Right == null) return null; - clause += ">= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.LessThan: - #region LessThan - - if (expr.Right == null) return null; - clause += "< "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.LessThanOrEqualTo: - #region LessThanOrEqualTo - - if (expr.Right == null) return null; - clause += "<= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.IsNull: - #region IsNull - - clause += " IS NULL"; - break; - - #endregion - - case OperatorEnum.IsNotNull: - #region IsNotNull - - clause += " IS NOT NULL"; - break; - - #endregion - - #endregion - } - - clause += ")"; - - return clause; - } - private string BuildOrderByClause(ResultOrder[] resultOrder) { if (resultOrder == null || resultOrder.Length < 0) return null; @@ -1224,70 +774,6 @@ private string PreparedTableName(string s) } } - private void BuildKeysValuesFromDictionary(Dictionary keyValuePairs, out string keys, out string vals) - { - keys = ""; - vals = ""; - int added = 0; - - foreach (KeyValuePair currKvp in keyValuePairs) - { - if (String.IsNullOrEmpty(currKvp.Key)) continue; - - if (added > 0) - { - keys += ","; - vals += ","; - } - - keys += PreparedFieldName(currKvp.Key); - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - vals += "'" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - vals += "'" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - vals += currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - vals += ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - vals += "decode('" + Helper.ByteArrayToHexString((byte[])currKvp.Value) + "', 'hex')"; - } - else - { - if (Helper.IsExtendedCharacters(currKvp.Value.ToString())) - { - vals += PreparedUnicodeValue(currKvp.Value.ToString()); - } - else - { - vals += PreparedStringValue(currKvp.Value.ToString()); - } - } - } - else - { - vals += "null"; - } - - added++; - } - } private void ValidateInputDictionaries(List> keyValuePairList) { @@ -1318,133 +804,6 @@ private string BuildKeysFromDictionary(Dictionary reference) return keys; } - - private List BuildValuesFromDictionaries(List> dicts) - { - List values = new List(); - - foreach (Dictionary currDict in dicts) - { - string vals = ""; - int valsAdded = 0; - - foreach (KeyValuePair currKvp in currDict) - { - if (valsAdded > 0) vals += ","; - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - vals += "'" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - vals += "'" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - vals += currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - vals += ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - vals += "decode('" + Helper.ByteArrayToHexString((byte[])currKvp.Value) + "', 'hex')"; - } - else - { - if (Helper.IsExtendedCharacters(currKvp.Value.ToString())) - { - vals += PreparedUnicodeValue(currKvp.Value.ToString()); - } - else - { - vals += PreparedStringValue(currKvp.Value.ToString()); - } - } - - } - else - { - vals += "null"; - } - - valsAdded++; - } - - values.Add(vals); - } - - return values; - } - - private string BuildKeyValueClauseFromDictionary(Dictionary keyValuePairs) - { - string keyValueClause = ""; - int added = 0; - - foreach (KeyValuePair currKvp in keyValuePairs) - { - if (String.IsNullOrEmpty(currKvp.Key)) continue; - - if (added > 0) keyValueClause += ","; - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "='" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "='" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + "decode('" + Helper.ByteArrayToHexString((byte[])currKvp.Value) + "', 'hex')"; - } - else - { - if (Helper.IsExtendedCharacters(currKvp.Value.ToString())) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + PreparedUnicodeValue(currKvp.Value.ToString()); - } - else - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + PreparedStringValue(currKvp.Value.ToString()); - } - } - } - else - { - keyValueClause += PreparedFieldName(currKvp.Key) + "= null"; - } - - added++; - } - - return keyValueClause; - } - #endregion } } From 4b11f3dfa14e8785c4548125eeb926e5d514b58d Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Thu, 28 Dec 2023 18:40:49 +0100 Subject: [PATCH 08/11] DatebaseWrapper.Core: Restored DatabaseClientBase members Timestamp, TimestampFormat and TimestampOffsetFormat. --- .../DatabaseClientBase.cs | 14 +++++ .../DatabaseHelperBase.cs | 25 +++++++++ .../DatabaseWrapper.Core.xml | 38 +++++++++++++ .../DatabaseClient.cs | 4 -- .../DatabaseWrapper.Postgresql.xml | 50 +++++++++++++++++ .../PostgresqlHelper.cs | 6 +- .../DatabaseClient.cs | 54 ++++++++++++++++++ .../DatabaseWrapper.SqlServer.xml | 50 +++++++++++++++++ .../SqlServerHelper.cs | 30 ++++++++++ src/DatabaseWrapper.Sqlite/DatabaseClient.cs | 55 +++++++++++++++++++ .../DatabaseWrapper.Sqlite.xml | 50 +++++++++++++++++ src/DatabaseWrapper.Sqlite/SqliteHelper.cs | 30 ++++++++++ 12 files changed, 398 insertions(+), 8 deletions(-) diff --git a/src/DatabaseWrapper.Core/DatabaseClientBase.cs b/src/DatabaseWrapper.Core/DatabaseClientBase.cs index d1eb491..3f8a40a 100644 --- a/src/DatabaseWrapper.Core/DatabaseClientBase.cs +++ b/src/DatabaseWrapper.Core/DatabaseClientBase.cs @@ -390,6 +390,20 @@ public DataTable Query(string query) /// The sum of the specified column from the matching rows. public abstract Task SumAsync(string tableName, string fieldName, Expr filter, CancellationToken token = default); + /// + /// Create a string timestamp from the given DateTime. + /// + /// DateTime. + /// A string with formatted timestamp. + public abstract string Timestamp(DateTime ts); + + /// + /// Create a string timestamp with offset from the given DateTimeOffset. + /// + /// DateTimeOffset. + /// A string with formatted timestamp. + public abstract string TimestampOffset(DateTimeOffset ts); + /// /// Sanitize an input string. /// diff --git a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs index fc9b704..e3e3bdd 100644 --- a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs +++ b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs @@ -15,6 +15,17 @@ namespace DatabaseWrapper.Core public abstract class DatabaseHelperBase { #region Public-Members + + /// + /// Timestamp format for use in DateTime.ToString([format]). + /// + public string TimestampFormat; + + /// + /// Timestamp offset format for use in DateTimeOffset.ToString([format]). + /// + public string TimestampOffsetFormat; + #endregion #region Private-Members @@ -435,6 +446,20 @@ public static void AddParameters(TC cmd, Func cre /// String. public abstract QueryAndParameters SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter); + /// + /// Retrieve a timestamp in the database format. + /// + /// DateTime. + /// String. + public abstract string GenerateTimestamp(DateTime ts); + + /// + /// Retrieve a timestamp offset in the database format. + /// + /// DateTimeOffset. + /// String. + public abstract string GenerateTimestampOffset(DateTimeOffset ts); + #endregion } } diff --git a/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml b/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml index a773c22..fb00751 100644 --- a/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml +++ b/src/DatabaseWrapper.Core/DatabaseWrapper.Core.xml @@ -430,6 +430,20 @@ Cancellation token. The sum of the specified column from the matching rows. + + + Create a string timestamp from the given DateTime. + + DateTime. + A string with formatted timestamp. + + + + Create a string timestamp with offset from the given DateTimeOffset. + + DateTimeOffset. + A string with formatted timestamp. + Sanitize an input string. @@ -442,6 +456,16 @@ Base implementation of helper properties and methods. + + + Timestamp format for use in DateTime.ToString([format]). + + + + + Timestamp offset format for use in DateTimeOffset.ToString([format]). + + Append object as the parameter to the parameters list. @@ -628,6 +652,20 @@ Expression filter. String. + + + Retrieve a timestamp in the database format. + + DateTime. + String. + + + + Retrieve a timestamp offset in the database format. + + DateTimeOffset. + String. + Database query event arguments. diff --git a/src/DatabaseWrapper.Postgresql/DatabaseClient.cs b/src/DatabaseWrapper.Postgresql/DatabaseClient.cs index 7226844..5dd28a7 100644 --- a/src/DatabaseWrapper.Postgresql/DatabaseClient.cs +++ b/src/DatabaseWrapper.Postgresql/DatabaseClient.cs @@ -35,7 +35,6 @@ private set } } -#if (false) /// /// Timestamp format. /// Default is MM/dd/yyyy hh:mm:ss.fffffff tt. @@ -69,7 +68,6 @@ private set _Helper.TimestampOffsetFormat = value; } } -#endif /// /// Maximum supported statement length. @@ -1007,7 +1005,6 @@ public override async Task SumAsync(string tableName, string fieldName, return 0m; } -#if (false) /// /// Create a string timestamp from the given DateTime. /// @@ -1027,7 +1024,6 @@ public override string TimestampOffset(DateTimeOffset ts) { return _Helper.GenerateTimestampOffset(ts); } -#endif /// /// Sanitize an input string. diff --git a/src/DatabaseWrapper.Postgresql/DatabaseWrapper.Postgresql.xml b/src/DatabaseWrapper.Postgresql/DatabaseWrapper.Postgresql.xml index 1c057e3..0f5f038 100644 --- a/src/DatabaseWrapper.Postgresql/DatabaseWrapper.Postgresql.xml +++ b/src/DatabaseWrapper.Postgresql/DatabaseWrapper.Postgresql.xml @@ -14,6 +14,18 @@ The connection string used to connect to the database. + + + Timestamp format. + Default is MM/dd/yyyy hh:mm:ss.fffffff tt. + + + + + Timestamp format with offset. + Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. + + Maximum supported statement length. @@ -364,6 +376,20 @@ Cancellation token. The sum of the specified column from the matching rows. + + + Create a string timestamp from the given DateTime. + + DateTime. + A string with formatted timestamp. + + + + Create a string timestamp with offset from the given DateTimeOffset. + + DateTimeOffset. + A string with formatted timestamp. + Sanitize an input string. @@ -382,6 +408,16 @@ PostgreSQL implementation of helper properties and methods. + + + Timestamp format for use in DateTime.ToString([format]). + + + + + Timestamp offset format for use in DateTimeOffset.ToString([format]). + + Build a connection string from DatabaseSettings. @@ -519,6 +555,20 @@ Expression filter. String. + + + Retrieve a timestamp in the database format. + + DateTime. + String. + + + + Retrieve a timestamp offset in the database format. + + DateTimeOffset. + String. + Extract the table name from an encapsulated name. diff --git a/src/DatabaseWrapper.Postgresql/PostgresqlHelper.cs b/src/DatabaseWrapper.Postgresql/PostgresqlHelper.cs index d575a3e..a334053 100644 --- a/src/DatabaseWrapper.Postgresql/PostgresqlHelper.cs +++ b/src/DatabaseWrapper.Postgresql/PostgresqlHelper.cs @@ -19,7 +19,7 @@ namespace DatabaseWrapper.Postgresql public class PostgresqlHelper : DatabaseHelperBase { #region Public-Members -#if (false) + /// /// Timestamp format for use in DateTime.ToString([format]). /// @@ -29,7 +29,7 @@ public class PostgresqlHelper : DatabaseHelperBase /// Timestamp offset format for use in DateTimeOffset.ToString([format]). /// public new string TimestampOffsetFormat { get; set; } = "MM/dd/yyyy hh:mm:ss.fffffff zzz"; -#endif + #endregion #region Private-Members @@ -479,7 +479,6 @@ public override QueryAndParameters SumQuery(string tableName, string fieldName, return (query, parameters); } -#if (false) /// /// Retrieve a timestamp in the database format. /// @@ -499,7 +498,6 @@ public override string GenerateTimestampOffset(DateTimeOffset ts) { return ts.ToString(TimestampOffsetFormat); } -#endif /// /// Extract the table name from an encapsulated name. diff --git a/src/DatabaseWrapper.SqlServer/DatabaseClient.cs b/src/DatabaseWrapper.SqlServer/DatabaseClient.cs index 75533dc..c81e22f 100644 --- a/src/DatabaseWrapper.SqlServer/DatabaseClient.cs +++ b/src/DatabaseWrapper.SqlServer/DatabaseClient.cs @@ -35,6 +35,40 @@ private set } } + /// + /// Timestamp format. + /// Default is MM/dd/yyyy hh:mm:ss.fffffff tt. + /// + public new string TimestampFormat + { + get + { + return _Helper.TimestampFormat; + } + set + { + if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(TimestampFormat)); + _Helper.TimestampFormat = value; + } + } + + /// + /// Timestamp format with offset. + /// Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. + /// + public new string TimestampOffsetFormat + { + get + { + return _Helper.TimestampOffsetFormat; + } + set + { + if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(TimestampOffsetFormat)); + _Helper.TimestampOffsetFormat = value; + } + } + /// /// Maximum supported statement length. /// @@ -974,6 +1008,26 @@ public override async Task SumAsync(string tableName, string fieldName, return 0m; } + /// + /// Create a string timestamp from the given DateTime. + /// + /// DateTime. + /// A string with formatted timestamp. + public override string Timestamp(DateTime ts) + { + return _Helper.GenerateTimestamp(ts); + } + + /// + /// Create a string timestamp with offset from the given DateTimeOffset. + /// + /// DateTimeOffset. + /// A string with formatted timestamp. + public override string TimestampOffset(DateTimeOffset ts) + { + return _Helper.GenerateTimestampOffset(ts); + } + /// /// Sanitize an input string. /// diff --git a/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml b/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml index 0e28893..951f2bd 100644 --- a/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml +++ b/src/DatabaseWrapper.SqlServer/DatabaseWrapper.SqlServer.xml @@ -14,6 +14,18 @@ The connection string used to connect to the database. + + + Timestamp format. + Default is MM/dd/yyyy hh:mm:ss.fffffff tt. + + + + + Timestamp format with offset. + Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. + + Maximum supported statement length. @@ -365,6 +377,20 @@ Cancellation token. The sum of the specified column from the matching rows. + + + Create a string timestamp from the given DateTime. + + DateTime. + A string with formatted timestamp. + + + + Create a string timestamp with offset from the given DateTimeOffset. + + DateTimeOffset. + A string with formatted timestamp. + Sanitize an input string. @@ -383,6 +409,16 @@ SQL Server implementation of helper properties and methods. + + + Timestamp format for use in DateTime.ToString([format]). + + + + + Timestamp offset format for use in DateTimeOffset.ToString([format]). + + Build a connection string from DatabaseSettings. @@ -520,6 +556,20 @@ Expression filter. String. + + + Retrieve a timestamp in the database format. + + DateTime. + String. + + + + Retrieve a timestamp offset in the database format. + + DateTimeOffset. + String. + Extract the table name from an encapsulated name. diff --git a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs index ee96354..7b09b89 100644 --- a/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs +++ b/src/DatabaseWrapper.SqlServer/SqlServerHelper.cs @@ -19,6 +19,16 @@ public class SqlServerHelper : DatabaseHelperBase { #region Public-Members + /// + /// Timestamp format for use in DateTime.ToString([format]). + /// + public new string TimestampFormat { get; set; } = "MM/dd/yyyy hh:mm:ss.fffffff tt"; + + /// + /// Timestamp offset format for use in DateTimeOffset.ToString([format]). + /// + public new string TimestampOffsetFormat { get; set; } = "MM/dd/yyyy hh:mm:ss.fffffff zzz"; + #endregion #region Private-Members @@ -589,6 +599,26 @@ public override QueryAndParameters SumQuery(string tableName, string fieldName, return (query, parameters); } + /// + /// Retrieve a timestamp in the database format. + /// + /// DateTime. + /// String. + public override string GenerateTimestamp(DateTime ts) + { + return ts.ToLocalTime().ToString(TimestampFormat); + } + + /// + /// Retrieve a timestamp offset in the database format. + /// + /// DateTimeOffset. + /// String. + public override string GenerateTimestampOffset(DateTimeOffset ts) + { + return ts.ToString(TimestampOffsetFormat); + } + /// /// Extract the table name from an encapsulated name. /// diff --git a/src/DatabaseWrapper.Sqlite/DatabaseClient.cs b/src/DatabaseWrapper.Sqlite/DatabaseClient.cs index e798e7a..47a1211 100644 --- a/src/DatabaseWrapper.Sqlite/DatabaseClient.cs +++ b/src/DatabaseWrapper.Sqlite/DatabaseClient.cs @@ -35,6 +35,41 @@ private set } } + /// + /// Timestamp format. + /// Default is yyyy-MM-dd HH:mm:ss.ffffff. + /// + public new string TimestampFormat + { + get + { + return _Helper.TimestampFormat; + } + set + { + if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(TimestampFormat)); + _Helper.TimestampFormat = value; + } + } + + /// + /// Timestamp format with offset. + /// Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. + /// + public new string TimestampOffsetFormat + { + get + { + return _Helper.TimestampOffsetFormat; + } + set + { + if (String.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(TimestampOffsetFormat)); + _Helper.TimestampOffsetFormat = value; + } + } + + /// /// Maximum supported statement length. /// @@ -1008,6 +1043,26 @@ public override async Task SumAsync(string tableName, string fieldName, return 0m; } + /// + /// Create a string timestamp from the given DateTime. + /// + /// DateTime. + /// A string with formatted timestamp. + public override string Timestamp(DateTime ts) + { + return _Helper.GenerateTimestamp(ts); + } + + /// + /// Create a string timestamp with offset from the given DateTimeOffset. + /// + /// DateTimeOffset. + /// A string with formatted timestamp. + public override string TimestampOffset(DateTimeOffset ts) + { + return _Helper.GenerateTimestampOffset(ts); + } + /// /// Sanitize an input string. /// diff --git a/src/DatabaseWrapper.Sqlite/DatabaseWrapper.Sqlite.xml b/src/DatabaseWrapper.Sqlite/DatabaseWrapper.Sqlite.xml index 2e7afd1..4fdf7c1 100644 --- a/src/DatabaseWrapper.Sqlite/DatabaseWrapper.Sqlite.xml +++ b/src/DatabaseWrapper.Sqlite/DatabaseWrapper.Sqlite.xml @@ -14,6 +14,18 @@ The connection string used to connect to the database. + + + Timestamp format. + Default is yyyy-MM-dd HH:mm:ss.ffffff. + + + + + Timestamp format with offset. + Default is MM/dd/yyyy hh:mm:ss.fffffff zzz. + + Maximum supported statement length. @@ -356,6 +368,20 @@ Cancellation token. The sum of the specified column from the matching rows. + + + Create a string timestamp from the given DateTime. + + DateTime. + A string with formatted timestamp. + + + + Create a string timestamp with offset from the given DateTimeOffset. + + DateTimeOffset. + A string with formatted timestamp. + Sanitize an input string. @@ -374,6 +400,16 @@ Sqlite implementation of helper properties and methods. + + + Timestamp format for use in DateTime.ToString([format]). + + + + + Timestamp offset format for use in DateTimeOffset.ToString([format]). + + Build a connection string from DatabaseSettings. @@ -518,5 +554,19 @@ Expression filter. String. + + + Retrieve a timestamp in the database format. + + DateTime. + String. + + + + Retrieve a timestamp offset in the database format. + + DateTimeOffset. + String. + diff --git a/src/DatabaseWrapper.Sqlite/SqliteHelper.cs b/src/DatabaseWrapper.Sqlite/SqliteHelper.cs index e69899b..70922c1 100644 --- a/src/DatabaseWrapper.Sqlite/SqliteHelper.cs +++ b/src/DatabaseWrapper.Sqlite/SqliteHelper.cs @@ -18,6 +18,16 @@ public class SqliteHelper : DatabaseHelperBase { #region Public-Members + /// + /// Timestamp format for use in DateTime.ToString([format]). + /// + public new string TimestampFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.ffffff"; + + /// + /// Timestamp offset format for use in DateTimeOffset.ToString([format]). + /// + public new string TimestampOffsetFormat { get; set; } = "yyyy-MM-dd HH:mm:ss.fffffff zzz"; + #endregion #region Private-Members @@ -580,6 +590,26 @@ public override QueryAndParameters SumQuery(string tableName, string fieldName, return (query, parameters); } + /// + /// Retrieve a timestamp in the database format. + /// + /// DateTime. + /// String. + public override string GenerateTimestamp(DateTime ts) + { + return ts.ToString(TimestampFormat); + } + + /// + /// Retrieve a timestamp offset in the database format. + /// + /// DateTimeOffset. + /// String. + public override string GenerateTimestampOffset(DateTimeOffset ts) + { + return ts.ToString(TimestampOffsetFormat); + } + #endregion #region Private-Methods From c2a11660b7cc8aac7ae06de32659e06e40b38ad6 Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Thu, 28 Dec 2023 20:14:24 +0100 Subject: [PATCH 09/11] DatabaseWrapper.Mysql: Use sql parameters. NB! Not working yet. --- src/DatabaseWrapper.Mysql/DatabaseClient.cs | 12 +- .../DatabaseWrapper.Mysql.xml | 15 +- src/DatabaseWrapper.Mysql/MysqlHelper.cs | 766 ++---------------- 3 files changed, 75 insertions(+), 718 deletions(-) diff --git a/src/DatabaseWrapper.Mysql/DatabaseClient.cs b/src/DatabaseWrapper.Mysql/DatabaseClient.cs index 87b4896..a73a96f 100644 --- a/src/DatabaseWrapper.Mysql/DatabaseClient.cs +++ b/src/DatabaseWrapper.Mysql/DatabaseClient.cs @@ -794,10 +794,11 @@ public override async Task TruncateAsync(string tableName, CancellationToken tok /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Database query defined outside of the database client, and, the corresponding parameters. /// A DataTable containing the results. - public override DataTable Query(string query) + public override DataTable Query((string Query, IEnumerable> Parameters) queryAndParameters) { + (var query, var parameters) = queryAndParameters; if (String.IsNullOrEmpty(query)) throw new ArgumentNullException(query); if (query.Length > MaxStatementLength) throw new ArgumentException("Query exceeds maximum statement length of " + MaxStatementLength + " characters."); @@ -822,6 +823,7 @@ public override DataTable Query(string query) cmd.CommandText = query; #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities + DatabaseHelperBase.AddParameters(cmd, (name, type) => new MySqlParameter(name, type), parameters); using (MySqlDataAdapter sda = new MySqlDataAdapter(cmd)) { DataSet ds = new DataSet(); @@ -872,11 +874,12 @@ public override DataTable Query(string query) /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Database query defined outside of the database client, and, the corresponding parameters. /// Cancellation token. /// A DataTable containing the results. - public override async Task QueryAsync(string query, CancellationToken token = default) + public override async Task QueryAsync((string Query, IEnumerable> Parameters) queryAndParameters, CancellationToken token = default) { + (var query, var parameters) = queryAndParameters; if (String.IsNullOrEmpty(query)) throw new ArgumentNullException(query); if (query.Length > MaxStatementLength) throw new ArgumentException("Query exceeds maximum statement length of " + MaxStatementLength + " characters."); @@ -901,6 +904,7 @@ public override async Task QueryAsync(string query, CancellationToken cmd.CommandText = query; #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities + DatabaseHelperBase.AddParameters(cmd, (name, type) => new MySqlParameter(name, type), parameters); using (MySqlDataAdapter sda = new MySqlDataAdapter(cmd)) { DataSet ds = new DataSet(); diff --git a/src/DatabaseWrapper.Mysql/DatabaseWrapper.Mysql.xml b/src/DatabaseWrapper.Mysql/DatabaseWrapper.Mysql.xml index 2b0d056..1a5c345 100644 --- a/src/DatabaseWrapper.Mysql/DatabaseWrapper.Mysql.xml +++ b/src/DatabaseWrapper.Mysql/DatabaseWrapper.Mysql.xml @@ -304,18 +304,18 @@ The table you wish to TRUNCATE. Cancellation token. - + Execute a query. - Database query defined outside of the database client. + Database query defined outside of the database client, and, the corresponding parameters. A DataTable containing the results. - + Execute a query. - Database query defined outside of the database client. + Database query defined outside of the database client, and, the corresponding parameters. Cancellation token. A DataTable containing the results. @@ -565,5 +565,12 @@ DateTimeOffset. String. + + + Prepare a field name for use in a SQL query. + + Name of the field to be prepared. + Field name for use in a SQL query. + diff --git a/src/DatabaseWrapper.Mysql/MysqlHelper.cs b/src/DatabaseWrapper.Mysql/MysqlHelper.cs index 8978f0a..3d9646c 100644 --- a/src/DatabaseWrapper.Mysql/MysqlHelper.cs +++ b/src/DatabaseWrapper.Mysql/MysqlHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using static System.FormattableString; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -10,6 +11,8 @@ namespace DatabaseWrapper.Mysql { + using QueryAndParameters = System.ValueTuple>>; + /// /// MySQL implementation of helper properties and methods. /// @@ -227,7 +230,7 @@ public override string DropTableQuery(string tableName) /// Expression filter. /// Result order. /// String. - public override string SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder) + public override QueryAndParameters SelectQuery(string tableName, int? indexStart, int? maxResults, List returnFields, Expr filter, ResultOrder[] resultOrder) { string query = ""; string whereClause = ""; @@ -268,7 +271,8 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe // // expressions // - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters_list = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters_list); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; @@ -294,7 +298,7 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe } } - return query; + return (query, parameters_list); } /// @@ -303,24 +307,21 @@ public override string SelectQuery(string tableName, int? indexStart, int? maxRe /// The table in which you wish to INSERT. /// The key-value pairs for the row you wish to INSERT. /// String. - public override string InsertQuery(string tableName, Dictionary keyValuePairs) + public override QueryAndParameters InsertQuery(string tableName, Dictionary keyValuePairs) { + var o_ret = keyValuePairs.Select(kv => new KeyValuePair("@F_" + kv.Key, kv.Value)); string ret = "START TRANSACTION; " + "INSERT INTO `" + SanitizeString(tableName) + "` " + "("; - string keys = ""; - string vals = ""; - BuildKeysValuesFromDictionary(keyValuePairs, out keys, out vals); - - ret += keys + ") " + + ret += string.Join(", ", keyValuePairs.Keys.Select(k => PreparedFieldName(k))) + ") " + "VALUES " + - "(" + vals + "); " + + "(" + string.Join(", ", o_ret.Select(k => k.Key)) + "); " + "SELECT LAST_INSERT_ID() AS id; " + "COMMIT; "; - return ret; + return (ret, o_ret); } /// @@ -329,11 +330,11 @@ public override string InsertQuery(string tableName, Dictionary /// The table in which you wish to INSERT. /// List of dictionaries containing key-value pairs for the rows you wish to INSERT. /// String. - public override string InsertMultipleQuery(string tableName, List> keyValuePairList) + public override QueryAndParameters InsertMultipleQuery(string tableName, List> keyValuePairList) { ValidateInputDictionaries(keyValuePairList); string keys = BuildKeysFromDictionary(keyValuePairList[0]); - List values = BuildValuesFromDictionaries(keyValuePairList); + var ret_values = new List>(); string ret = "START TRANSACTION;" + @@ -341,18 +342,22 @@ public override string InsertMultipleQuery(string tableName, List 0) ret += ","; - ret += " (" + value + ")"; - added++; + var dict = keyValuePairList[i_dict]; + var prefix = Invariant($"@F{i_dict}_"); + var this_round = dict.Select(kv => new KeyValuePair(prefix + kv.Key, kv.Value)); + if (i_dict>0) { + ret += ", "; + } + ret += "(" + string.Join(", ", this_round.Select(kv => kv.Key)) + ")"; + ret_values.AddRange(this_round); } ret += "; COMMIT; "; - return ret; + return (ret, ret_values); } /// @@ -362,17 +367,18 @@ public override string InsertMultipleQuery(string tableName, ListThe key-value pairs for the data you wish to UPDATE. /// The expression containing the UPDATE filter (i.e. WHERE clause data). /// String. - public override string UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter) + public override QueryAndParameters UpdateQuery(string tableName, Dictionary keyValuePairs, Expr filter) { - string keyValueClause = BuildKeyValueClauseFromDictionary(keyValuePairs); + const string FIELD_PREFIX = "@F"; + var parameters = keyValuePairs.Select(kv => new KeyValuePair(FIELD_PREFIX + kv.Key, kv.Value)).ToList(); string ret = "UPDATE `" + SanitizeString(tableName) + "` SET " + - keyValueClause + " "; + string.Join(", ", parameters.Select(kv => kv.Key.Substring(FIELD_PREFIX.Length) + "=" + kv.Key)) + " "; - if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter) + " "; + if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter, parameters) + " "; - return ret; + return (ret, parameters); } /// @@ -381,14 +387,15 @@ public override string UpdateQuery(string tableName, Dictionary /// Table name. /// Expression filter. /// String. - public override string DeleteQuery(string tableName, Expr filter) + public override QueryAndParameters DeleteQuery(string tableName, Expr filter) { string ret = "DELETE FROM `" + SanitizeString(tableName) + "` "; - if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter) + " "; + var parameters = new List>(); + if (filter != null) ret += "WHERE " + ExpressionToWhereClause(filter, parameters) + " "; - return ret; + return (ret, parameters); } /// @@ -407,7 +414,7 @@ public override string TruncateQuery(string tableName) /// Table name. /// Expression filter. /// String. - public override string ExistsQuery(string tableName, Expr filter) + public override QueryAndParameters ExistsQuery(string tableName, Expr filter) { string query = ""; string whereClause = ""; @@ -422,14 +429,15 @@ public override string ExistsQuery(string tableName, Expr filter) // // expressions // - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } query += "LIMIT 1"; - return query; + return (query, parameters); } /// @@ -439,7 +447,7 @@ public override string ExistsQuery(string tableName, Expr filter) /// Column name to use to temporarily store the result. /// Expression filter. /// String. - public override string CountQuery(string tableName, string countColumnName, Expr filter) + public override QueryAndParameters CountQuery(string tableName, string countColumnName, Expr filter) { string query = ""; string whereClause = ""; @@ -454,13 +462,14 @@ public override string CountQuery(string tableName, string countColumnName, Expr // // expressions // - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } - return query; + return (query, parameters); } /// @@ -471,7 +480,7 @@ public override string CountQuery(string tableName, string countColumnName, Expr /// Column name to temporarily store the result. /// Expression filter. /// String. - public override string SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter) + public override QueryAndParameters SumQuery(string tableName, string fieldName, string sumColumnName, Expr filter) { string query = ""; string whereClause = ""; @@ -486,13 +495,14 @@ public override string SumQuery(string tableName, string fieldName, string sumCo // // expressions // - if (filter != null) whereClause = ExpressionToWhereClause(filter); + var parameters = new List>(); + if (filter != null) whereClause = ExpressionToWhereClause(filter, parameters); if (!String.IsNullOrEmpty(whereClause)) { query += "WHERE " + whereClause + " "; } - return query; + return (query, parameters); } /// @@ -515,488 +525,25 @@ public override string GenerateTimestampOffset(DateTimeOffset ts) return ts.DateTime.ToString(TimestampFormat); } - #endregion - - #region Private-Methods - - private string PreparedFieldName(string fieldName) + /// + /// Prepare a field name for use in a SQL query. + /// + /// Name of the field to be prepared. + /// Field name for use in a SQL query. + public override string PreparedFieldName(string fieldName) { return "`" + fieldName + "`"; } + #endregion + + #region Private-Methods + private string PreparedStringValue(string str) { return "'" + SanitizeString(str) + "'"; } - private string ExpressionToWhereClause(Expr expr) - { - if (expr == null) return null; - - string clause = ""; - - if (expr.Left == null) return null; - - clause += "("; - - if (expr.Left is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Left) + " "; - } - else - { - if (!(expr.Left is string)) - { - throw new ArgumentException("Left term must be of type Expression or String"); - } - - if (expr.Operator != OperatorEnum.Contains - && expr.Operator != OperatorEnum.ContainsNot - && expr.Operator != OperatorEnum.StartsWith - && expr.Operator != OperatorEnum.StartsWithNot - && expr.Operator != OperatorEnum.EndsWith - && expr.Operator != OperatorEnum.EndsWithNot) - { - // - // These operators will add the left term - // - clause += PreparedFieldName(expr.Left.ToString()) + " "; - } - } - - switch (expr.Operator) - { - #region Process-By-Operators - - case OperatorEnum.And: - #region And - - if (expr.Right == null) return null; - clause += "AND "; - - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.Or: - #region Or - - if (expr.Right == null) return null; - clause += "OR "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.Equals: - #region Equals - - if (expr.Right == null) return null; - clause += "= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.NotEquals: - #region NotEquals - - if (expr.Right == null) return null; - clause += "<> "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.In: - #region In - - if (expr.Right == null) return null; - int inAdded = 0; - if (!Helper.IsList(expr.Right)) return null; - List inTempList = Helper.ObjectToList(expr.Right); - clause += " IN ("; - foreach (object currObj in inTempList) - { - if (currObj == null) continue; - if (inAdded > 0) clause += ","; - if (currObj is DateTime || currObj is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(currObj)) + "'"; - } - else if (currObj is int || currObj is long || currObj is decimal) - { - clause += currObj.ToString(); - } - else - { - clause += PreparedStringValue(currObj.ToString()); - } - inAdded++; - } - clause += ")"; - break; - - #endregion - - case OperatorEnum.NotIn: - #region NotIn - - if (expr.Right == null) return null; - int notInAdded = 0; - if (!Helper.IsList(expr.Right)) return null; - List notInTempList = Helper.ObjectToList(expr.Right); - clause += " NOT IN ("; - foreach (object currObj in notInTempList) - { - if (currObj == null) continue; - if (notInAdded > 0) clause += ","; - if (currObj is DateTime || currObj is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(currObj)) + "'"; - } - else if (currObj is int || currObj is long || currObj is decimal) - { - clause += currObj.ToString(); - } - else - { - clause += PreparedStringValue(currObj.ToString()); - } - notInAdded++; - } - clause += ")"; - break; - - #endregion - - case OperatorEnum.Contains: - #region Contains - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - "OR " + PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString() + "%") + - "OR " + PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.ContainsNot: - #region ContainsNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - "OR " + PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString() + "%") + - "OR " + PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.StartsWith: - #region StartsWith - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.StartsWithNot: - #region StartsWithNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue(expr.Right.ToString() + "%") + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.EndsWith: - #region EndsWith - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.EndsWithNot: - #region EndsWithNot - - if (expr.Right == null) return null; - if (expr.Right is string) - { - clause += - "(" + - PreparedFieldName(expr.Left.ToString()) + " NOT LIKE " + PreparedStringValue("%" + expr.Right.ToString()) + - ")"; - } - else - { - return null; - } - break; - - #endregion - - case OperatorEnum.GreaterThan: - #region GreaterThan - - if (expr.Right == null) return null; - clause += "> "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.GreaterThanOrEqualTo: - #region GreaterThanOrEqualTo - - if (expr.Right == null) return null; - clause += ">= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.LessThan: - #region LessThan - - if (expr.Right == null) return null; - clause += "< "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.LessThanOrEqualTo: - #region LessThanOrEqualTo - - if (expr.Right == null) return null; - clause += "<= "; - if (expr.Right is Expr) - { - clause += ExpressionToWhereClause((Expr)expr.Right); - } - else - { - if (expr.Right is DateTime || expr.Right is DateTime?) - { - clause += "'" + GenerateTimestamp(Convert.ToDateTime(expr.Right)) + "'"; - } - else if (expr.Right is int || expr.Right is long || expr.Right is decimal) - { - clause += expr.Right.ToString(); - } - else - { - clause += PreparedStringValue(expr.Right.ToString()); - } - } - break; - - #endregion - - case OperatorEnum.IsNull: - #region IsNull - - clause += " IS NULL"; - break; - - #endregion - - case OperatorEnum.IsNotNull: - #region IsNotNull - - clause += " IS NOT NULL"; - break; - - #endregion - - #endregion - } - - clause += ")"; - - return clause; - } - private string PreparedUnicodeValue(string s) { return "N" + PreparedStringValue(s); @@ -1020,81 +567,6 @@ private string BuildOrderByClause(ResultOrder[] resultOrder) return ret; } - private void BuildKeysValuesFromDictionary(Dictionary keyValuePairs, out string keys, out string vals) - { - keys = ""; - vals = ""; - int added = 0; - - foreach (KeyValuePair currKvp in keyValuePairs) - { - if (String.IsNullOrEmpty(currKvp.Key)) continue; - - if (added > 0) - { - keys += ","; - vals += ","; - } - - keys += PreparedFieldName(currKvp.Key); - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - vals += "'" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - vals += "'" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - vals += currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - vals += ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - vals += "x'" + BitConverter.ToString((byte[])currKvp.Value).Replace("-", "") + "'"; - } - else - { - if (IsExtendedCharacters(currKvp.Value.ToString())) - { - vals += PreparedUnicodeValue(currKvp.Value.ToString()); - } - else - { - vals += PreparedStringValue(currKvp.Value.ToString()); - } - } - } - else - { - vals += "null"; - } - - added++; - } - } - - private bool IsExtendedCharacters(string data) - { - if (String.IsNullOrEmpty(data)) return false; - foreach (char c in data) - { - if ((int)c > 255) return true; - } - return false; - } - private void ValidateInputDictionaries(List> keyValuePairList) { Dictionary reference = keyValuePairList[0]; @@ -1125,132 +597,6 @@ private string BuildKeysFromDictionary(Dictionary reference) return keys; } - private List BuildValuesFromDictionaries(List> dicts) - { - List values = new List(); - - foreach (Dictionary currDict in dicts) - { - string vals = ""; - int valsAdded = 0; - - foreach (KeyValuePair currKvp in currDict) - { - if (valsAdded > 0) vals += ","; - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - vals += "'" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - vals += "'" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - vals += currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - vals += ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - vals += "x'" + BitConverter.ToString((byte[])currKvp.Value).Replace("-", "") + "'"; - } - else - { - if (IsExtendedCharacters(currKvp.Value.ToString())) - { - vals += PreparedUnicodeValue(currKvp.Value.ToString()); - } - else - { - vals += PreparedStringValue(currKvp.Value.ToString()); - } - } - - } - else - { - vals += "null"; - } - - valsAdded++; - } - - values.Add(vals); - } - - return values; - } - - private string BuildKeyValueClauseFromDictionary(Dictionary keyValuePairs) - { - string keyValueClause = ""; - int added = 0; - - foreach (KeyValuePair currKvp in keyValuePairs) - { - if (String.IsNullOrEmpty(currKvp.Key)) continue; - - if (added > 0) keyValueClause += ","; - - if (currKvp.Value != null) - { - if (currKvp.Value is DateTime - || currKvp.Value is DateTime?) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "='" + ((DateTime)currKvp.Value).ToString(TimestampFormat) + "'"; - } - else if (currKvp.Value is DateTimeOffset - || currKvp.Value is DateTimeOffset?) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "='" + ((DateTimeOffset)currKvp.Value).ToString(TimestampOffsetFormat) + "'"; - } - else if (currKvp.Value is int - || currKvp.Value is long - || currKvp.Value is decimal) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + currKvp.Value.ToString(); - } - else if (currKvp.Value is bool) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + ((bool)currKvp.Value ? "1" : "0"); - } - else if (currKvp.Value is byte[]) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + "x'" + BitConverter.ToString((byte[])currKvp.Value).Replace("-", "") + "'"; - } - else - { - if (IsExtendedCharacters(currKvp.Value.ToString())) - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + PreparedUnicodeValue(currKvp.Value.ToString()); - } - else - { - keyValueClause += PreparedFieldName(currKvp.Key) + "=" + PreparedStringValue(currKvp.Value.ToString()); - } - } - } - else - { - keyValueClause += PreparedFieldName(currKvp.Key) + "= null"; - } - - added++; - } - - return keyValueClause; - } - #endregion } } \ No newline at end of file From 6395eb7f546302bbf421d9cb3099e471dd53ec77 Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Fri, 29 Dec 2023 10:59:45 +0100 Subject: [PATCH 10/11] DatabaseWrapper.Mysql: Corrected, works now. --- src/DatabaseWrapper.Mysql/DatabaseClient.cs | 27 ++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/DatabaseWrapper.Mysql/DatabaseClient.cs b/src/DatabaseWrapper.Mysql/DatabaseClient.cs index a73a96f..531f18b 100644 --- a/src/DatabaseWrapper.Mysql/DatabaseClient.cs +++ b/src/DatabaseWrapper.Mysql/DatabaseClient.cs @@ -116,7 +116,28 @@ private set private string _CountColumnName = "__count__"; private string _SumColumnName = "__sum__"; private MysqlHelper _Helper = new MysqlHelper(); - + + static readonly Dictionary DbTypeMapping = new Dictionary() + { + { SqlDbType.Int, MySqlDbType.Int32 }, + { SqlDbType.BigInt, MySqlDbType.Int64 }, + { SqlDbType.Float, MySqlDbType.Double }, + { SqlDbType.UniqueIdentifier, MySqlDbType.Guid }, + { SqlDbType.Image, MySqlDbType.Blob }, + { SqlDbType.DateTimeOffset, MySqlDbType.DateTime }, + { SqlDbType.NText, MySqlDbType.Text }, + { SqlDbType.NVarChar, MySqlDbType.VarChar }, + { SqlDbType.Bit, MySqlDbType.Bit }, + { SqlDbType.DateTime, MySqlDbType.DateTime } + }; + + static MySqlParameter CreateMySqlParameter(string name, SqlDbType type) + { + var real_type = DbTypeMapping[type]; + var r = new MySqlParameter(name, real_type); + return r; + } + #endregion #region Constructors-and-Factories @@ -823,7 +844,7 @@ public override DataTable Query((string Query, IEnumerable new MySqlParameter(name, type), parameters); + DatabaseHelperBase.AddParameters(cmd, CreateMySqlParameter, parameters); using (MySqlDataAdapter sda = new MySqlDataAdapter(cmd)) { DataSet ds = new DataSet(); @@ -904,7 +925,7 @@ public override async Task QueryAsync((string Query, IEnumerable new MySqlParameter(name, type), parameters); + DatabaseHelperBase.AddParameters(cmd, CreateMySqlParameter, parameters); using (MySqlDataAdapter sda = new MySqlDataAdapter(cmd)) { DataSet ds = new DataSet(); From 44b941724c6e199e70aaa1fc2a2e38c2930e01d1 Mon Sep 17 00:00:00 2001 From: Andrei Errapart Date: Fri, 29 Dec 2023 13:12:42 +0100 Subject: [PATCH 11/11] DatabaseWrapper: Updated for changes in the DatabaseWrapper.Core. --- src/DatabaseWrapper/DatabaseClient.cs | 24 ++++++++++++------------ src/DatabaseWrapper/DatabaseWrapper.xml | 8 ++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/DatabaseWrapper/DatabaseClient.cs b/src/DatabaseWrapper/DatabaseClient.cs index e888abc..87e48f4 100644 --- a/src/DatabaseWrapper/DatabaseClient.cs +++ b/src/DatabaseWrapper/DatabaseClient.cs @@ -1169,20 +1169,20 @@ public override async Task TruncateAsync(string tableName, CancellationToken tok /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Database query defined outside of the database client. /// A DataTable containing the results. - public override DataTable Query(string query) + public override DataTable Query((string Query, IEnumerable> Parameters) queryAndParameters) { switch (_Settings.Type) { case DbTypeEnum.Mysql: - return _Mysql.Query(query); + return _Mysql.Query(queryAndParameters); case DbTypeEnum.Postgresql: - return _Postgresql.Query(query); + return _Postgresql.Query(queryAndParameters); case DbTypeEnum.Sqlite: - return _Sqlite.Query(query); + return _Sqlite.Query(queryAndParameters); case DbTypeEnum.SqlServer: - return _SqlServer.Query(query); + return _SqlServer.Query(queryAndParameters); default: throw new ArgumentException("Unknown database type '" + _Settings.Type.ToString() + "'."); } @@ -1191,21 +1191,21 @@ public override DataTable Query(string query) /// /// Execute a query. /// - /// Database query defined outside of the database client. + /// Database query defined outside of the database client. /// Cancellation token. /// A DataTable containing the results. - public override async Task QueryAsync(string query, CancellationToken token = default) + public override async Task QueryAsync((string Query, IEnumerable> Parameters) queryAndParameters, CancellationToken token = default) { switch (_Settings.Type) { case DbTypeEnum.Mysql: - return await _Mysql.QueryAsync(query, token).ConfigureAwait(false); + return await _Mysql.QueryAsync(queryAndParameters, token).ConfigureAwait(false); case DbTypeEnum.Postgresql: - return await _Postgresql.QueryAsync(query, token).ConfigureAwait(false); + return await _Postgresql.QueryAsync(queryAndParameters, token).ConfigureAwait(false); case DbTypeEnum.Sqlite: - return await _Sqlite.QueryAsync(query, token).ConfigureAwait(false); + return await _Sqlite.QueryAsync(queryAndParameters, token).ConfigureAwait(false); case DbTypeEnum.SqlServer: - return await _SqlServer.QueryAsync(query, token).ConfigureAwait(false); + return await _SqlServer.QueryAsync(queryAndParameters, token).ConfigureAwait(false); default: throw new ArgumentException("Unknown database type '" + _Settings.Type.ToString() + "'."); } diff --git a/src/DatabaseWrapper/DatabaseWrapper.xml b/src/DatabaseWrapper/DatabaseWrapper.xml index 6f6db74..210852b 100644 --- a/src/DatabaseWrapper/DatabaseWrapper.xml +++ b/src/DatabaseWrapper/DatabaseWrapper.xml @@ -329,18 +329,18 @@ The table you wish to TRUNCATE. Cancellation token. - + Execute a query. - Database query defined outside of the database client. + Database query defined outside of the database client. A DataTable containing the results. - + Execute a query. - Database query defined outside of the database client. + Database query defined outside of the database client. Cancellation token. A DataTable containing the results.