Skip to content

Commit

Permalink
Introduced inline parameters for raw queries to provide more flexibil…
Browse files Browse the repository at this point in the history
…ity around query generation
  • Loading branch information
YuriyIvon committed Jul 30, 2023
1 parent 2e1d131 commit 7b20d6e
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 41 deletions.
20 changes: 20 additions & 0 deletions src/DatabaseBenchmark/Databases/Common/InlineParameterFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using DatabaseBenchmark.Common;

namespace DatabaseBenchmark.Databases.Common
{
public static class InlineParameterFormatter
{
public static string Format(string format, object value) =>
value switch
{
null => null,
bool b => b.ToString().ToLower(),
int n => n.ToString(format),
long n => n.ToString(format),
double n => n.ToString(format),
DateTime dt => dt.ToString(format ?? "s"),
Guid g => g.ToString(format),
_ => value.ToString()
};
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using DatabaseBenchmark.Core.Interfaces;
using DatabaseBenchmark.Databases.Common;
using DatabaseBenchmark.Databases.Elasticsearch.Interfaces;
using DatabaseBenchmark.Model;
using Elasticsearch.Net;
using Nest;
using System.Reflection;
using System.Text;
using System.Text.Json;
using RawQuery = DatabaseBenchmark.Model.RawQuery;

namespace DatabaseBenchmark.Databases.Elasticsearch
Expand Down Expand Up @@ -58,12 +61,12 @@ private string ApplyParameters(string queryText)

if (rawValue is IEnumerable<object> rawCollection)
{
var aliases = rawCollection.Select(v => FormatValue(v)).ToArray();
var aliases = rawCollection.Select(v => FormatParameter(parameter, v)).ToArray();
parameterString = string.Join(", ", aliases);
}
else
{
parameterString = FormatValue(rawValue);
parameterString = FormatParameter(parameter, rawValue);
}

queryText = queryText.Replace($"${{{parameter.Name}}}", parameterString);
Expand All @@ -72,19 +75,6 @@ private string ApplyParameters(string queryText)
return queryText;
}

private static string FormatValue(object value)
{
if (value != null)
{
var stringValue = value.ToString();

return (value is bool || value is int || value is long || value is double)
? stringValue : $"\"{stringValue.Replace("\"", "\\\"")}\"";
}

return "null";
}

private static void CopyPublicProperties<T>(T source, T destination)
{
foreach(var property in typeof(T).GetProperties(
Expand All @@ -97,5 +87,10 @@ private static void CopyPublicProperties<T>(T source, T destination)
}
}
}

