Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
c027ed7
commit 0f9d3c3
Showing
5 changed files
with
280 additions
and
256 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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); | ||
} | } | ||
} | } | ||
} | } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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)); | |||
} | |||
} | |||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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; | ||
} | } | ||
} | } | ||
} | } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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; | ||
} | } | ||
} | } | ||
} | } |
Oops, something went wrong.