Skip to content

Commit ad13089

Browse files
committed
#13 ensured compatibility with $filter, $orderby, $top, $skip.
1 parent 25675f5 commit ad13089

File tree

6 files changed

+198
-20
lines changed

6 files changed

+198
-20
lines changed

SubSonic.Tests/DAL/OData/ODataCompatibilityTests.cs

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace SubSonic.Tests.DAL.OData
1212
using Infrastructure.Logging;
1313
using Linq.Expressions;
1414
using System;
15+
using System.Linq.Expressions;
1516

1617
[TestFixture]
1718
public class ODataCompatibilityTests
@@ -68,6 +69,8 @@ public void ShouldBeAbleToApplyWhereUsingOData(string @operator)
6869

6970
IQueryable select = personQueryOptions.ApplyTo(Context.People);
7071

72+
select.Expression.Should().BeOfType<DbSelectExpression>();
73+
7174
IDbQuery query = null;
7275

7376
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
@@ -128,6 +131,8 @@ public void ShouldBeAbleToApplyOrderByUsingOData(string direction)
128131

129132
IQueryable select = personQueryOptions.ApplyTo(Context.People);
130133

134+
select.Expression.Should().BeOfType<DbSelectExpression>();
135+
131136
IDbQuery query = null;
132137

133138
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
@@ -186,6 +191,8 @@ public void ShouldBeAbleToApplyOrderByThenByUsingOData(string direction)
186191

187192
IQueryable select = personQueryOptions.ApplyTo(Context.People);
188193

194+
select.Expression.Should().BeOfType<DbSelectExpression>();
195+
189196
IDbQuery query = null;
190197

191198
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
@@ -218,7 +225,7 @@ public void ShouldBeAbleToApplyOrderByThenByUsingOData(string direction)
218225
[Test]
219226
[TestCase(1)]
220227
[TestCase(10)]
221-
public void ShouldBeAbleToApplyTopUsingOData(int top)
228+
public void ShouldBeAbleToApplyTakeUsingOData(int top)
222229
{
223230
personQueryOptions.ApplyTo(Arg.Any<IQueryable>())
224231
.Returns(call =>
@@ -235,6 +242,8 @@ public void ShouldBeAbleToApplyTopUsingOData(int top)
235242

236243
IDbQuery query = null;
237244

245+
select.Expression.Should().BeOfType<DbSelectExpression>();
246+
238247
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
239248

240249
using (var perf = logging.Start("SQL Query Writer"))
@@ -252,5 +261,104 @@ public void ShouldBeAbleToApplyTopUsingOData(int top)
252261
query.Sql.Should().NotBeNullOrEmpty();
253262
query.Sql.Should().Contain($"SELECT TOP ({top})");
254263
}
264+
265+
/// <summary>
266+
/// simulate a $skip
267+
/// </summary>
268+
/// <param name="top"></param>
269+
/// <remarks>
270+
/// skips by themselves do not make a lick of sense, but we should recognize that a skip was applied
271+
/// </remarks>
272+
[Test]
273+
[TestCase(1)]
274+
[TestCase(10)]
275+
public void ShouldBeAbleToApplySkipUsingOData(int skip)
276+
{
277+
personQueryOptions.ApplyTo(Arg.Any<IQueryable>())
278+
.Returns(call =>
279+
{
280+
if (call.Arg<IQueryable>() is IQueryable<Models.Person> people)
281+
{
282+
return people.Skip(skip);
283+
}
284+
285+
throw new NotImplementedException();
286+
});
287+
288+
IQueryable select = personQueryOptions.ApplyTo(Context.People);
289+
290+
select.Expression.Should().BeOfType<DbSelectExpression>();
291+
292+
if (select.Expression is DbSelectExpression expression)
293+
{
294+
if (expression.Skip is ConstantExpression constant)
295+
{
296+
constant.Value.Should().Be(skip);
297+
}
298+
else
299+
{
300+
throw Error.InvalidOperation();
301+
}
302+
}
303+
}
304+
305+
/// <summary>
306+
/// simulate a $skip and $top
307+
/// </summary>
308+
/// <param name="top"></param>
309+
/// <remarks>
310+
/// skips by themselves do not make a lick of sense, but we should recognize that a skip was applied
311+
/// </remarks>
312+
[Test]
313+
[TestCase(5, 10, true)]
314+
[TestCase(10, 0, false)]
315+
public void ShouldBeAbleToApplyTakeSkipUsingOData(int take, int skip, bool reverse)
316+
{
317+
personQueryOptions.ApplyTo(Arg.Any<IQueryable>())
318+
.Returns(call =>
319+
{
320+
if (call.Arg<IQueryable>() is IQueryable<Models.Person> people)
321+
{
322+
if (!reverse)
323+
{
324+
return people.Take(take).Skip(skip);
325+
}
326+
else
327+
{
328+
return people.Skip(skip).Take(take);
329+
}
330+
}
331+
332+
throw new NotImplementedException();
333+
});
334+
335+
IQueryable select = personQueryOptions.ApplyTo(Context.People);
336+
337+
int
338+
pageNumber = skip / take;
339+
340+
select.Expression.Should().BeOfType<DbSelectPageExpression>();
341+
342+
IDbQuery query = null;
343+
344+
var logging = Context.Instance.GetService<ISubSonicLogger<DbSelectExpression>>();
345+
346+
using (var perf = logging.Start("SQL Query Writer"))
347+
{
348+
FluentActions.Invoking(() =>
349+
{
350+
ISubSonicQueryProvider<Models.Person> builder = Context.Instance.GetService<ISubSonicQueryProvider<Models.Person>>();
351+
352+
query = builder.ToQuery(select.Expression);
353+
}).Should().NotThrow();
354+
}
355+
356+
logging.LogInformation("\n" + query.Sql + "\n");
357+
358+
query.Sql.Should().NotBeNullOrEmpty();
359+
query.Sql.Should().NotContain($"SELECT TOP ({take})");
360+
query.Parameters.Get("@PageSize").Value.Should().Be(take);
361+
query.Parameters.Get("@PageNumber").Value.Should().Be(pageNumber);
362+
}
255363
}
256364
}