private static string FormatParameter(RawQueryParameter parameter, object value) =>
parameter.Inline
? InlineParameterFormatter.Format(parameter.InlineFormat, value)
: JsonSerializer.Serialize(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public ElasticsearchRawQueryExecutorFactory(
Container.RegisterDecorator<IDistinctValuesProvider, CachedDistinctValuesProvider>(Lifestyle);

Container.Register<ElasticClient>(createClient, Lifestyle);
//TODO: Find a better way to instantiate the default serializer
Container.Register<IElasticsearchSerializer>(() => Container.GetInstance<ElasticClient>().RequestResponseSerializer, Lifestyle);
Container.Register<IDistinctValuesProvider, ElasticsearchDistinctValuesProvider>(Lifestyle);
Container.Register<IRandomValueProvider, RandomValueProvider>(Lifestyle);
Expand Down
22 changes: 8 additions & 14 deletions src/DatabaseBenchmark/Databases/MongoDb/MongoDbRawQueryBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using DatabaseBenchmark.Core.Interfaces;
using DatabaseBenchmark.Databases.Common;
using DatabaseBenchmark.Databases.MongoDb.Interfaces;
using DatabaseBenchmark.Model;
using MongoDB.Bson;
using System.Text.Json;

namespace DatabaseBenchmark.Databases.MongoDb
{
Expand Down Expand Up @@ -47,12 +49,12 @@ private string ApplyParameters(string queryText)

if (rawValue is IEnumerable<object> rawCollection)
{
var aliases = rawCollection.Select(v => FormatValue(v)).ToArray();
var aliases = rawCollection.Select(v => FormatParameter(parameter, v)).ToArray();
parameterString = string.Join(", ", aliases);
}
else
{
parameterString = FormatValue(rawValue);
parameterString = FormatParameter(parameter, rawValue);
}

queryText = queryText.Replace($"${{{parameter.Name}}}", parameterString);
Expand All @@ -61,17 +63,9 @@ private string ApplyParameters(string queryText)
return queryText;
}

private static string FormatValue(object value)
{
if (value != null)
{
var stringValue = value.ToString();

return (value is bool || value is int || value is long || value is double)
? stringValue : $"\"{stringValue.Replace("\"", "\\\"")}\"";
}

return "null";
}
private static string FormatParameter(RawQueryParameter parameter, object value) =>
parameter.Inline
? InlineParameterFormatter.Format(parameter.InlineFormat, value)
: JsonSerializer.Serialize(value);
}
}
10 changes: 8 additions & 2 deletions src/DatabaseBenchmark/Databases/Sql/SqlRawQueryBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DatabaseBenchmark.Core.Interfaces;
using DatabaseBenchmark.Databases.Common;
using DatabaseBenchmark.Databases.Sql.Interfaces;
using DatabaseBenchmark.Model;

Expand Down Expand Up @@ -48,18 +49,23 @@ private string ApplyParameters(string queryText)

if (rawValue is IEnumerable<object> rawCollection)
{
var aliases = rawCollection.Select(v => _parametersBuilder.Append(v, parameter.Type)).ToArray();
var aliases = rawCollection.Select(v => BuildParameter(parameter, v)).ToArray();
parameterString = string.Join(", ", aliases);
}
else
{
parameterString = _parametersBuilder.Append(rawValue, parameter.Type);
parameterString = BuildParameter(parameter, rawValue);
}

queryText = queryText.Replace($"${{{parameter.Name}}}", parameterString);
}

return queryText;
}

private string BuildParameter(RawQueryParameter parameter, object value) =>
parameter.Inline
? InlineParameterFormatter.Format(parameter.InlineFormat, value)
: _parametersBuilder.Append(value, parameter.Type);
}
}
4 changes: 4 additions & 0 deletions src/DatabaseBenchmark/Model/RawQueryParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ public class RawQueryParameter
public bool RandomizeValue { get; set; } = false;

public ValueRandomizationRule ValueRandomizationRule { get; set; } = new ValueRandomizationRule();

public bool Inline { get; set; } = false;

