forked from nhibernate/nhibernate-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMsSql2005DialectQueryPager.cs
156 lines (136 loc) · 4.31 KB
/
MsSql2005DialectQueryPager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
using System.Text;
using NHibernate.SqlCommand.Parser;
using NHibernate.SqlCommand;
namespace NHibernate.Dialect
{
/// <summary>
/// Transforms a T-SQL SELECT statement into a statement that will - when executed - return a 'page' of results. The page is defined
/// by a page size ('limit'), and/or a starting page number ('offset').
/// </summary>
internal class MsSql2005DialectQueryPager
{
private readonly SqlString _sourceQuery;
public MsSql2005DialectQueryPager(SqlString sourceQuery)
{
_sourceQuery = sourceQuery;
}
/// <summary>
/// Returns a TSQL SELECT statement that will - when executed - return a 'page' of results.
/// </summary>
/// <param name="offset"></param>
/// <param name="limit"></param>
/// <returns></returns>
public SqlString PageBy(SqlString offset, SqlString limit)
{
if (offset == null)
return PageByLimitOnly(limit);
return PageByLimitAndOffset(offset, limit);
}
private SqlString PageByLimitOnly(SqlString limit)
{
using (var tokenEnum = new SqlTokenizer(_sourceQuery).GetEnumerator())
{
if (!tokenEnum.TryParseUntilFirstMsSqlSelectColumn()) return null;
var insertPoint = tokenEnum.Current.SqlIndex;
return _sourceQuery.Insert(insertPoint, new SqlString("TOP (", limit, ") "));
}
}
private SqlString PageByLimitAndOffset(SqlString offset, SqlString limit)
{
var queryParser = new MsSqlSelectParser(_sourceQuery);
if (queryParser.SelectIndex < 0) return null;
var result = new SqlStringBuilder();
BuildSelectClauseForPagingQuery(queryParser, limit, result);
if (queryParser.IsDistinct)
{
BuildFromClauseForPagingDistinctQuery(queryParser, result);
}
else
{
BuildFromClauseForPagingQuery(queryParser, result);
}
BuildWhereAndOrderClausesForPagingQuery(offset, result);
return result.ToSqlString();
}
private static void BuildSelectClauseForPagingQuery(MsSqlSelectParser sqlQuery, SqlString limit, SqlStringBuilder result)
{
result.Add(sqlQuery.Sql.Substring(0, sqlQuery.SelectIndex));
result.Add("SELECT ");
if (limit != null)
{
result.Add("TOP (").Add(limit).Add(") ");
}
else
{
// ORDER BY can only be used in subqueries if TOP is also specified.
result.Add("TOP (" + int.MaxValue + ") ");
}
var sb = new StringBuilder();
foreach (var column in sqlQuery.SelectColumns)
{
if (sb.Length > 0) sb.Append(", ");
sb.Append(column.Alias);
}
result.Add(sb.ToString());
}
private static void BuildFromClauseForPagingQuery(MsSqlSelectParser sqlQuery, SqlStringBuilder result)
{
result.Add(" FROM (")
.Add(sqlQuery.SelectClause)
.Add(", ROW_NUMBER() OVER(ORDER BY ");
var orderIndex = 0;
foreach (var order in sqlQuery.Orders)
{
if (orderIndex++ > 0) result.Add(", ");
if (order.Column.Name != null)
{
result.Add(order.Column.Name);
}
else
{
result.Add(sqlQuery.Sql.Substring(order.Column.SqlIndex, order.Column.SqlLength).Trim());
}
if (order.IsDescending) result.Add(" DESC");
}
if (orderIndex == 0)
{
result.Add("CURRENT_TIMESTAMP");
}
result.Add(") as __hibernate_sort_row ")
.Add(sqlQuery.FromAndWhereClause)
.Add(") as query");
}
private static void BuildFromClauseForPagingDistinctQuery(MsSqlSelectParser sqlQuery, SqlStringBuilder result)
{
result.Add(" FROM (SELECT *, ROW_NUMBER() OVER(ORDER BY ");
int orderIndex = 0;
foreach (var order in sqlQuery.Orders)
{
if (orderIndex++ > 0) result.Add(", ");
if (!order.Column.InSelectClause)
{
throw new HibernateException(
"The dialect was unable to perform paging of a statement that requires distinct results, and " +
"is ordered by a column that is not included in the result set of the query.");
}
result.Add("q_.").Add(order.Column.Alias);
if (order.IsDescending) result.Add(" DESC");
}
if (orderIndex == 0)
{
result.Add("CURRENT_TIMESTAMP");
}
result.Add(") as __hibernate_sort_row FROM (")
.Add(sqlQuery.SelectClause)
.Add(" ")
.Add(sqlQuery.FromAndWhereClause)
.Add(") as q_) as query");
}
private static void BuildWhereAndOrderClausesForPagingQuery(SqlString offset, SqlStringBuilder result)
{
result.Add(" WHERE query.__hibernate_sort_row > ")
.Add(offset)
.Add(" ORDER BY query.__hibernate_sort_row");
}
}
}