Updated the SqlEvaluator to work with {{ }} rather than @ for expression. Supporting updates also added.
This commit is contained in:
parent
d8b6eda93f
commit
d3dc6f3597
|
|
@ -10,6 +10,12 @@ namespace Elsa.Sql.MySql;
|
|||
/// <param name="connectionString"></param>
|
||||
public class MySqlClient(string connectionString) : BaseSqlClient(connectionString)
|
||||
{
|
||||
public override string ParameterMarker { get; set; } = "@";
|
||||
|
||||
public override string ParameterText { get; set; } = "";
|
||||
|
||||
public override bool IncrementParameter { get; set; } = false;
|
||||
|
||||
protected override DbConnection CreateConnection() => new MySqlConnection(_connectionString);
|
||||
|
||||
protected override DbCommand CreateCommand(string query, DbConnection connection) => new MySqlCommand(query, (MySqlConnection)connection);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ namespace Elsa.Sql.SqlServer;
|
|||
/// <param name="connectionString"></param>
|
||||
public class SqlServerClient(string connectionString) : BaseSqlClient(connectionString)
|
||||
{
|
||||
public override string ParameterText { get; set; } = "p";
|
||||
|
||||
protected override DbConnection CreateConnection() => new SqlConnection(_connectionString);
|
||||
|
||||
protected override DbCommand CreateCommand(string query, DbConnection connection) => new SqlCommand(query, (SqlConnection)connection);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ namespace Elsa.Sql.Sqlite;
|
|||
/// <param name="connectionString"></param>
|
||||
public class SqliteClient(string connectionString) : BaseSqlClient(connectionString)
|
||||
{
|
||||
public override string ParameterText { get; set; } = "p";
|
||||
|
||||
protected override DbConnection CreateConnection() => new SqliteConnection(_connectionString);
|
||||
|
||||
protected override DbCommand CreateCommand(string query, DbConnection connection) => new SqliteCommand(query, (SqliteConnection)connection);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Text;
|
||||
using Elsa.Sql.Models;
|
||||
|
||||
namespace Elsa.Sql.Client;
|
||||
|
|
@ -11,6 +12,24 @@ public abstract class BaseSqlClient : ISqlClient
|
|||
/// </summary>
|
||||
protected readonly string _connectionString;
|
||||
|
||||
/// <summary>
|
||||
/// The marker used when injecting parameters into a query.
|
||||
/// Default: "@"
|
||||
/// </summary>
|
||||
public virtual string ParameterMarker { get; set; } = "@";
|
||||
|
||||
/// <summary>
|
||||
/// The text following the <c>ParameterMarker</c>when injecting parameters into a query
|
||||
/// Default: <see cref="string.Empty"/>
|
||||
/// </summary>
|
||||
public virtual string ParameterText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to add a counter to the end of the parameter string
|
||||
/// Default: false
|
||||
/// </summary>
|
||||
public virtual bool IncrementParameter { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Create a connection using the client specific connection.
|
||||
/// </summary>
|
||||
|
|
@ -38,8 +57,9 @@ public abstract class BaseSqlClient : ISqlClient
|
|||
{
|
||||
using var connection = CreateConnection();
|
||||
connection.Open();
|
||||
var command = CreateCommand(evaluatedQuery.Query, connection);
|
||||
AddParameters(command, evaluatedQuery.Parameters);
|
||||
var query = ReplaceQueryParameters(evaluatedQuery);
|
||||
var command = CreateCommand(query, connection);
|
||||
AddCommandParameters(command, evaluatedQuery.Parameters);
|
||||
|
||||
var result = await command.ExecuteNonQueryAsync();
|
||||
return result;
|
||||
|
|
@ -52,8 +72,9 @@ public abstract class BaseSqlClient : ISqlClient
|
|||
{
|
||||
using var connection = CreateConnection();
|
||||
connection.Open();
|
||||
var command = CreateCommand(evaluatedQuery.Query, connection);
|
||||
AddParameters(command, evaluatedQuery.Parameters);
|
||||
var query = ReplaceQueryParameters(evaluatedQuery);
|
||||
var command = CreateCommand(query, connection);
|
||||
AddCommandParameters(command, evaluatedQuery.Parameters);
|
||||
|
||||
var result = await command.ExecuteScalarAsync();
|
||||
return result;
|
||||
|
|
@ -66,20 +87,42 @@ public abstract class BaseSqlClient : ISqlClient
|
|||
{
|
||||
using var connection = CreateConnection();
|
||||
connection.Open();
|
||||
var command = CreateCommand(evaluatedQuery.Query, connection);
|
||||
AddParameters(command, evaluatedQuery.Parameters);
|
||||
var query = ReplaceQueryParameters(evaluatedQuery);
|
||||
var command = CreateCommand(query, connection);
|
||||
AddCommandParameters(command, evaluatedQuery.Parameters);
|
||||
|
||||
using var reader = await command.ExecuteReaderAsync();
|
||||
return await Task.FromResult(ReadAsDataSet(reader));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the evaluated parameters with client specific parameters.
|
||||
/// </summary>
|
||||
/// <param name="evaluatedQuery">Query to replace parameters for.</param>
|
||||
/// <returns></returns>
|
||||
private string ReplaceQueryParameters(EvaluatedQuery evaluatedQuery)
|
||||
{
|
||||
var count = 1;
|
||||
var clientUpdatedParams = new Dictionary<string, object>();
|
||||
var queryBuilder = new StringBuilder(evaluatedQuery.Query);
|
||||
foreach (var param in evaluatedQuery.Parameters)
|
||||
{
|
||||
var counterValue = IncrementParameter ? count++.ToString() : string.Empty;
|
||||
var newKey = $"{ParameterMarker}{ParameterText}{counterValue}";
|
||||
queryBuilder.Replace(param.Key, newKey);
|
||||
clientUpdatedParams[newKey] = param.Value;
|
||||
}
|
||||
evaluatedQuery.Parameters = clientUpdatedParams;
|
||||
return queryBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inject parameters into the query to prevent SQL injection.
|
||||
/// </summary>
|
||||
/// <param name="command">Command to add the parameters to</param>
|
||||
/// <param name="parameters">Parameters to add</param>
|
||||
/// <returns></returns>
|
||||
private DbCommand AddParameters(DbCommand command, Dictionary<string, object?> parameters)
|
||||
private DbCommand AddCommandParameters(DbCommand command, Dictionary<string, object?> parameters)
|
||||
{
|
||||
// Add parameters dynamically
|
||||
foreach (var param in parameters)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\Elsa.Features\Elsa.Features.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Sql.Models\Elsa.Sql.Models.csproj" />
|
||||
<ProjectReference Include="..\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@
|
|||
/// <summary>
|
||||
/// Query with parameterized values
|
||||
/// </summary>
|
||||
public string Query { get; }
|
||||
public string Query { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Parameters to inject into the query at execution
|
||||
/// </summary>
|
||||
public Dictionary<string, object?> Parameters { get; } = new Dictionary<string, object?>();
|
||||
public Dictionary<string, object?> Parameters { get; set; } = new Dictionary<string, object?>();
|
||||
|
||||
/// <summary>
|
||||
/// An evaluated query response.
|
||||
|
|
|
|||
|
|
@ -21,39 +21,43 @@ public class SqlEvaluator() : ISqlEvaluator
|
|||
ExpressionEvaluatorOptions options,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!expression.Contains("@")) return new EvaluatedQuery(expression);
|
||||
if (!expression.Contains("{{")) return new EvaluatedQuery(expression);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var parameters = new Dictionary<string, object?>();
|
||||
int start = 0;
|
||||
var parameters = new Dictionary<string, object?>();
|
||||
int paramIndex = 0;
|
||||
|
||||
while (start < expression.Length)
|
||||
{
|
||||
int atIndex = expression.IndexOf('@', start);
|
||||
if (atIndex == -1)
|
||||
int openIndex = expression.IndexOf("{{", start);
|
||||
if (openIndex == -1)
|
||||
{
|
||||
sb.Append(expression.Substring(start));
|
||||
sb.Append(expression.AsSpan(start));
|
||||
break;
|
||||
}
|
||||
|
||||
sb.Append(expression.Substring(start, atIndex - start));
|
||||
// Append everything before {{
|
||||
sb.Append(expression.AsSpan(start, openIndex - start));
|
||||
|
||||
int endIndex = atIndex + 1;
|
||||
while (endIndex < expression.Length && (char.IsLetterOrDigit((char)expression[endIndex]) || expression[endIndex] == '.' || expression[endIndex] == '_'))
|
||||
{
|
||||
endIndex++;
|
||||
}
|
||||
// Find the closing }}
|
||||
int closeIndex = expression.IndexOf("}}", openIndex + 2);
|
||||
if (closeIndex == -1) throw new FormatException("Unmatched '{{' found in SQL expression.");
|
||||
|
||||
string key = expression.Substring(atIndex + 1, endIndex - atIndex - 1);
|
||||
// Extract key
|
||||
string key = expression.Substring(openIndex + 2, closeIndex - openIndex - 2).Trim();
|
||||
if (string.IsNullOrEmpty(key)) throw new FormatException("Empty placeholder '{{}}' is not allowed.");
|
||||
|
||||
// Resolve value
|
||||
object? value = ResolveValue(key, context);
|
||||
if (value is null) throw new NullReferenceException($"No value found for '{key}'.");
|
||||
if (value is null) throw new NullReferenceException($"No value found for '{{{{{key}}}}}'.");
|
||||
|
||||
string paramName = $"@param{paramIndex++}";
|
||||
// Replace with parameterized name
|
||||
string paramName = $"{{{{p{paramIndex++}}}}}";
|
||||
parameters[paramName] = value;
|
||||
|
||||
sb.Append(paramName);
|
||||
start = endIndex;
|
||||
start = closeIndex + 2;
|
||||
}
|
||||
|
||||
return new EvaluatedQuery(sb.ToString(), parameters);
|
||||
|
|
|
|||
Loading…
Reference in a new issue