public string InlineFormat { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using DatabaseBenchmark.Databases.Elasticsearch;
using DatabaseBenchmark.Tests.Utils;
using Nest;
using System.IO;
using Xunit;

namespace DatabaseBenchmark.Tests.Databases
{
public class ElasticsearchRawQueryBuilderTests
{
[Fact]
public void BuildParameterizedQuery()
{
var query = SampleInputs.RawElasticsearchQuery;
//TODO: Find a better way to instantiate the default serializer
var serializer = new ElasticClient().RequestResponseSerializer;
var builder = new ElasticsearchRawQueryBuilder(query, serializer, null);

var searchRequest = builder.Build();

using var stream = new MemoryStream();
serializer.Serialize(searchRequest, stream);
var queryText = stream.ReadAsString();

Assert.Equal(@"{""query"":{""bool"":{""must"":[{""term"":{""category"":{""value"":""ABC""}}},{""range"":{""createdDate"":{""gte"":""2020-01-02T03:04:05""}}},{""range"":{""price"":{""lte"":25.5}}},{""term"":{""available"":{""value"":true}}}]}}}", queryText);
}

[Fact]
public void BuildInlineParameterizedQuery()
{
var query = SampleInputs.RawElasticsearchInlineQuery;
//TODO: Find a better way to instantiate the default serializer
var serializer = new ElasticClient().RequestResponseSerializer;
var builder = new ElasticsearchRawQueryBuilder(query, serializer, null);

var searchRequest = builder.Build();

using var stream = new MemoryStream();
serializer.Serialize(searchRequest, stream);
var queryText = stream.ReadAsString();

Assert.Equal(@"{""query"":{""bool"":{""must"":[{""term"":{""category"":{""value"":""ABC""}}},{""range"":{""createdDate"":{""gte"":""2020-01-02T03:04:05""}}},{""range"":{""price"":{""lte"":25.5}}},{""term"":{""available"":{""value"":true}}}]}}}", queryText);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using DatabaseBenchmark.Databases.MongoDb;
using DatabaseBenchmark.Tests.Utils;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Bson;
using Xunit;

namespace DatabaseBenchmark.Tests.Databases
{
public class MongoDbRawQueryBuilderTests
{
[Fact]
public void BuildParameterizedQuery()
{
var query = SampleInputs.RawMongoDbQuery;
var builder = new MongoDbRawQueryBuilder(query, null);

var queryParts = builder.Build();

var queryText = new BsonArray(queryParts).ToString();
Assert.Equal(@"[{ ""$match"" : { ""$and"" : [{ ""category"" : ""ABC"" }, { ""createdDate"" : { ""$gte"" : ""2020-01-02T03:04:05"" } }, { ""price"" : { ""$lte"" : 25.5 } }, { ""available"" : true }] } }]", queryText);
}

[Fact]
public void BuildInlineParameterizedQuery()
{
var query = SampleInputs.RawMongoDbInlineQuery;
var builder = new MongoDbRawQueryBuilder(query, null);

var queryParts = builder.Build();

var queryText = new BsonArray(queryParts).ToString();
Assert.Equal(@"[{ ""$match"" : { ""$and"" : [{ ""category"" : ""ABC"" }, { ""createdDate"" : { ""$gte"" : ""2020-01-02T03:04:05"" } }, { ""price"" : { ""$lte"" : 25.5 } }, { ""available"" : true }] } }]", queryText);
}
}
}
21 changes: 19 additions & 2 deletions tests/DatabaseBenchmark.Tests/Databases/SqlRawQueryBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using DatabaseBenchmark.Databases.Sql;
using DatabaseBenchmark.Model;
using DatabaseBenchmark.Tests.Utils;
using System;
using Xunit;

namespace DatabaseBenchmark.Tests.Databases
Expand All @@ -16,14 +17,30 @@ public void BuildParameterizedQuery()

var queryText = builder.Build();

Assert.Equal("SELECT * FROM Sample WHERE Category = @p0", queryText);
Assert.Equal("SELECT * FROM Sample WHERE Category = @p0 AND CreatedDate >= @p1 AND Price <= @p2 AND Available = @p3", queryText);

var reference = new SqlQueryParameter[]
{
new ('@', "p0", "ABC", ColumnType.String)
new ('@', "p0", "ABC", ColumnType.String),
new ('@', "p1", new DateTime(2020, 1, 2, 3, 4, 5), ColumnType.DateTime),
new ('@', "p2", 25.5, ColumnType.Double),
new ('@', "p3", true, ColumnType.Boolean)
};

Assert.Equal(reference, parametersBuilder.Parameters);
}

[Fact]
public void BuildInlineParameterizedQuery()
{
var query = SampleInputs.RawSqlInlineQuery;
var parametersBuilder = new SqlParametersBuilder();
var builder = new SqlRawQueryBuilder(query, parametersBuilder, null);

var queryText = builder.Build();

Assert.Equal("SELECT * FROM Sample WHERE Category = 'ABC' AND CreatedDate >= '2020-01-02T03:04:05' AND Price <= 25.5", queryText);
Assert.Empty(parametersBuilder.Parameters);
}
}
}
Loading

0 comments on commit 7b20d6e

Please sign in to comment.