Skip to content

Commit 583f42c

Browse files
ggeurtsoskarb
authored andcommitted
Paging for MsSql2012 changed to the new recommended standard syntax and made MsSql20xxDialect.GetLimitString() implementations more consistent.
1 parent b03cf6a commit 583f42c

File tree

6 files changed

+209
-40
lines changed

6 files changed

+209
-40
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using NHibernate.Dialect;
2+
using NHibernate.SqlCommand;
3+
using NUnit.Framework;
4+
5+
namespace NHibernate.Test.DialectTest
6+
{
7+
[TestFixture]
8+
public class MsSql2012DialectFixture
9+
{
10+
[Test]
11+
public void GetLimitString()
12+
{
13+
var d = new MsSql2012Dialect();
14+
15+
SqlString str = d.GetLimitString(new SqlString("select distinct c.Contact_Id as Contact1_19_0_, c.Rating as Rating2_19_0_, c.Last_Name as Last_Name3_19_0, c.First_Name as First_Name4_19_0 from dbo.Contact c where COALESCE(c.Rating, 0) > 0 order by c.Rating desc , c.Last_Name , c.First_Name"), new SqlString("111"), new SqlString("222"));
16+
Assert.AreEqual(
17+
"select distinct c.Contact_Id as Contact1_19_0_, c.Rating as Rating2_19_0_, c.Last_Name as Last_Name3_19_0, c.First_Name as First_Name4_19_0 from dbo.Contact c where COALESCE(c.Rating, 0) > 0 order by c.Rating desc , c.Last_Name , c.First_Name OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
18+
str.ToString());
19+
20+
str = d.GetLimitString(new SqlString("SELECT fish.id FROM fish"), new SqlString("111"), new SqlString("222"));
21+
Assert.AreEqual(
22+
"SELECT fish.id FROM fish ORDER BY CURRENT_TIMESTAMP OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
23+
str.ToString());
24+
25+
str = d.GetLimitString(new SqlString("SELECT DISTINCT fish_.id FROM fish fish_"), new SqlString("111"), new SqlString("222"));
26+
Assert.AreEqual(
27+
"SELECT DISTINCT fish_.id FROM fish fish_ ORDER BY CURRENT_TIMESTAMP OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
28+
str.ToString());
29+
30+
str = d.GetLimitString(new SqlString("SELECT DISTINCT fish_.id as ixx9_ FROM fish fish_"), new SqlString("111"), new SqlString("222"));
31+
Assert.AreEqual(
32+
"SELECT DISTINCT fish_.id as ixx9_ FROM fish fish_ ORDER BY CURRENT_TIMESTAMP OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
33+
str.ToString());
34+
35+
str = d.GetLimitString(new SqlString("SELECT * FROM fish ORDER BY name"), new SqlString("111"), new SqlString("222"));
36+
Assert.AreEqual(
37+
"SELECT * FROM fish ORDER BY name OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
38+
str.ToString());
39+
40+
str = d.GetLimitString(new SqlString("SELECT fish.id, fish.name FROM fish ORDER BY name DESC"), new SqlString("111"), new SqlString("222"));
41+
Assert.AreEqual(
42+
"SELECT fish.id, fish.name FROM fish ORDER BY name DESC OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
43+
str.ToString());
44+
45+
str = d.GetLimitString(new SqlString("SELECT * FROM fish LEFT JOIN (SELECT * FROM meat ORDER BY weight) AS t ORDER BY name DESC"), new SqlString("111"), new SqlString("222"));
46+
Assert.AreEqual(
47+
"SELECT * FROM fish LEFT JOIN (SELECT * FROM meat ORDER BY weight) AS t ORDER BY name DESC OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
48+
str.ToString());
49+
50+
str = d.GetLimitString(new SqlString("SELECT *, (SELECT COUNT(1) FROM fowl WHERE fish_id = fish.id) AS some_count FROM fish"), new SqlString("111"), new SqlString("222"));
51+
Assert.AreEqual(
52+
"SELECT *, (SELECT COUNT(1) FROM fowl WHERE fish_id = fish.id) AS some_count FROM fish ORDER BY CURRENT_TIMESTAMP OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
53+
str.ToString());
54+
55+
str = d.GetLimitString(new SqlString("SELECT * FROM fish WHERE scales = ", Parameter.Placeholder), new SqlString("111"), new SqlString("222"));
56+
Assert.AreEqual(
57+
"SELECT * FROM fish WHERE scales = ? ORDER BY CURRENT_TIMESTAMP OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
58+
str.ToString());
59+
60+
str = d.GetLimitString(new SqlString("SELECT f.Type, COUNT(DISTINCT f.Name) AS Name FROM Fish f GROUP BY f.Type ORDER BY COUNT(DISTINCT f.Name)"), new SqlString("111"), new SqlString("222"));
61+
Assert.AreEqual(
62+
"SELECT f.Type, COUNT(DISTINCT f.Name) AS Name FROM Fish f GROUP BY f.Type ORDER BY COUNT(DISTINCT f.Name) OFFSET 111 ROWS FETCH FIRST 222 ROWS ONLY",
63+
str.ToString());
64+
}
65+
66+
[Test]
67+
public void OnlyOffsetLimit()
68+
{
69+
var d = new MsSql2012Dialect();
70+
71+
SqlString str = d.GetLimitString(new SqlString("select distinct c.Contact_Id as Contact1_19_0_, c._Rating as Rating2_19_0_ from dbo.Contact c where COALESCE(c.Rating, 0) > 0 order by c.Rating desc , c.Last_Name , c.First_Name"), null, new SqlString("10"));
72+
Assert.That(str.ToString(), Is.EqualTo("select distinct c.Contact_Id as Contact1_19_0_, c._Rating as Rating2_19_0_ from dbo.Contact c where COALESCE(c.Rating, 0) > 0 order by c.Rating desc , c.Last_Name , c.First_Name OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY"));
73+
}
74+
75+
[Test]
76+
public void GetLimitStringWithSqlComments()
77+
{
78+
var d = new MsSql2012Dialect();
79+
var limitSqlQuery = d.GetLimitString(new SqlString(" /* criteria query */ SELECT p from lcdtm"), null, new SqlString("2"));
80+
Assert.That(limitSqlQuery, Is.Not.Null);
81+
Assert.That(limitSqlQuery.ToString(), Is.EqualTo(" /* criteria query */ SELECT p from lcdtm ORDER BY CURRENT_TIMESTAMP OFFSET 0 ROWS FETCH FIRST 2 ROWS ONLY"));
82+
}
83+
84+
[Test]
85+
public void GetLimitStringWithSqlCommonTableExpression()
86+
{
87+
const string SQL = @"
88+
WITH DirectReports (ManagerID, EmployeeID, Title, DeptID, Level)
89+
( -- Anchor member definition
90+
SELECT ManagerID, EmployeeID, Title, Deptid, 0 AS Level
91+
FROM MyEmployees
92+
WHERE ManagerID IS NULL
93+
94+
UNION ALL
95+
96+
-- Recursive member definition
97+
SELECT e.ManagerID, e.EmployeeID, e.Title, e.Deptid, Level + 1
98+
FROM MyEmployees AS e
99+
INNER JOIN DirectReports AS ON e.ManagerID = d.EmployeeID
100+
)
101+
-- Statement that executes the CTE
102+
SELECT ManagerID, EmployeeID, Title, Level
103+
FROM DirectReports";
104+
105+
const string EXPECTED_SQL = @"
106+
WITH DirectReports (ManagerID, EmployeeID, Title, DeptID, Level)
107+
( -- Anchor member definition
108+
SELECT ManagerID, EmployeeID, Title, Deptid, 0 AS Level
109+
FROM MyEmployees
110+
WHERE ManagerID IS NULL
111+
112+
UNION ALL
113+
114+
-- Recursive member definition
115+
SELECT e.ManagerID, e.EmployeeID, e.Title, e.Deptid, Level + 1
116+
FROM MyEmployees AS e
117+
INNER JOIN DirectReports AS ON e.ManagerID = d.EmployeeID
118+
)
119+
-- Statement that executes the CTE
120+
SELECT ManagerID, EmployeeID, Title, Level
121+
FROM DirectReports ORDER BY CURRENT_TIMESTAMP OFFSET 0 ROWS FETCH FIRST 2 ROWS ONLY";
122+
123+
var d = new MsSql2012Dialect();
124+
var limitSqlQuery = d.GetLimitString(new SqlString(SQL), null, new SqlString("2"));
125+
Assert.That(limitSqlQuery, Is.Not.Null);
126+
Assert.That(limitSqlQuery.ToString(), Is.EqualTo(EXPECTED_SQL));
127+
}
128+
129+
[Test]
130+
public void DontReturnLimitStringForStoredProcedureCall()
131+
{
132+
VerifyLimitStringForStoredProcedureCalls("EXEC sp_stored_procedures");
133+
VerifyLimitStringForStoredProcedureCalls(@"
134+
DECLARE @id int
135+
SELECT @id = id FROM persons WHERE name LIKE ?
136+
EXEC get_person_summary @id");
137+
VerifyLimitStringForStoredProcedureCalls(@"
138+
DECLARE @id int
139+
SELECT DISTINCT TOP 1 @id = id FROM persons WHERE name LIKE ?
140+
EXEC get_person_summary @id");
141+
VerifyLimitStringForStoredProcedureCalls(@"
142+
DECLARE @id int
143+
SELECT DISTINCT TOP (?) PERCENT WITH TIES @id = id FROM persons WHERE name LIKE ?
144+
EXEC get_person_summary @id");
145+
}
146+
147+
private static void VerifyLimitStringForStoredProcedureCalls(string sql)
148+
{
149+
var d = new MsSql2012Dialect();
150+
var limitSql = d.GetLimitString(new SqlString(sql), null, new SqlString("2"));
151+
Assert.That(limitSql, Is.Null, "Limit only: {0}", sql);
152+
153+
limitSql = d.GetLimitString(new SqlString(sql), new SqlString("10"), null);
154+
Assert.That(limitSql, Is.Null, "Offset only: {0}", sql);
155+
156+
limitSql = d.GetLimitString(new SqlString(sql), new SqlString("10"), new SqlString("2"));
157+
Assert.That(limitSql, Is.Null, "Limit and Offset: {0}", sql);
158+
}
159+
}
160+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@
219219
<Compile Include="DialectTest\FunctionTests\SubstringSupportFixture.cs" />
220220
<Compile Include="DialectTest\FunctionTests\SequenceSupportFixture.cs" />
221221
<Compile Include="DialectTest\LockHintAppenderFixture.cs" />
222+
<Compile Include="DialectTest\MsSql2012DialectFixture.cs" />
222223
<Compile Include="DialectTest\MsSqlCe40DialectFixture.cs" />
223224
<Compile Include="DialectTest\SchemaTests\ColumnMetaDataFixture.cs" />
224225
<Compile Include="DriverTest\DbProviderFactoryDriveConnectionCommandProviderTest.cs" />

src/NHibernate/Dialect/MsSql2000Dialect.cs

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -341,26 +341,11 @@ public override bool SupportsVariableLimit
341341

342342
public override SqlString GetLimitString(SqlString querySqlString, SqlString offset, SqlString limit)
343343
{
344-
int insertPoint;
345-
return TryFindLimitInsertPoint(querySqlString, out insertPoint)
346-
? querySqlString.Insert(insertPoint, new SqlString("top ", limit, " "))
347-
: null;
348-
}
349-
350-
protected static bool TryFindLimitInsertPoint(SqlString sql, out int result)
351-
{
352-
var tokenEnum = new SqlTokenizer(sql).GetEnumerator();
353-
354-
SqlToken selectToken;
355-
bool isDistinct;
356-
if (tokenEnum.TryParseUntilFirstMsSqlSelectColumn(out selectToken, out isDistinct))
357-
{
358-
result = tokenEnum.Current.SqlIndex;
359-
return true;
360-
}
344+
var tokenEnum = new SqlTokenizer(querySqlString).GetEnumerator();
345+
if (!tokenEnum.TryParseUntilFirstMsSqlSelectColumn()) return null;
361346

362-
result = -1;
363-
return false;
347+
int insertPoint = tokenEnum.Current.SqlIndex;
348+
return querySqlString.Insert(insertPoint, new SqlString("top ", limit, " "));
364349
}
365350

366351
/// <summary>

src/NHibernate/Dialect/MsSql2005DialectQueryPager.cs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ public SqlString PageBy(SqlString offset, SqlString limit)
3333

3434
private SqlString PageByLimitOnly(SqlString limit)
3535
{
36-
int insertPoint;
37-
return TryFindLimitInsertPoint(_sourceQuery, out insertPoint)
38-
? _sourceQuery.Insert(insertPoint, new SqlString("TOP (", limit, ") "))
39-
: null;
36+
var tokenEnum = new SqlTokenizer(_sourceQuery).GetEnumerator();
37+
if (!tokenEnum.TryParseUntilFirstMsSqlSelectColumn()) return null;
38+
39+
int insertPoint = tokenEnum.Current.SqlIndex;
40+
return _sourceQuery.Insert(insertPoint, new SqlString("TOP (", limit, ") "));
4041
}
4142

4243
private SqlString PageByLimitAndOffset(SqlString offset, SqlString limit)
@@ -58,22 +59,6 @@ private SqlString PageByLimitAndOffset(SqlString offset, SqlString limit)
5859
return result.ToSqlString();
5960
}
6061

61-
protected static bool TryFindLimitInsertPoint(SqlString sql, out int result)
62-
{
63-
var tokenEnum = new SqlTokenizer(sql).GetEnumerator();
64-
65-
SqlToken selectToken;
66-
bool isDistinct;
67-
if (tokenEnum.TryParseUntilFirstMsSqlSelectColumn(out selectToken, out isDistinct))
68-
{
69-
result = tokenEnum.Current.SqlIndex;
70-
return true;
71-
}
72-
73-
result = -1;
74-
return false;
75-
}
76-
7762
private static void BuildSelectClauseForPagingQuery(MsSqlSelectParser sqlQuery, SqlString limit, SqlStringBuilder result)
7863
{
7964
result.Add(sqlQuery.Sql.Substring(0, sqlQuery.SelectIndex));

src/NHibernate/Dialect/MsSql2012Dialect.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using NHibernate.Dialect.Function;
1+
using NHibernate.Dialect.Function;
2+
using NHibernate.SqlCommand;
3+
using NHibernate.SqlCommand.Parser;
24

35
namespace NHibernate.Dialect
46
{
@@ -53,5 +55,34 @@ protected override void RegisterFunctions()
5355
base.RegisterFunctions();
5456
RegisterFunction("iif", new StandardSafeSQLFunction("iif", 3));
5557
}
58+
59+
public override SqlString GetLimitString(SqlString querySqlString, SqlString offset, SqlString limit)
60+
{
61+
var tokenEnum = new SqlTokenizer(querySqlString).GetEnumerator();
62+
if (!tokenEnum.TryParseUntilFirstMsSqlSelectColumn()) return null;
63+
64+
var result = new SqlStringBuilder(querySqlString);
65+
if (!tokenEnum.TryParseUntil("order"))
66+
{
67+
result.Add(" ORDER BY CURRENT_TIMESTAMP");
68+
}
69+
70+
result.Add(" OFFSET ");
71+
if (offset != null)
72+
{
73+
result.Add(offset).Add(" ROWS");
74+
}
75+
else
76+
{
77+
result.Add("0 ROWS");
78+
}
79+
80+
if (limit != null)
81+
{
82+
result.Add(" FETCH FIRST ").Add(limit).Add(" ROWS ONLY");
83+
}
84+
85+
return result.ToSqlString();
86+
}
5687
}
5788
}

src/NHibernate/SqlCommand/Parser/SqlTokenizerExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ public static bool TryParseUntil(this IEnumerator<SqlToken> tokenEnum, string ke
3131
return false;
3232
}
3333

34+
public static bool TryParseUntilFirstMsSqlSelectColumn(this IEnumerator<SqlToken> tokenEnum)
35+
{
36+
SqlToken selectToken;
37+
bool isDistinct;
38+
return TryParseUntilFirstMsSqlSelectColumn(tokenEnum, out selectToken, out isDistinct);
39+
}
40+
3441
public static bool TryParseUntilFirstMsSqlSelectColumn(this IEnumerator<SqlToken> tokenEnum, out SqlToken selectToken, out bool isDistinct)
3542
{
3643
selectToken = null;

0 commit comments

Comments
 (0)