Skip to content

Commit

Permalink
Add support for multi paging - required for ASA8
Browse files Browse the repository at this point in the history
  • Loading branch information
richardhopton committed Nov 20, 2011
1 parent 50f0f54 commit 208c9f6
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 77 deletions.
84 changes: 77 additions & 7 deletions Simple.Data.Ado/AdoAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,16 @@ public override IEnumerable<IDictionary<string, object>> RunQuery(SimpleQuery qu
{
if (query.Clauses.OfType<WithCountClause>().Any()) return RunQueryWithCount(query, out unhandledClauses);

var commandBuilders = this.GetQueryCommandBuilders(query, out unhandledClauses);
var connection = _connectionProvider.CreateConnection();
return new QueryBuilder(this).Build(query, out unhandledClauses)
.GetCommand(connection)
.ToEnumerable(connection);
if (ProviderSupportsCompoundStatements || commandBuilders.Length == 1)
{
return CommandBuilder.CreateCommand(_providerHelper.GetCustomProvider<IDbParameterFactory>(_schema.SchemaProvider), commandBuilders, connection).ToEnumerable(connection);
}
else
{
return commandBuilders.SelectMany(cb => cb.GetCommand(connection).ToEnumerable(connection));
}
}

private IEnumerable<IDictionary<string, object>> RunQueryWithCount(SimpleQuery query, out IEnumerable<SimpleQueryClauseBase> unhandledClauses)
Expand Down Expand Up @@ -141,19 +147,83 @@ private IEnumerable<IDictionary<string, object>> RunQueryWithCount(SimpleQuery q
}
}

public override IEnumerable<IEnumerable<IDictionary<string,object>>> RunQueries(SimpleQuery[] queries, List<IEnumerable<SimpleQueryClauseBase>> unhandledClauses)
private ICommandBuilder[] GetPagedQueryCommandBuilders(SimpleQuery query, out IEnumerable<SimpleQueryClauseBase> unhandledClauses)
{
return this.GetPagedQueryCommandBuilders(query, -1, out unhandledClauses);
}

private ICommandBuilder[] GetPagedQueryCommandBuilders(SimpleQuery query, Int32 bulkIndex, out IEnumerable<SimpleQueryClauseBase> unhandledClauses)
{
var commandBuilders = new List<ICommandBuilder>();
var unhandledClausesList = new List<SimpleQueryClauseBase>();
unhandledClauses = unhandledClausesList;

IEnumerable<SimpleQueryClauseBase> unhandledClausesForPagedQuery;
var mainCommandBuilder = new QueryBuilder(this, bulkIndex).Build(query, out unhandledClausesForPagedQuery);
unhandledClausesList.AddRange(unhandledClausesForPagedQuery);

const int maxInt = 2147483646;

var skipClause = query.Clauses.OfType<SkipClause>().FirstOrDefault() ?? new SkipClause(0);
var takeClause = query.Clauses.OfType<TakeClause>().FirstOrDefault() ?? new TakeClause(maxInt);

if (skipClause.Count != 0 || takeClause.Count != maxInt)
{
var queryPager = this.ProviderHelper.GetCustomProvider<IQueryPager>(this.ConnectionProvider);
if (queryPager == null)
{
unhandledClausesList.AddRange(query.OfType<SkipClause>());
unhandledClausesList.AddRange(query.OfType<TakeClause>());
}

var commandTexts = queryPager.ApplyPaging(mainCommandBuilder.Text, skipClause.Count, takeClause.Count);

foreach (var commandText in commandTexts)
{
var commandBuilder = new CommandBuilder(commandText, this._schema, mainCommandBuilder.Parameters);
commandBuilders.Add(commandBuilder);
}
}
return commandBuilders.ToArray();
}

private ICommandBuilder[] GetQueryCommandBuilders(SimpleQuery query, out IEnumerable<SimpleQueryClauseBase> unhandledClauses)
{
if (query.Clauses.OfType<TakeClause>().Any() || query.Clauses.OfType<SkipClause>().Any())
{
return this.GetPagedQueryCommandBuilders(query, out unhandledClauses);
}
else
{
return new[] { new QueryBuilder(this).Build(query, out unhandledClauses) };
}
}

private ICommandBuilder[] GetQueryCommandBuilders(SimpleQuery query, Int32 bulkIndex, out IEnumerable<SimpleQueryClauseBase> unhandledClauses)
{
if (query.Clauses.OfType<TakeClause>().Any() || query.Clauses.OfType<SkipClause>().Any())
{
return this.GetPagedQueryCommandBuilders(query, bulkIndex, out unhandledClauses);
}
else
{
return new[] { new QueryBuilder(this, bulkIndex).Build(query, out unhandledClauses) };
}
}