SubSonic/Error.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,19 @@ public static Exception NotImplemented()
2121
return new NotImplementedException();
2222
}
2323

24-
internal static Exception NotSupported(string message)
24+
public static Exception NotSupported(string message)
2525
{
2626
return new NotSupportedException(message);
2727
}
28+
29+
public static Exception DivideByZero()
30+
{
31+
return new DivideByZeroException();
32+
}
33+
34+
public static Exception InvalidOperation()
35+
{
36+
return new InvalidOperationException();
37+
}
2838
}
2939
}

SubSonic/Infrastructure/Builders/DbSqlQueryBuilder/DbSqlQueryBuilderBuildMethods.cs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public Expression BuildSelect(Expression select, Expression where)
4545
{
4646
if (select is DbSelectExpression _select)
4747
{
48-
return new DbSelectExpression(_select.QueryObject, _select.Type, _select.From, _select.Columns, where, _select.OrderBy, _select.GroupBy, _select.IsDistinct, _select.Take);
48+
return new DbSelectExpression(_select.QueryObject, _select.Type, _select.From, _select.Columns, where, _select.OrderBy, _select.GroupBy, _select.IsDistinct, _select.Take, _select.Skip);
4949
}
5050

5151
throw new NotSupportedException();
@@ -55,7 +55,7 @@ public Expression BuildSelect(Expression expression, bool isDistinct)
5555
{
5656
if (expression is DbSelectExpression select)
5757
{
58-
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, select.OrderBy, select.GroupBy, isDistinct, select.Take);
58+
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, select.OrderBy, select.GroupBy, isDistinct, select.Take, select.Skip);
5959
}
6060

