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;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Simple.Data.Ado; using Simple.Data.Ado;


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


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


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


yield return string.Format("{0} OFFSET {1} ROWS FETCH NEXT {2} ROWS ONLY", sql, skip, take); 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 System.Text.RegularExpressions;
using NUnit.Framework; using NUnit.Framework;
using Simple.Data.SqlCe40; using Simple.Data.SqlCe40;
using System.Linq; using System.Linq;


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


[Test] [Test]
public void ShouldApplyLimitUsingTop() public void ShouldApplyLimitUsingTop()
{ {
var sql = "select a,b,c from d where a = 1 order by c"; 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 expected = new[] { "select top(5) a,b,c from d where a = 1 order by c" };


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


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


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

var pagedSql = new SqlCe40QueryPager().ApplyLimit(sql, 5);
var pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, new string[0], 5, 10); var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant());
var modified = pagedSql.Select(x=> Normalize.Replace(x, " ").ToLowerInvariant());

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

[Test]
[Test] public void ShouldApplyPagingUsingOrderBy()
public void ShouldApplyPagingUsingOrderByFirstColumnIfNotAlreadyOrdered() {
{ var sql = "select a,b,c from d where a = 1 order by c";
var sql = "select a,b,c from d where a = 1"; var expected = new[]{
var expected = new[]{ "select a,b,c from d where a = 1 order by c offset 5 rows fetch next 10 rows only"};
"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], 5, 10);
var pagedSql = new SqlCe40QueryPager().ApplyPaging(sql, new string[0], 10, 20); var modified = pagedSql.Select(x=> Normalize.Replace(x, " ").ToLowerInvariant());
var modified = pagedSql.Select(x => Normalize.Replace(x, " ").ToLowerInvariant());

Assert.IsTrue(expected.SequenceEqual(modified));
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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;


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


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


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


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


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


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


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


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


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


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


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


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


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


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

0 comments on commit 0f9d3c3

Please sign in to comment.