Skip to content

Commit

Permalink
Fixed Filter to handler enums correctly.
Browse files Browse the repository at this point in the history
Minor refactoring.
  • Loading branch information
NimaAra committed May 25, 2019
1 parent f40eefc commit 20971a1
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 157 deletions.
2 changes: 1 addition & 1 deletion Easy.Storage.Common/DBContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ internal DBContext(IDbConnection dbConnection, Dialect dialect, string tableName
/// <summary>
/// Gets a <see cref="Query{T}"/> instance for building queries.
/// </summary>
public Query<T> Query => Query<T>.Make(Table);
public Query<T> Query => Query<T>.For(Table);

/// <summary>
/// Gets the records represented by the <typeparamref name="T"/> from the storage.
Expand Down
4 changes: 3 additions & 1 deletion Easy.Storage.Common/Extensions/HelperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ public static class HelperExtensions
public static DynamicParameters ToDynamicParameters(
this IReadOnlyDictionary<string, object> parameters, object template = null)
{
var result = template is null ? new DynamicParameters() : new DynamicParameters(template);
if (template is null) { return new DynamicParameters(parameters); }

var result = new DynamicParameters(template);
foreach (var pair in parameters)
{
result.Add(pair.Key, pair.Value);
Expand Down
4 changes: 2 additions & 2 deletions Easy.Storage.Common/Filter/Filter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
/// <typeparam name="T">The type of the records to filter.</typeparam>
public sealed class Filter<T>
{
private readonly FilteredQuery _query;
private readonly FilterBuilder _query;

internal Filter(Table table) => _query = FilteredQuery.Make(table);
internal Filter(Table table) => _query = FilterBuilder.For(table);

/// <summary>
/// Gets the parameters generated by this instance.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
namespace Easy.Storage.Common.Filter
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Easy.Common.Extensions;
using Easy.Storage.Common.Extensions;

/// <summary>
/// Represents a filtered query.
/// Represents an abstraction for building <see cref="Filter"/>.
/// </summary>
internal sealed class FilteredQuery
internal sealed class FilterBuilder
{
private readonly Table _table;
private readonly StringBuilder _builder;
private uint _paramCounter;

private FilteredQuery(Table table)
private FilterBuilder(Table table)
{
_table = table;
_builder = new StringBuilder();
Parameters = new Dictionary<string, object>();
}

internal Dictionary<string, object> Parameters { get; }
internal static FilteredQuery Make(Table table) => new FilteredQuery(table);

internal static FilterBuilder For(Table table) => new FilterBuilder(table);

/// <summary>
/// Compiles and gets the <c>SQL</c> of the <see cref="FilteredQuery"/>.
/// Compiles and gets the <c>SQL</c> of the <see cref="FilterBuilder"/>.
/// </summary>
/// <param name="sqlToPrefix">
/// An optional <c>SQL</c> to prefix to the result.
Expand Down Expand Up @@ -72,14 +74,22 @@ internal void AddClause<T, TProperty>(Expression<Func<T, TProperty>> selector, O
AppendSQL(clause, selector, value, @operator.AsString());
}

internal void AddInClause<T, TProperty>(Expression<Func<T, TProperty>> selector, string clause, bool isIn, IEnumerable<TProperty> values)
internal void AddInClause<T, TProperty>(
Expression<Func<T, TProperty>> selector,
string clause,
bool isIn,
IEnumerable<TProperty> values)
{
var inClause = isIn ? Formatter.InClauseSeparator : Formatter.NotInClauseSeparator;
AppendSQL(clause, selector, values, inClause);
}

// ReSharper disable once InconsistentNaming
private void AppendSQL<T, TProperty, TValue>(string clause, Expression<Func<T, TProperty>> selector, TValue value, string operation)
private void AppendSQL<T, TProperty, TValue>(
string clause,
Expression<Func<T, TProperty>> selector,
TValue value,
string operation)
{
var propertyName = selector.GetPropertyName();
var paramName = AddAndReturnParameter(propertyName, value);
Expand All @@ -91,7 +101,26 @@ private void AppendSQL<T, TProperty, TValue>(string clause, Expression<Func<T, T
private string AddAndReturnParameter<TValue>(string propertyName, TValue value)
{
var paramName = string.Concat(propertyName, (++_paramCounter).ToString());
Parameters.Add(paramName, value);

object valAsObject = value;

switch (value)
{
case Enum _:
valAsObject = value.ToString();
break;
case IEnumerable sequence:
{
var type = typeof(TValue);
if (type.GenericTypeArguments.Length > 0 && type.GenericTypeArguments[0].IsEnum)
{
valAsObject = sequence.Cast<object>().Select(x => x.ToString());
}
break;
}
}

Parameters.Add(paramName, valAsObject);
return paramName;
}
}
Expand Down
2 changes: 1 addition & 1 deletion Easy.Storage.Common/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class Query<T>

private Query(Table table) => _table = table;

internal static Query<T> Make(Table table) => new Query<T>(table);
internal static Query<T> For(Table table) => new Query<T>(table);

/// <summary>
/// Gets an instance of the <see cref="Filter{T}"/> for creating query filters.
Expand Down
4 changes: 2 additions & 2 deletions Easy.Storage.Tests.Unit/Dialect/GenericSQLDialectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void When_getting_partial_update_query()
{
var dialect = GenericSQLDialect.Instance;
var table = Table.MakeOrGet<Person>(dialect, string.Empty);
var filter = Query<Person>.Make(table).Filter.And(x => x.Id, Operator.Is, 1);
var filter = Query<Person>.For(table).Filter.And(x => x.Id, Operator.Is, 1);

var person = new Person { Age = 10, Name = "Joe" };
dialect.GetPartialUpdateQuery(table, person, filter)
Expand All @@ -49,7 +49,7 @@ public void When_getting_partial_update_query()
([Id]=@Id1);");

var lonelyTable = Table.MakeOrGet<Lonely>(dialect, string.Empty);
var lonelyFilter = Query<Lonely>.Make(table).Filter.And(x => x.Id, Operator.Is, 1);
var lonelyFilter = Query<Lonely>.For(table).Filter.And(x => x.Id, Operator.Is, 1);
var lonely = new Lonely { Id = 1 };
dialect.GetPartialUpdateQuery(lonelyTable, lonely, lonelyFilter)
.ShouldBe(@"UPDATE [Lonely] SET
Expand Down
4 changes: 2 additions & 2 deletions Easy.Storage.Tests.Unit/Dialect/SQLServerDialectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void When_getting_partial_update_query()
{
var dialect = SQLServerDialect.Instance;
var table = Table.MakeOrGet<Person>(dialect, string.Empty);
var filter = Query<Person>.Make(table).Filter.And(x => x.Id, Operator.Is, 1);
var filter = Query<Person>.For(table).Filter.And(x => x.Id, Operator.Is, 1);

var person = new Person { Age = 10, Name = "Joe"};
dialect.GetPartialUpdateQuery(table, person, filter)
Expand All @@ -51,7 +51,7 @@ public void When_getting_partial_update_query()
([Id]=@Id1);");

var lonelyTable = Table.MakeOrGet<Lonely>(dialect, string.Empty);
var lonelyFilter = Query<Lonely>.Make(table).Filter.And(x => x.Id, Operator.Is, 1);
var lonelyFilter = Query<Lonely>.For(table).Filter.And(x => x.Id, Operator.Is, 1);
var lonely = new Lonely { Id = 1 };
dialect.GetPartialUpdateQuery(lonelyTable, lonely, lonelyFilter)
.ShouldBe(@"UPDATE [Lonely] SET
Expand Down
4 changes: 2 additions & 2 deletions Easy.Storage.Tests.Unit/Dialect/SQLiteDialectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void When_getting_partial_update_query()
{
var dialect = SQLiteDialect.Instance;
var table = Table.MakeOrGet<Person>(dialect, string.Empty);
var filter = Query<Person>.Make(table).Filter.And(x => x.Id, Operator.Is, 1);
var filter = Query<Person>.For(table).Filter.And(x => x.Id, Operator.Is, 1);

var person = new Person { Age = 10, Name = "Joe" };
dialect.GetPartialUpdateQuery(table, person, filter)
Expand All @@ -50,7 +50,7 @@ public void When_getting_partial_update_query()
([Id]=@Id1);");

var lonelyTable = Table.MakeOrGet<Lonely>(dialect, string.Empty);
var lonelyFilter = Query<Lonely>.Make(table).Filter.And(x => x.Id, Operator.Is, 1);
var lonelyFilter = Query<Lonely>.For(table).Filter.And(x => x.Id, Operator.Is, 1);
var lonely = new Lonely { Id = 1 };
dialect.GetPartialUpdateQuery(lonelyTable, lonely, lonelyFilter)
.ShouldBe(@"UPDATE [Lonely] SET
Expand Down
178 changes: 178 additions & 0 deletions Easy.Storage.Tests.Unit/Filter/FilterBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
namespace Easy.Storage.Tests.Unit.Filter
{
using Easy.Storage.Common;
using Easy.Storage.Common.Filter;
using Easy.Storage.Tests.Unit.Models;
using NUnit.Framework;
using Shouldly;

[TestFixture]
internal sealed class FilterBuilderTests
{
private static readonly Table _table =
Table.MakeOrGet<PersonWithType>(GenericSQLDialect.Instance, string.Empty);

[Test]
public void When_creating_a_builder()
{
var filter = FilterBuilder.For(_table);
filter.ShouldNotBeNull();
filter.Parameters.ShouldBeEmpty();

var sql = filter.Compile();
sql.ShouldBe(@"SELECT
[Person].[Type] AS 'Type',
[Person].[Id] AS 'Id',
[Person].[Name] AS 'Name',
[Person].[Age] AS 'Age'
FROM [Person]
WHERE
1 = 1;");
}

[Test]
public void When_adding_equality_clause()
{
var filter = FilterBuilder.For(_table);
filter.AddClause<PersonWithType, long>(p => p.Id, Operator.Is, 1, Formatter.AndClauseSeparator);
filter.Parameters.Count.ShouldBe(1);
filter.Parameters["Id1"].ShouldBe(1);

var sql = filter.Compile();
sql.ShouldBe(@"SELECT
[Person].[Type] AS 'Type',
[Person].[Id] AS 'Id',
[Person].[Name] AS 'Name',
[Person].[Age] AS 'Age'
FROM [Person]
WHERE
1 = 1
AND
([Id]=@Id1);");
}

[Test]
public void When_adding_in_clause()
{
var filter = FilterBuilder.For(_table);
filter.AddInClause<PersonWithType, string>(p => p.Name, Formatter.AndClauseSeparator, true, new [] { "Foo", "Bar" });
filter.Parameters.Count.ShouldBe(1);
filter.Parameters["Name1"].ShouldBe(new [] { "Foo", "Bar"});

var sql = filter.Compile();
sql.ShouldBe(@"SELECT
[Person].[Type] AS 'Type',
[Person].[Id] AS 'Id',
[Person].[Name] AS 'Name',
[Person].[Age] AS 'Age'
FROM [Person]
WHERE
1 = 1
AND
([Name] IN @Name1);");
}

[Test]
public void When_adding_not_in_clause()
{
var filter = FilterBuilder.For(_table);
filter.AddInClause<PersonWithType, string>(p => p.Name, Formatter.AndClauseSeparator, false, new[] { "Foo", "Bar" });
filter.Parameters.Count.ShouldBe(1);
filter.Parameters["Name1"].ShouldBe(new[] { "Foo", "Bar" });

var sql = filter.Compile();
sql.ShouldBe(@"SELECT
[Person].[Type] AS 'Type',
[Person].[Id] AS 'Id',
[Person].[Name] AS 'Name',
[Person].[Age] AS 'Age'
FROM [Person]
WHERE
1 = 1
AND
([Name] NOT IN @Name1);");
}

[Test]
public void When_adding_filter_to_a_delete_statement()
{
var filter = FilterBuilder.For(_table);
filter.AddClause<PersonWithType, string>(p => p.Name, Operator.Is, "Foo", Formatter.AndClauseSeparator);
filter.Parameters.Count.ShouldBe(1);
filter.Parameters["Name1"].ShouldBe("Foo");

var sql = filter.Compile(
Table.MakeOrGet<PersonWithType>(GenericSQLDialect.Instance, string.Empty).Delete);
sql.ShouldBe(@"DELETE FROM [Person]
WHERE
1 = 1
AND
([Name]=@Name1);");
}

[Test]
public void When_adding_filter_to_a_update_statement()
{
var filter = FilterBuilder.For(_table);
filter.AddClause<PersonWithType, string>(p => p.Name, Operator.Is, "Foo", Formatter.AndClauseSeparator);
filter.Parameters.Count.ShouldBe(1);
filter.Parameters["Name1"].ShouldBe("Foo");

var sql = filter.Compile(
Table.MakeOrGet<Person>(GenericSQLDialect.Instance, string.Empty).UpdateAll);
sql.ShouldBe(@"UPDATE [Person] SET
[Id] = @Id,
[Name] = @Name,
[Age] = @Age
WHERE
1 = 1
AND
([Name]=@Name1);");
}

[Test]
public void When_filter_has_int_as_parameter()
{
var filter = Query<PersonWithType>.For(_table)
.Filter.And(x => x.Id, Operator.Is, 1);

filter.Parameters["Id1"].ShouldBe(1);
}

[Test]
public void When_filter_has_string_as_parameter()
{
var filter = Query<PersonWithType>.For(_table)
.Filter.And(x => x.Name, Operator.Is, "Foo");

filter.Parameters["Name1"].ShouldBe("Foo");
}

[Test]
public void When_filter_has_enum_as_parameter()
{
var filter = Query<PersonWithType>.For(_table)
.Filter.And(x => x.Type, Operator.Is, SomeType.TypeB);

filter.Parameters["Type1"].ShouldBe("TypeB");
}

[Test]
public void When_filter_has_array_of_enums_as_parameter()
{
var filter = Query<PersonWithType>.For(_table)
.Filter.AndIn(x => x.Type, new[] { SomeType.TypeA, SomeType.TypeB });

filter.Parameters["Type1"].ShouldBe(new[] { "TypeA", "TypeB" });
}

[Test]
public void When_filter_has_array_of_numbers_as_parameter()
{
var filter = Query<PersonWithType>.For(_table)
.Filter.AndIn(x => x.Id, new long[] { 1, 2 });

filter.Parameters["Id1"].ShouldBe(new[] { 1, 2 });
}
}
}
Loading

0 comments on commit 20971a1

Please sign in to comment.