Skip to content

Commit 6412991

Browse files
committed
HavingClause first test pass
1 parent 8e6d19f commit 6412991

9 files changed

Lines changed: 334 additions & 18 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ apply_config.bat
2020
NDependOut
2121
*.dotCover
2222
*_mm_cache.bin
23+
*.idc
24+
Simple.Data.sln.DotSettings.user

Simple.Data.InMemoryTest/InMemoryTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,15 @@ public void SelectWithMaxShouldReturnMax()
104104
Assert.AreEqual(50, records[1].MaxAge);
105105
}
106106

107+
[Test]
108+
public void SelectWithHavingSumShouldReturnOnlyMatchingRows()
109+
{
110+
var db = CreateAggregateTestDb();
111+
var records = db.Test.All().Select(db.Test.Name).Having(db.Test.Age.Sum() > 50).ToList();
112+
Assert.AreEqual(1, records.Count);
113+
Assert.AreEqual("Bob", records[0].Name);
114+
}
115+
107116
private static dynamic CreateAggregateTestDb()
108117
{
109118
Database.UseMockAdapter(new InMemoryAdapter());
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Simple.Data
2+
{
3+
public class AllColumnsSpecialReference : SpecialReference
4+
{
5+
public AllColumnsSpecialReference() : base("*")
6+
{
7+
}
8+
}
9+
}

Simple.Data/QueryPolyfills/DictionaryQueryRunner.cs

Lines changed: 106 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66

77
internal class DictionaryQueryRunner
88
{
9+
const string AutoColumnPrefix = "___having___";
910
private static readonly
1011
Dictionary<Type, Func<SimpleQueryClauseBase, IEnumerable<IDictionary<string, object>>, IEnumerable<IDictionary<string, object>>>> ClauseHandlers =
1112
new Dictionary<Type, Func<SimpleQueryClauseBase, IEnumerable<IDictionary<string, object>>, IEnumerable<IDictionary<string, object>>>>
1213
{
1314
{ typeof(DistinctClause), (c,d) => d.Distinct(new DictionaryEqualityComparer()) },
1415
{ typeof(SkipClause), (c,d) => d.Skip(((SkipClause)c).Count) },
1516
{ typeof(TakeClause), (c,d) => d.Take(((TakeClause)c).Count) },
16-
{ typeof(SelectClause), (c,d) => new SelectClauseHandler((SelectClause)c).Run(d) },
17-
{ typeof(WhereClause), (c,d) => new WhereClauseHandler((WhereClause)c).Run(d) },
1817
{ typeof(OrderByClause), (c, d) => new OrderByClauseHandler((OrderByClause)c).Run(d) }
1918
};
2019

@@ -37,15 +36,13 @@ public DictionaryQueryRunner(IEnumerable<IDictionary<string, object>> source, pa
3736

3837
public IEnumerable<IDictionary<string, object>> Run()
3938
{
40-
IEnumerable<IDictionary<string, object>> source;
39+
var source = RunWhereClauses(_source);
40+
4141
if (_withCountClause != null)
4242
{
43-
source = _source.ToList();
44-
_withCountClause.SetCount(source.Count());
45-
}
46-
else
47-
{
48-
source = _source;
43+
var list = source.ToList();
44+
_withCountClause.SetCount(list.Count);
45+
source = list;
4946
}
5047

5148
foreach (var clause in _clauses)
@@ -57,24 +54,116 @@ public IEnumerable<IDictionary<string, object>> Run()
5754
}
5855
}
5956

57+
source = RunHavingClauses(source);
58+
source = RunSelectClauses(source);
59+
return source;
60+
}
61+
62+
private IEnumerable<IDictionary<string, object>> RunWhereClauses(IEnumerable<IDictionary<string, object>> source)
63+
{
64+
foreach (var whereClause in _clauses.OfType<WhereClause>())
65+
{
66+
source = new WhereClauseHandler(whereClause).Run(source);
67+
}
6068
return source;
6169
}
70+
71+
private IEnumerable<IDictionary<string, object>> RunSelectClauses(IEnumerable<IDictionary<string, object>> source)
72+
{
73+
foreach (var selectClause in _clauses.OfType<SelectClause>())
74+
{
75+
source = new SelectClauseHandler(selectClause).Run(source);
76+
}
77+
return source;
78+
}
79+
80+
private IEnumerable<IDictionary<string, object>> RunHavingClauses(IEnumerable<IDictionary<string, object>> source)
81+
{
82+
var havingClauses = _clauses.OfType<HavingClause>().ToList();
83+
if (havingClauses.Count == 0) return source;
84+
85+
var selectClause = _clauses.OfType<SelectClause>().FirstOrDefault();
86+
87+
List<SimpleReference> selectReferences;
88+
89+
if (selectClause != null)
90+
{
91+
selectReferences = selectClause.Columns.ToList();
92+
}
93+
else
94+
{
95+
selectReferences = new List<SimpleReference> { new AllColumnsSpecialReference() };
96+
}
97+
98+
foreach (var clause in havingClauses)
99+
{
100+
var criteria = HavingToWhere(clause.Criteria, selectReferences);
101+
source = new SelectClauseHandler(new SelectClause(selectReferences)).Run(source).ToList();
102+
source = new WhereClauseHandler(new WhereClause(criteria)).Run(source);
103+
source = source.Select(d => d.Where(kvp => !kvp.Key.StartsWith(AutoColumnPrefix)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
104+
}
105+
106+
return source;
107+
}
108+
109+
private SimpleExpression HavingToWhere(SimpleExpression criteria, List<SimpleReference> selectReferences)
110+
{
111+
if (criteria.LeftOperand is SimpleExpression)
112+
{
113+
return new SimpleExpression(HavingToWhere((SimpleExpression) criteria.LeftOperand, selectReferences),
114+
HavingToWhere((SimpleExpression) criteria.RightOperand, selectReferences),
115+
criteria.Type);
116+
}
117+
118+
object leftOperand = ReplaceFunctionOperand(criteria.LeftOperand, selectReferences);
119+
object rightOperand = ReplaceFunctionOperand(criteria.RightOperand, selectReferences);
120+
121+
return new SimpleExpression(leftOperand, rightOperand, criteria.Type);
122+
}
123+
124+
private static object ReplaceFunctionOperand(object operand, List<SimpleReference> selectReferences)
125+
{
126+
var leftFunction = operand as FunctionReference;
127+
if (!leftFunction.IsNull())
128+
{
129+
var alias = AutoColumnPrefix + Guid.NewGuid().ToString("N");
130+
selectReferences.Add(leftFunction.As(alias));
131+
return new ObjectReference(alias);
132+
}
133+
return operand;
134+
}
135+
136+
private static bool AreEquivalentReferences(FunctionReference reference1, FunctionReference reference2)
137+
{
138+
if (reference1.IsNull()) return reference2.IsNull();
139+
if (reference2.IsNull()) return false;
140+
return reference1.Name == reference2.Name && reference1.Argument == reference2.Argument;
141+
}
62142
}
63143

64-
internal class OrderByClauseHandler
144+
internal class GenericEqualityComparer<T> : IEqualityComparer<T>
65145
{
66-
private readonly OrderByClause _orderByClause;
146+
private readonly Func<T, T, bool> _equals;
147+
private readonly Func<T, int> _getHashCode;
148+
149+
public GenericEqualityComparer(Func<T, T, bool> @equals) : this(@equals, _ => 1)
150+
{
151+
}
152+
153+
public GenericEqualityComparer(Func<T, T, bool> @equals, Func<T, int> getHashCode)
154+
{
155+
_equals = @equals;
156+
_getHashCode = getHashCode;
157+
}
67158

68-
public OrderByClauseHandler(OrderByClause orderByClause)
159+
public bool Equals(T x, T y)
69160
{
70-
_orderByClause = orderByClause;
161+
return _equals(x, y);
71162
}
72163

73-
public IEnumerable<IDictionary<string, object>> Run(IEnumerable<IDictionary<string, object>> source)
164+
public int GetHashCode(T obj)
74165
{
75-
return _orderByClause.Direction == OrderByDirection.Ascending
76-
? source.OrderBy(d => d[_orderByClause.Reference.GetName()])
77-
: source.OrderByDescending(d => d[_orderByClause.Reference.GetName()]);
166+
return _getHashCode(obj);
78167
}
79168
}
80169
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text.RegularExpressions;
6+
7+
namespace Simple.Data.QueryPolyfills
8+
{
9+
internal class HavingClauseHandler
10+
{
11+
private readonly Dictionary<SimpleExpressionType, Func<SimpleExpression, Func<IDictionary<string, object>, bool>>> _expressionFormatters;
12+
13+
private readonly WhereClause _whereClause;
14+
15+
public HavingClauseHandler(WhereClause whereClause)
16+
{
17+
_whereClause = whereClause;
18+
_expressionFormatters = new Dictionary<SimpleExpressionType, Func<SimpleExpression, Func<IDictionary<string,object>, bool>>>
19+
{
20+
{SimpleExpressionType.And, LogicalExpressionToWhereClause},
21+
{SimpleExpressionType.Or, LogicalExpressionToWhereClause},
22+
{SimpleExpressionType.Equal, EqualExpressionToWhereClause},
23+
{SimpleExpressionType.NotEqual, NotEqualExpressionToWhereClause},
24+
{SimpleExpressionType.Function, FunctionExpressionToWhereClause},
25+
{SimpleExpressionType.GreaterThan, GreaterThanToWhereClause},
26+
{SimpleExpressionType.LessThan, LessThanToWhereClause},
27+
{SimpleExpressionType.GreaterThanOrEqual, GreaterThanOrEqualToWhereClause},
28+
{SimpleExpressionType.LessThanOrEqual, LessThanOrEqualToWhereClause},
29+
{SimpleExpressionType.Empty, expr => _ => true },
30+
};
31+
}
32+
33+
private Func<IDictionary<string, object>, bool> FunctionExpressionToWhereClause(SimpleExpression arg)
34+
{
35+
var key = GetKeyFromLeftOperand(arg);
36+
var function = arg.RightOperand as SimpleFunction;
37+
if (ReferenceEquals(function, null)) throw new InvalidOperationException("Expression type of function but no function supplied.");
38+
if (function.Name.Equals("like", StringComparison.OrdinalIgnoreCase))
39+
{
40+
var pattern = function.Args[0].ToString();
41+
if (pattern.Contains("%") || pattern.Contains("_")) // SQL Server LIKE
42+
{
43+
pattern = pattern.Replace("%", ".*").Replace('_', '.');
44+
}
45+
46+
var regex = new Regex("^" + pattern + "$", RegexOptions.Multiline | RegexOptions.IgnoreCase);
47+
48+
return d => d.ContainsKey(key) && (!ReferenceEquals(d[key], null)) && regex.IsMatch(d[key].ToString());
49+
}
50+
51+
throw new NotSupportedException("Expression Function not supported.");
52+
}
53+
54+
private Func<IDictionary<string, object>, bool> GreaterThanToWhereClause(SimpleExpression arg)
55+
{
56+
var key = GetKeyFromLeftOperand(arg);
57+
58+
return d => d.ContainsKey(key) && !ReferenceEquals(d[key], null) && ((IComparable)d[key]).CompareTo(arg.RightOperand) > 0;
59+
}
60+
61+
private Func<IDictionary<string, object>, bool> LessThanToWhereClause(SimpleExpression arg)
62+
{
63+
var key = GetKeyFromLeftOperand(arg);
64+
65+
return d => d.ContainsKey(key) && !ReferenceEquals(d[key], null) && ((IComparable)d[key]).CompareTo(arg.RightOperand) < 0;
66+
}
67+
68+
private Func<IDictionary<string, object>, bool> GreaterThanOrEqualToWhereClause(SimpleExpression arg)
69+
{
70+
var key = GetKeyFromLeftOperand(arg);
71+
72+
return d => d.ContainsKey(key) && !ReferenceEquals(d[key], null) && ((IComparable)d[key]).CompareTo(arg.RightOperand) >= 0;
73+
}
74+
75+
private Func<IDictionary<string, object>, bool> LessThanOrEqualToWhereClause(SimpleExpression arg)
76+
{
77+
var key = GetKeyFromLeftOperand(arg);
78+
79+
return d => d.ContainsKey(key) && !ReferenceEquals(d[key], null) && ((IComparable)d[key]).CompareTo(arg.RightOperand) <= 0;
80+
}
81+
82+
private Func<IDictionary<string, object>, bool> NotEqualExpressionToWhereClause(SimpleExpression arg)
83+
{
84+
var key = GetKeyFromLeftOperand(arg);
85+
86+
if (ReferenceEquals(arg.RightOperand, null))
87+
{
88+
return d => d.ContainsKey(key) && d[key] != null;
89+
}
90+
91+
if (arg.RightOperand.GetType().IsArray)
92+
{
93+
return
94+
d =>
95+
d.ContainsKey(key) &&
96+
!((IEnumerable)d[key]).Cast<object>().SequenceEqual(((IEnumerable)arg.RightOperand).Cast<object>());
97+
}
98+
99+
return d => d.ContainsKey(key) && !Equals(d[key], arg.RightOperand);
100+
}
101+
102+
private Func<IDictionary<string, object>, bool> EqualExpressionToWhereClause(SimpleExpression arg)
103+
{
104+
var key = GetKeyFromLeftOperand(arg);
105+
106+
if (ReferenceEquals(arg.RightOperand, null))
107+
{
108+
return d => (!d.ContainsKey(key)) || d[key] == null;
109+
}
110+
111+
if (arg.RightOperand.GetType().IsArray)
112+
{
113+
return
114+
d =>
115+
d.ContainsKey(key) &&
116+
((IEnumerable) d[key]).Cast<object>().SequenceEqual(((IEnumerable) arg.RightOperand).Cast<object>());
117+
}
118+
119+
return d => d.ContainsKey(key) && Equals(d[key], arg.RightOperand);
120+
}
121+
122+
private static string GetKeyFromLeftOperand(SimpleExpression arg)
123+
{
124+
var reference = arg.LeftOperand as ObjectReference;
125+
126+
if (reference.IsNull()) throw new NotSupportedException("Only ObjectReference types are supported.");
127+
128+
var key = reference.GetName();
129+
return key;
130+
}
131+
132+
private Func<IDictionary<string,object>, bool> Format(SimpleExpression expression)
133+
{
134+
Func<SimpleExpression, Func<IDictionary<string,object>,bool>> formatter;
135+
136+
if (_expressionFormatters.TryGetValue(expression.Type, out formatter))
137+
{
138+
return formatter(expression);
139+
}
140+
141+
return _ => true;
142+
}
143+
144+
private Func<IDictionary<string, object>, bool> LogicalExpressionToWhereClause(SimpleExpression arg)
145+
{
146+
var left = Format((SimpleExpression) arg.LeftOperand);
147+
var right = Format((SimpleExpression) arg.RightOperand);
148+
149+
if (arg.Type == SimpleExpressionType.Or)
150+
{
151+
return d => (left(d) || right(d));
152+
}
153+
return d => (left(d) && right(d));
154+
}
155+
156+
public IEnumerable<IDictionary<string, object>> Run(IEnumerable<IDictionary<string, object>> source)
157+
{
158+
var predicate = Format(_whereClause.Criteria);
159+
return source.Where(predicate);
160+
}
161+
}
162+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
namespace Simple.Data.QueryPolyfills
5+
{
6+
internal class OrderByClauseHandler
7+
{
8+
private readonly OrderByClause _orderByClause;
9+
10+
public OrderByClauseHandler(OrderByClause orderByClause)
11+
{
12+
_orderByClause = orderByClause;
13+
}
14+
15+
public IEnumerable<IDictionary<string, object>> Run(IEnumerable<IDictionary<string, object>> source)
16+
{
17+
return _orderByClause.Direction == OrderByDirection.Ascending
18+
? source.OrderBy(d => d[_orderByClause.Reference.GetName()])
19+
: source.OrderByDescending(d => d[_orderByClause.Reference.GetName()]);
20+
}
21+
}
22+
}

Simple.Data/QueryPolyfills/SelectClauseHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class SelectClauseHandler
1010
private readonly IList<SimpleReference> _references;
1111
private readonly IList<ValueResolver> _resolvers;
1212
private Func<int, IDictionary<string, object>> _creator;
13-
private GroupingHandler _groupingHandler;
13+
private readonly GroupingHandler _groupingHandler;
1414

1515
public SelectClauseHandler(SelectClause clause)
1616
{

0 commit comments

Comments
 (0)