6161
throw new NotSupportedException();
@@ -65,7 +65,7 @@ public Expression BuildSelect(Expression expression, int count)
6565
{
6666
if (expression is DbSelectExpression select)
6767
{
68-
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, Expression.Constant(count));
68+
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, Expression.Constant(count), select.Skip);
6969
}
7070

7171
throw new NotSupportedException();
@@ -98,7 +98,7 @@ public Expression BuildSelect(Expression expression, IDbEntityProperty property)
9898
typeof(SysLinq.IQueryable<>).MakeGenericType(property.PropertyType),
9999
select.From,
100100
select.Columns.Where(x => x.PropertyName.Equals(property.PropertyName, StringComparison.CurrentCulture)),
101-
select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take);
101+
select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take, select.Skip);
102102
}
103103

104104
throw new NotSupportedException();
@@ -108,7 +108,7 @@ public Expression BuildSelect(Expression expression, IEnumerable<DbOrderByDeclar
108108
{
109109
if (expression is DbSelectExpression select)
110110
{
111-
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, orderBy, select.GroupBy, select.IsDistinct, select.Take);
111+
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, orderBy, select.GroupBy, select.IsDistinct, select.Take, select.Skip);
112112
}
113113

114114
throw new NotSupportedException();
@@ -117,7 +117,7 @@ public Expression BuildSelect(Expression expression, IEnumerable<Expression> gro
117117
{
118118
if (expression is DbSelectExpression select)
119119
{
120-
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, select.OrderBy, groupBy, select.IsDistinct, select.Take);
120+
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, select.OrderBy, groupBy, select.IsDistinct, select.Take, select.Skip);
121121
}
122122

123123
throw new NotSupportedException();
@@ -136,7 +136,7 @@ public Expression BuildSelect<TEntity, TColumn>(Expression expression, Expressio
136136
{
137137
if (expression is DbSelectExpression select)
138138
{
139-
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns.Where(col => col.PropertyName == selector.GetPropertyName()), select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take);
139+
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns.Where(col => col.PropertyName == selector.GetPropertyName()), select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take, select.Skip);
140140
}
141141
return expression;
142142
}
@@ -501,7 +501,7 @@ protected virtual Expression BuildQuery(Expression expression)
501501
}
502502
else if (call.Method.IsSkip())
503503
{
504-
throw Error.NotImplemented();
504+
return BuildSelectWithSkip(call);
505505
}
506506
else
507507
{
@@ -534,6 +534,35 @@ private static Type[] GetTypeArguments(LambdaExpression expression)
534534
return Array.Empty<Type>();
535535
}
536536

537+
private Expression BuildSelectWithSkip(MethodCallExpression expression)
538+
{
539+
if (!(expression is null))
540+
{
541+
DbSelectExpression select = null;
542+
Expression skip = null;
543+
544+
foreach (var argument in expression.Arguments)
545+
{
546+
if (argument is DbSelectExpression _select)
547+
{
548+
select = _select;
549+
}
550+
else if (argument is ConstantExpression constant)
551+
{
552+
skip = constant;
553+
}
554+
}
555+
556+
return DbExpression.DbSelect(
557+
select,
558+
select.Take,
559+
skip
560+
);
561+
}
562+
563+
return expression;
564+
}
565+
537566
private Expression BuildSelectWithTake(MethodCallExpression expression)
538567
{
539568
if (!(expression is null))
@@ -555,7 +584,8 @@ private Expression BuildSelectWithTake(MethodCallExpression expression)
555584

556585
return DbExpression.DbSelect(
557586
select,
558-
take
587+
take,
588+
select.Skip
559589
);
560590
}
561591

SubSonic/Infrastructure/Builders/DbSqlTableTypeProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public Expression BuildSelect(Expression expression, IDbEntityProperty property)
111111
typeof(SysLinq.IQueryable<>).MakeGenericType(property.PropertyType),
112112
select.From,
113113
select.Columns.Where(x => x.PropertyName.Equals(property.PropertyName, StringComparison.CurrentCulture)),
114-
select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take);
114+
select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take, select.Skip);
115115
}
116116

