Skip to content

Commit

Permalink
Fix for issue #244
Browse files Browse the repository at this point in the history
I assumed that SQL Ce supports DISTINCT when doing the regex replace -
to be fair it won't break anything if it doesn't!
  • Loading branch information
richardhopton committed Nov 27, 2012
1 parent c027ed7 commit 0f9d3c3
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 256 deletions.
68 changes: 34 additions & 34 deletions Simple.Data.SqlCe40/SqlCe40QueryPager.cs
@@ -1,34 +1,34 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Simple.Data.Ado;

namespace Simple.Data.SqlCe40
{
[Export(typeof(IQueryPager))]
public class SqlCe40QueryPager : IQueryPager
{
private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(FROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase);
private static readonly Regex SelectMatch = new Regex(@"^SELECT\s*", RegexOptions.IgnoreCase);

public IEnumerable<string> ApplyLimit(string sql, int take)
{
yield return SelectMatch.Replace(sql, match => match.Value + " TOP(" + take + ") ");
}

public IEnumerable<string> ApplyPaging(string sql, string[] keys, int skip, int take)
{
if (sql.IndexOf("order by", StringComparison.InvariantCultureIgnoreCase) < 0)
{
var match = ColumnExtract.Match(sql);
var columns = match.Groups[1].Value.Trim();
sql += " ORDER BY " + columns.Split(',').First().Trim();
}

yield return string.Format("{0} OFFSET {1} ROWS FETCH NEXT {2} ROWS ONLY", sql, skip, take);
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Simple.Data.Ado;

namespace Simple.Data.SqlCe40
{
[Export(typeof(IQueryPager))]
public class SqlCe40QueryPager : IQueryPager
{
private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(FROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase);
private static readonly Regex SelectMatch = new Regex(@"^SELECT\s*(DISTINCT)?", RegexOptions.IgnoreCase);

public IEnumerable<string> ApplyLimit(string sql, int take)
{
yield return SelectMatch.Replace(sql, match => match.Value + " TOP(" + take + ") ");
}

public IEnumerable<string> ApplyPaging(string sql, string[] keys, int skip, int take)
{
if (sql.IndexOf("order by", StringComparison.InvariantCultureIgnoreCase) < 0)
{
var match = ColumnExtract.Match(sql);
var columns = match.Groups[1].Value.Trim();
sql += " ORDER BY " + columns.Split(',').First().Trim();
}

yield return string.Format("{0} OFFSET {1} ROWS FETCH NEXT {2} ROWS ONLY", sql, skip, take);
}
}
}
114 changes: 63 additions & 51 deletions Simple.Data.SqlCe40Test/SqlCe40QueryPagerTest.cs
@@ -1,51 +1,63 @@
using System.Text.RegularExpressions;
using NUnit.Framework;
using Simple.Data.SqlCe40;
using System.Linq;

namespace Simple.Data.SqlCe40Test
{
[TestFixture]
public class SqlCe40QueryPagerTest
{
static readonly Regex Normalize = new Regex(@"\s+", RegexOptions.Multiline);

[Test]
public void ShouldApplyLimitUsingTop()
{
var sql = "select a,b,c from d where a = 1 order by c";
var expected = new[] { "select top(5) a,b,c from d where a = 1 order by c" };

var pagedSql = new SqlCe40QueryPager().ApplyLimit(sql, 5);
var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant());

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

[Test]
public void ShouldApplyPagingUsingOrderBy()
{
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 pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, new string[0], 5, 10);
var modified = pagedSql.Select(x=> Normalize.Replace(x, " ").ToLowerInvariant());

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

[Test]
public void ShouldApplyPagingUsingOrderByFirstColumnIfNotAlreadyOrdered()
{
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 pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, new string[0], 10, 20);
var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant());

Assert.IsTrue(expected.SequenceEqual(modified));
}
}
}
using System.Text.RegularExpressions;
using NUnit.Framework;
using Simple.Data.SqlCe40;
using System.Linq;

namespace Simple.Data.SqlCe40Test
{
[TestFixture]
public class SqlCe40QueryPagerTest
{
static readonly Regex Normalize = new Regex(@"\s+", RegexOptions.Multiline);

[Test]
public void ShouldApplyLimitUsingTop()
{
var sql = "select a,b,c from d where a = 1 order by c";
var expected = new[] { "select top(5) a,b,c from d where a = 1 order by c" };

var pagedSql = new SqlCe40QueryPager().ApplyLimit(sql, 5);
var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant());

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

[Test]
public void ShouldApplyLimitUsingTopWithDistinct()
{
var sql = "select distinct a,b,c from d where a = 1 order by c";
var expected = new[] { "select distinct top(5) a,b,c from d where a = 1 order by c" };

var pagedSql = new SqlCe40QueryPager().ApplyLimit(sql, 5);
var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant());

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

[Test]
public void ShouldApplyPagingUsingOrderBy()
{
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 pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, new string[0], 5, 10);
var modified = pagedSql.Select(x=> Normalize.Replace(x, " ").ToLowerInvariant());

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

[Test]
public void ShouldApplyPagingUsingOrderByFirstColumnIfNotAlreadyOrdered()
{
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 pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, new string[0], 10, 20);
var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant());

Assert.IsTrue(expected.SequenceEqual(modified));
}
}
}
46 changes: 23 additions & 23 deletions Simple.Data.SqlServer/SqlCommandOptimizer.cs
@@ -1,23 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Simple.Data.SqlServer
{
using System.ComponentModel.Composition;
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using Ado;

[Export(typeof(CommandOptimizer))]
public class SqlCommandOptimizer : CommandOptimizer
{
public override System.Data.IDbCommand OptimizeFindOne(System.Data.IDbCommand command)
{
command.CommandText = Regex.Replace(command.CommandText, "^SELECT ", "SELECT TOP 1 ",
RegexOptions.IgnoreCase);
return command;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Simple.Data.SqlServer
{
using System.ComponentModel.Composition;
using System.Data.SqlClient;
using System.Text.RegularExpressions;
using Ado;

[Export(typeof(CommandOptimizer))]
public class SqlCommandOptimizer : CommandOptimizer
{
public override System.Data.IDbCommand OptimizeFindOne(System.Data.IDbCommand command)
{
command.CommandText = Regex.Replace(command.CommandText, @"^SELECT\s*(DISTINCT)?", "SELECT $1 TOP 1 ",
RegexOptions.IgnoreCase);
return command;
}
}
}
156 changes: 78 additions & 78 deletions Simple.Data.SqlServer/SqlQueryPager.cs
@@ -1,78 +1,78 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Simple.Data.Ado;

namespace Simple.Data.SqlServer
{
[Export(typeof(IQueryPager))]
public class SqlQueryPager : IQueryPager
{
private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(FROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase);
private static readonly Regex SelectMatch = new Regex(@"^SELECT\s*", RegexOptions.IgnoreCase);

public IEnumerable<string> ApplyLimit(string sql, int take)
{
yield return SelectMatch.Replace(sql, match => match.Value + " TOP " + take + " ");
}

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

var match = ColumnExtract.Match(sql);
var columns = match.Groups[1].Value.Trim();
var fromEtc = match.Groups[2].Value.Trim();

builder.Append(string.Join(",", keys));

var orderBy = ExtractOrderBy(columns, keys, ref fromEtc);

builder.AppendFormat(", ROW_NUMBER() OVER({0}) AS [_#_]", orderBy);
builder.AppendLine();
builder.Append(fromEtc);
builder.AppendLine(")");
builder.AppendFormat("SELECT {0} FROM __Data ", columns);
builder.AppendFormat("JOIN {0} ON ",
keys[0].Substring(0, keys[0].LastIndexOf(".", StringComparison.OrdinalIgnoreCase)));
builder.AppendFormat(string.Join(" ", keys.Select(MakeDataJoin)));
var rest = Regex.Replace(fromEtc, @"^from (\[.*?\]\.\[.*?\])", @"");
builder.Append(rest);

builder.AppendFormat(" AND [_#_] BETWEEN {0} AND {1}", skip + 1, skip + take);

yield return builder.ToString();
}

private static string MakeDataJoin(string key)
{
return key + " = __Data" + key.Substring(key.LastIndexOf(".", StringComparison.OrdinalIgnoreCase));
}

private static string DequalifyColumns(string original)
{
var q = from part in original.Split(',')
select part.Substring(Math.Max(part.LastIndexOf('.') + 1, part.LastIndexOf('[')));
return string.Join(",", q);
}

private static string ExtractOrderBy(string columns, string[] keys, ref string fromEtc)
{
string orderBy;
int index = fromEtc.IndexOf("ORDER BY", StringComparison.InvariantCultureIgnoreCase);
if (index > -1)
{
orderBy = fromEtc.Substring(index).Trim();
fromEtc = fromEtc.Remove(index).Trim();
}
else
{
orderBy = "ORDER BY " + string.Join(", ", keys);
}
return orderBy;
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Simple.Data.Ado;

namespace Simple.Data.SqlServer
{
[Export(typeof(IQueryPager))]
public class SqlQueryPager : IQueryPager
{
private static readonly Regex ColumnExtract = new Regex(@"SELECT\s*(.*)\s*(FROM.*)", RegexOptions.Multiline | RegexOptions.IgnoreCase);
private static readonly Regex SelectMatch = new Regex(@"^SELECT\s*(DISTINCT)?", RegexOptions.IgnoreCase);

public IEnumerable<string> ApplyLimit(string sql, int take)
{
yield return SelectMatch.Replace(sql, match => match.Value + " TOP " + take + " ");
}

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

var match = ColumnExtract.Match(sql);
var columns = match.Groups[1].Value.Trim();
var fromEtc = match.Groups[2].Value.Trim();

builder.Append(string.Join(",", keys));

var orderBy = ExtractOrderBy(columns, keys, ref fromEtc);

builder.AppendFormat(", ROW_NUMBER() OVER({0}) AS [_#_]", orderBy);
builder.AppendLine();
builder.Append(fromEtc);
builder.AppendLine(")");
builder.AppendFormat("SELECT {0} FROM __Data ", columns);
builder.AppendFormat("JOIN {0} ON ",
keys[0].Substring(0, keys[0].LastIndexOf(".", StringComparison.OrdinalIgnoreCase)));
builder.AppendFormat(string.Join(" ", keys.Select(MakeDataJoin)));
var rest = Regex.Replace(fromEtc, @"^from (\[.*?\]\.\[.*?\])", @"");
builder.Append(rest);

builder.AppendFormat(" AND [_#_] BETWEEN {0} AND {1}", skip + 1, skip + take);

yield return builder.ToString();
}

private static string MakeDataJoin(string key)
{
return key + " = __Data" + key.Substring(key.LastIndexOf(".", StringComparison.OrdinalIgnoreCase));
}

private static string DequalifyColumns(string original)
{
var q = from part in original.Split(',')
select part.Substring(Math.Max(part.LastIndexOf('.') + 1, part.LastIndexOf('[')));
return string.Join(",", q);
}

private static string ExtractOrderBy(string columns, string[] keys, ref string fromEtc)
{
string orderBy;
int index = fromEtc.IndexOf("ORDER BY", StringComparison.InvariantCultureIgnoreCase);
if (index > -1)
{
orderBy = fromEtc.Substring(index).Trim();
fromEtc = fromEtc.Remove(index).Trim();
}
else
{
orderBy = "ORDER BY " + string.Join(", ", keys);
}
return orderBy;
}
}
}

0 comments on commit 0f9d3c3

Please sign in to comment.