public override IEnumerable<IEnumerable<IDictionary<string, object>>> RunQueries(SimpleQuery[] queries, List<IEnumerable<SimpleQueryClauseBase>> unhandledClauses)
{
if (ProviderSupportsCompoundStatements && queries.Length > 1)
{
var commandBuilders = new ICommandBuilder[queries.Length];
var commandBuilders = new List<ICommandBuilder>();
for (int i = 0; i < queries.Length; i++)
{
IEnumerable<SimpleQueryClauseBase> unhandledClausesForThisQuery;
commandBuilders[i] = new QueryBuilder(this, i).Build(queries[i], out unhandledClausesForThisQuery);
commandBuilders.AddRange(GetQueryCommandBuilders(queries[i], i, out unhandledClausesForThisQuery));
unhandledClauses.Add(unhandledClausesForThisQuery);
}
var connection = _connectionProvider.CreateConnection();
var command = CommandBuilder.CreateCommand(_providerHelper.GetCustomProvider<IDbParameterFactory>(_schema.SchemaProvider), commandBuilders, connection);
var command = CommandBuilder.CreateCommand(_providerHelper.GetCustomProvider<IDbParameterFactory>(_schema.SchemaProvider), commandBuilders.ToArray(), connection);
foreach (var item in command.ToEnumerables(connection))
{
yield return item.ToList();
Expand Down
9 changes: 9 additions & 0 deletions Simple.Data.Ado/CommandBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ public CommandBuilder(string text, DatabaseSchema schema, int bulkIndex)
_parameterSuffix = (bulkIndex >= 0) ? "_c" + bulkIndex : string.Empty;
}

public CommandBuilder(string text, DatabaseSchema schema, IEnumerable<KeyValuePair<ParameterTemplate, Object>> parameters)
: this(text, schema, -1)
{
foreach (var kvp in parameters)
{
_parameters.Add(kvp.Key, kvp.Value);
}
}

public ParameterTemplate AddParameter(object value)
{
string name = _schemaProvider.NameParameter("p" + Interlocked.Increment(ref _number) + _parameterSuffix);
Expand Down
11 changes: 9 additions & 2 deletions Simple.Data.Ado/DataReaderEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public bool MoveNext()
{
ExecuteReader();
}
return _lastRead = _reader.Read();
return _lastRead = (_reader != null && _reader.Read());
}

private void ExecuteReader()
Expand All @@ -55,7 +55,10 @@ private void ExecuteReader()
if (_connection.State == ConnectionState.Closed)
_connection.Open();
_reader = _command.ExecuteReader();
_index = _index ?? _reader.CreateDictionaryIndex();
if (_reader != null)
{
_index = _index ?? _reader.CreateDictionaryIndex();
}
}
catch (DbException ex)
{
Expand All @@ -73,6 +76,10 @@ public IDictionary<string, object> Current
{
get
{
if (_reader == null)
{
return null;
}
if (!_lastRead) throw new InvalidOperationException();
return _reader.ToDictionary(_index);
}
Expand Down
4 changes: 3 additions & 1 deletion Simple.Data.Ado/IQueryPager.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections.Generic;

namespace Simple.Data.Ado
{
public interface IQueryPager
{
string ApplyPaging(string sql, string skipParameterName, string takeParameterName);
IEnumerable<string> ApplyPaging(string sql, int skip, int take);
}
}
16 changes: 13 additions & 3 deletions Simple.Data.Ado/ProviderHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,13 @@ private static IConnectionProvider LoadProviderByConnectionToken(ConnectionToken

public T GetCustomProvider<T>(IConnectionProvider connectionProvider)
{
return (T)_customProviderCache.GetOrAdd(typeof (T), t => GetCustomProviderExport<T>(connectionProvider));
return (T)_customProviderCache.GetOrAdd(typeof (T), t => GetCustomProviderExport<T>(connectionProvider.GetType().Assembly) ??
GetCustomProviderServiceProvider(connectionProvider as IServiceProvider, t));
}

private static T GetCustomProviderExport<T>(IConnectionProvider connectionProvider)
private static Object GetCustomProviderExport<T>(Assembly assembly)
{
using (var assemblyCatalog = new AssemblyCatalog(connectionProvider.GetType().Assembly))
using (var assemblyCatalog = new AssemblyCatalog(assembly))
{
using (var container = new CompositionContainer(assemblyCatalog))
{
Expand All @@ -126,6 +127,15 @@ private static T GetCustomProviderExport<T>(IConnectionProvider connectionProvid
}
}

private static Object GetCustomProviderServiceProvider(IServiceProvider serviceProvider, Type type)
{
if (serviceProvider != null)
{
return serviceProvider.GetService(type);
}
return null;
}

public T GetCustomProvider<T>(ISchemaProvider schemaProvider)
{
return (T)_customProviderCache.GetOrAdd(typeof(T), t => GetCustomProviderExport<T>(schemaProvider));
Expand Down
22 changes: 0 additions & 22 deletions Simple.Data.Ado/QueryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public ICommandBuilder Build(SimpleQuery query, out IEnumerable<SimpleQueryClaus
HandleGrouping();
HandleHavingCriteria();
HandleOrderBy();
HandlePaging();

unhandledClauses = _unhandledClauses;
return _commandBuilder;
Expand Down Expand Up @@ -164,27 +163,6 @@ private void HandleOrderBy()
_commandBuilder.Append(" ORDER BY " + string.Join(", ", orderNames));
}

private void HandlePaging()
{
const int maxInt = 2147483646;

var skipClause = _query.Clauses.OfType<SkipClause>().FirstOrDefault() ?? new SkipClause(0);
var takeClause = _query.Clauses.OfType<TakeClause>().FirstOrDefault() ?? new TakeClause(maxInt);
if (skipClause.Count != 0 || takeClause.Count != maxInt)
{
var queryPager = _adoAdapter.ProviderHelper.GetCustomProvider<IQueryPager>(_adoAdapter.ConnectionProvider);
if (queryPager == null)
{
_unhandledClauses.AddRange(_query.OfType<SkipClause>());
_unhandledClauses.AddRange(_query.OfType<TakeClause>());
}

var skipTemplate = _commandBuilder.AddParameter("skip", DbType.Int32, skipClause.Count);
var takeTemplate = _commandBuilder.AddParameter("take", DbType.Int32, takeClause.Count);
_commandBuilder.SetText(queryPager.ApplyPaging(_commandBuilder.Text, skipTemplate.Name, takeTemplate.Name));
}
}

private string ToOrderByDirective(OrderByClause item)
{
var col = _table.FindColumn(item.Reference.GetName());
Expand Down
4 changes: 2 additions & 2 deletions Simple.Data.SqlCe40/SqlCe40QueryPager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class SqlCe40QueryPager : IQueryPager
{
private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(FROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase);

public string ApplyPaging(string sql, string skipParameterName, string takeParameterName)
public IEnumerable<string> ApplyPaging(string sql, int skip, int take)
{
if (sql.IndexOf("order by", StringComparison.InvariantCultureIgnoreCase) < 0)
{
Expand All @@ -22,7 +22,7 @@ public string ApplyPaging(string sql, string skipParameterName, string takeParam
sql += " ORDER BY " + columns.Split(',').First().Trim();
}

return string.Format("{0} OFFSET {1} ROWS FETCH NEXT {2} ROWS ONLY", sql, skipParameterName, takeParameterName);
yield return string.Format("{0} OFFSET {1} ROWS FETCH NEXT {2} ROWS ONLY", sql, skip, take);
}
}
}
25 changes: 13 additions & 12 deletions Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text.RegularExpressions;
using NUnit.Framework;
using Simple.Data.SqlCe40;
using System.Linq;

namespace Simple.Data.SqlCe40Test
{
Expand All @@ -12,27 +13,27 @@ public class SqlCe40QueryPagerTest
[Test]
public void ShouldApplyPagingUsingOrderBy()
{
const string sql = "select a,b,c from d where a = 1 order by c";
const string expected =
"select a,b,c from d where a = 1 order by c offset @skip rows fetch next @take rows only";
var sql = "select a,b,c from d where a = 1 order by c";
var expected = new[]{
"select a,b,c from d where a = 1 order by c offset 5 rows fetch next 10 rows only"};

var modified = new SqlCe40QueryPager().ApplyPaging(sql, "@skip", "@take");
modified = Normalize.Replace(modified, " ").ToLowerInvariant();
var pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, 5, 10);
var modified = pagedSql.Select(x=> Normalize.Replace(x, " ").ToLowerInvariant());

Assert.AreEqual(expected, modified);
Assert.IsTrue(expected.SequenceEqual(modified));
}

[Test]
public void ShouldApplyPagingUsingOrderByFirstColumnIfNotAlreadyOrdered()
{
const string sql = "select a,b,c from d where a = 1";
const string expected =
"select a,b,c from d where a = 1 order by a offset @skip rows fetch next @take rows only";
var sql = "select a,b,c from d where a = 1";
var expected = new[]{
"select a,b,c from d where a = 1 order by a offset 10 rows fetch next 20 rows only"};

var modified = new SqlCe40QueryPager().ApplyPaging(sql, "@skip", "@take");
modified = Normalize.Replace(modified, " ").ToLowerInvariant();
var pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, 10, 20);
var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant());

Assert.AreEqual(expected, modified);
Assert.IsTrue(expected.SequenceEqual(modified));
}
}
}
8 changes: 4 additions & 4 deletions Simple.Data.SqlServer/SqlQueryPager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class SqlQueryPager : IQueryPager
{
private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(FROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase);

public string ApplyPaging(string sql, string skipParameterName, string takeParameterName)
public IEnumerable<string> ApplyPaging(string sql, int skip, int take)
{
var builder = new StringBuilder("WITH __Data AS (SELECT ");

Expand All @@ -29,10 +29,10 @@ public string ApplyPaging(string sql, string skipParameterName, string takeParam
builder.AppendLine();
builder.Append(fromEtc);
builder.AppendLine(")");
builder.AppendFormat("SELECT {0} FROM __Data WHERE [_#_] BETWEEN {1} + 1 AND {1} + {2}", DequalifyColumns(columns),
skipParameterName, takeParameterName);
builder.AppendFormat("SELECT {0} FROM __Data WHERE [_#_] BETWEEN {1} AND {2}", DequalifyColumns(columns),
skip + 1, skip + take);

return builder.ToString();
yield return builder.ToString();
}

private static string DequalifyColumns(string original)
Expand Down
Loading

0 comments on commit 208c9f6

Please sign in to comment.