117117
throw new NotSupportedException();

SubSonic/Linq/Expressions/DbSelectExpression.cs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ protected internal DbSelectExpression(
5555
IEnumerable<DbOrderByDeclaration> orderBy,
5656
IEnumerable<Expression> groupBy
5757
)
58-
: this(collection, type, table, columns, where, orderBy, groupBy, false, null) { }
58+
: this(collection, type, table, columns, where, orderBy, groupBy, false, null, null) { }
5959

6060
protected internal DbSelectExpression(
6161
object collection,
@@ -66,7 +66,8 @@ protected internal DbSelectExpression(
6666
IEnumerable<DbOrderByDeclaration> orderBy,
6767
IEnumerable<Expression> groupBy,
6868
bool isDistinct,
69-
Expression take)
69+
Expression take,
70+
Expression skip)
7071
: this(collection, type, table, columns)
7172
{
7273
IsDistinct = isDistinct;
@@ -83,6 +84,7 @@ protected internal DbSelectExpression(
8384
GroupBy = new List<Expression>(groupBy).AsReadOnly();
8485
}
8586
Take = take;
87+
Skip = skip;
8688
}
8789

8890
public bool IsCte { get; set; }
@@ -96,7 +98,9 @@ protected internal DbSelectExpression(
9698
public ReadOnlyCollection<DbOrderByDeclaration> OrderBy { get; }
9799
public ReadOnlyCollection<Expression> GroupBy { get; }
98100
public bool IsDistinct { get; }
99-
public Expression Take { get; set; }
101+
public Expression Take { get; }
102+
103+
public Expression Skip { get; }
100104
public string QueryText
101105
{
102106
get { return DbContext.GenerateSqlFor(this); }
@@ -140,17 +144,41 @@ public static DbExpression DbSelect(DbSelectExpression select, DbExpression wher
140144
throw Error.ArgumentNull(nameof(select));
141145
}
142146

143-
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, where ?? select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take);
147+
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, where ?? select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take, select.Skip);
144148
}
145149

146-
public static DbExpression DbSelect(DbSelectExpression select, Expression take)
150+
public static DbExpression DbSelect(DbSelectExpression select, Expression take, Expression skip)
147151
{
148152
if (select is null)
149153
{
150154
throw Error.ArgumentNull(nameof(select));
151155
}
152156

153-
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, take);
157+
if (!(take is null) && !(skip is null))
158+
{ // in this use case we are most likely paging
159+
if(take is ConstantExpression _take)
160+
{
161+
if (skip is ConstantExpression _skip)
162+
{
163+
int pageSize = (int)_take.Value;
164+
165+
if (pageSize == 0)
166+
{
167+
throw Error.DivideByZero();
168+
}
169+
170+
int pageNumber = ((int)_skip.Value) / pageSize;
171+
172+
return new DbSelectPageExpression(
173+
new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, null, null),
174+
pageNumber,
175+
pageSize
176+
);
177+
}
178+
}
179+
}
180+
181+
return new DbSelectExpression(select.QueryObject, select.Type, select.From, select.Columns, select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, take, skip);
154182
}
155183

156184
public static DbExpression DbSelect(DbSelectExpression select, DbJoinExpression join)
@@ -174,7 +202,7 @@ public static DbExpression DbSelect(DbSelectExpression select, DbJoinExpression
174202

175203
dbTable.Joins.Add(join);
176204

177-
return new DbSelectExpression(select.QueryObject, select.Type, dbTable, select.Columns, select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take);
205+
return new DbSelectExpression(select.QueryObject, select.Type, dbTable, select.Columns, select.Where, select.OrderBy, select.GroupBy, select.IsDistinct, select.Take, select.Skip);
178206
}
179207
}
180208
}

0 commit comments

Comments
 (0)