/src/NHibernate/SqlCommand/Parser/MsSqlSelectParser.cs

https://github.com/whut/nhibernate-core · C# · 378 lines · 301 code · 60 blank · 17 comment · 59 complexity · e6a6bc12ddcaae6b41bacd9ec3790513 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. namespace NHibernate.SqlCommand.Parser
  4. {
  5. using System.Linq;
  6. /// <summary>
  7. /// Represents SQL Server SELECT query parser, primarily intended to support generation of
  8. /// limit queries by SQL Server dialects.
  9. /// </summary>
  10. internal class MsSqlSelectParser
  11. {
  12. private readonly List<ColumnDefinition> _columns = new List<ColumnDefinition>();
  13. private readonly List<OrderDefinition> _orders = new List<OrderDefinition>();
  14. private int _nextOrderAliasIndex;
  15. public MsSqlSelectParser(SqlString sql)
  16. {
  17. if (sql == null) throw new ArgumentNullException("sql");
  18. this.Sql = sql;
  19. this.SelectIndex = this.FromIndex = this.OrderByIndex = -1;
  20. var tokenEnum = new SqlTokenizer(sql).GetEnumerator();
  21. tokenEnum.MoveNext();
  22. // Custom SQL may contain multiple SELECT statements, for example to assign parameters.
  23. // Therefore we loop over SELECT statements until a SELECT is found that returns data.
  24. SqlToken selectToken;
  25. bool isDistinct;
  26. if (tokenEnum.TryParseUntilFirstMsSqlSelectColumn(out selectToken, out isDistinct))
  27. {
  28. this.SelectIndex = selectToken.SqlIndex;
  29. this.IsDistinct = isDistinct;
  30. _columns.AddRange(ParseColumnDefinitions(tokenEnum));
  31. if (tokenEnum.TryParseUntil("from"))
  32. {
  33. this.FromIndex = tokenEnum.Current.SqlIndex;
  34. SqlToken orderToken;
  35. if (tokenEnum.TryParseUntilFirstOrderColumn(out orderToken))
  36. {
  37. this.OrderByIndex = orderToken.SqlIndex;
  38. foreach (var order in ParseOrderDefinitions(tokenEnum))
  39. {
  40. _orders.Add(order);
  41. if (!order.Column.InSelectClause)
  42. {
  43. _columns.Add(order.Column);
  44. }
  45. }
  46. }
  47. }
  48. return;
  49. }
  50. }
  51. public SqlString Sql { get; private set; }
  52. public int SelectIndex { get; private set; }
  53. public int FromIndex { get; private set; }
  54. public int OrderByIndex { get; private set; }
  55. public bool IsDistinct { get; private set; }
  56. /// <summary>
  57. /// Column definitions in SELECT clause
  58. /// </summary>
  59. public IEnumerable<ColumnDefinition> SelectColumns
  60. {
  61. get { return _columns.Where(c => c.InSelectClause); }
  62. }
  63. /// <summary>
  64. /// Column definitions for columns that appear in ORDER BY clause
  65. /// but do not appear in SELECT clause.
  66. /// </summary>
  67. public IEnumerable<ColumnDefinition> NonSelectColumns
  68. {
  69. get { return _columns.Where(c => !c.InSelectClause); }
  70. }
  71. /// <summary>
  72. /// Sort orders as defined in ORDER BY clause
  73. /// </summary>
  74. public IEnumerable<OrderDefinition> Orders
  75. {
  76. get { return _orders; }
  77. }
  78. public SqlString SelectClause
  79. {
  80. get { return this.Sql.Substring(this.SelectIndex, this.FromIndex - this.SelectIndex).Trim(); }
  81. }
  82. public SqlString FromAndWhereClause
  83. {
  84. get
  85. {
  86. return this.OrderByIndex >= 0
  87. ? this.Sql.Substring(this.FromIndex, this.OrderByIndex - this.FromIndex).Trim()
  88. : this.Sql.Substring(this.FromIndex).Trim();
  89. }
  90. }
  91. public SqlString ColumnExpression(ColumnDefinition column)
  92. {
  93. return this.Sql.Substring(column.SqlIndex, column.SqlLength);
  94. }
  95. private IEnumerable<ColumnDefinition> ParseColumnDefinitions(IEnumerator<SqlToken> tokenEnum)
  96. {
  97. int blockLevel = 0;
  98. SqlToken columnBeginToken = null;
  99. SqlToken columnEndToken = null;
  100. SqlToken columnAliasToken = null;
  101. SqlToken prevToken = null;
  102. do
  103. {
  104. var token = tokenEnum.Current;
  105. if (token == null) break;
  106. columnBeginToken = columnBeginToken ?? token;
  107. switch (token.TokenType)
  108. {
  109. case SqlTokenType.BracketOpen:
  110. blockLevel++;
  111. break;
  112. case SqlTokenType.BracketClose:
  113. blockLevel--;
  114. break;
  115. case SqlTokenType.Text:
  116. if (blockLevel != 0) break;
  117. if (token.Equals(",", StringComparison.InvariantCultureIgnoreCase))
  118. {
  119. if (columnAliasToken != null)
  120. {
  121. yield return ParseSelectColumnDefinition(columnBeginToken, columnEndToken ?? columnAliasToken, columnAliasToken);
  122. }
  123. }
  124. if (token.Equals("from", StringComparison.InvariantCultureIgnoreCase))
  125. {
  126. if (columnAliasToken != null)
  127. {
  128. yield return ParseSelectColumnDefinition(columnBeginToken, columnEndToken ?? columnAliasToken, columnAliasToken);
  129. }
  130. yield break;
  131. }
  132. if (token.Equals("as", StringComparison.InvariantCultureIgnoreCase))
  133. {
  134. columnEndToken = prevToken;
  135. }
  136. columnAliasToken = token;
  137. break;
  138. case SqlTokenType.DelimitedText:
  139. if (blockLevel != 0) break;
  140. columnAliasToken = token;
  141. break;
  142. case SqlTokenType.Comma:
  143. if (blockLevel != 0) break;
  144. if (columnAliasToken != null)
  145. {
  146. yield return ParseSelectColumnDefinition(columnBeginToken, columnEndToken ?? columnAliasToken, columnAliasToken);
  147. }
  148. columnBeginToken = columnEndToken = columnAliasToken = null;
  149. break;
  150. }
  151. prevToken = token;
  152. } while (tokenEnum.MoveNext());
  153. if (columnAliasToken != null)
  154. {
  155. yield return ParseSelectColumnDefinition(columnBeginToken, columnEndToken ?? columnAliasToken, columnAliasToken);
  156. }
  157. }
  158. private ColumnDefinition ParseSelectColumnDefinition(SqlToken beginToken, SqlToken endToken, SqlToken aliasToken)
  159. {
  160. var name = beginToken == endToken
  161. ? beginToken.Value
  162. : null;
  163. var alias = aliasToken.Value;
  164. var dotIndex = alias.LastIndexOf('.');
  165. alias = dotIndex >= 0
  166. ? alias.Substring(dotIndex + 1)
  167. : alias;
  168. var sqlIndex = beginToken.SqlIndex;
  169. var sqlLength = (endToken != null ? endToken.SqlIndex + endToken.Length : Sql.Length) - beginToken.SqlIndex;
  170. return new ColumnDefinition(sqlIndex, sqlLength, name, alias, true);
  171. }
  172. private ColumnDefinition ParseOrderColumnDefinition(SqlToken beginToken, SqlToken endToken, string alias)
  173. {
  174. var sqlIndex = beginToken.SqlIndex;
  175. var sqlLength = (endToken != null ? endToken.SqlIndex + endToken.Length : Sql.Length) - beginToken.SqlIndex;
  176. return new ColumnDefinition(sqlIndex, sqlLength, null, alias, false);
  177. }
  178. private IEnumerable<OrderDefinition> ParseOrderDefinitions(IEnumerator<SqlToken> tokenEnum)
  179. {
  180. int blockLevel = 0;
  181. SqlToken orderBeginToken = null;
  182. SqlToken orderEndToken = null;
  183. SqlToken directionToken = null;
  184. SqlToken prevToken = null;
  185. do
  186. {
  187. var token = tokenEnum.Current;
  188. if (token == null) break;
  189. if (token.TokenType == SqlTokenType.Whitespace) continue;
  190. orderBeginToken = orderBeginToken ?? token;
  191. switch (token.TokenType)
  192. {
  193. case SqlTokenType.BracketOpen:
  194. blockLevel++;
  195. break;
  196. case SqlTokenType.BracketClose:
  197. blockLevel--;
  198. break;
  199. case SqlTokenType.Text:
  200. if (blockLevel != 0) break;
  201. if (token.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
  202. || token.Equals("desc", StringComparison.InvariantCultureIgnoreCase))
  203. {
  204. orderEndToken = prevToken;
  205. directionToken = token;
  206. }
  207. break;
  208. case SqlTokenType.DelimitedText:
  209. if (blockLevel != 0) break;
  210. break;
  211. case SqlTokenType.Comma:
  212. if (blockLevel != 0) break;
  213. yield return ParseOrderDefinition(orderBeginToken, orderEndToken ?? prevToken, directionToken);
  214. orderBeginToken = orderEndToken = directionToken = null;
  215. break;
  216. }
  217. prevToken = token;
  218. } while (tokenEnum.MoveNext());
  219. if (orderBeginToken != null)
  220. {
  221. yield return ParseOrderDefinition(orderBeginToken, orderEndToken ?? prevToken, directionToken);
  222. }
  223. }
  224. private OrderDefinition ParseOrderDefinition(SqlToken beginToken, SqlToken endToken, SqlToken directionToken)
  225. {
  226. var isDescending = directionToken != null &&
  227. directionToken.Equals("desc", StringComparison.InvariantCultureIgnoreCase);
  228. var columnNameOrIndex = beginToken == endToken
  229. ? beginToken.Value
  230. : null;
  231. ColumnDefinition column;
  232. if (!TryGetColumnDefinition(columnNameOrIndex, out column, beginToken, endToken))
  233. {
  234. // Column appears in order by clause, but not in select clause
  235. column = ParseOrderColumnDefinition(beginToken, endToken, "__c" + _nextOrderAliasIndex++);
  236. }
  237. return new OrderDefinition(column, isDescending);
  238. }
  239. private bool TryGetColumnDefinition(string columnNameOrIndex, out ColumnDefinition result, SqlToken beginToken, SqlToken endToken)
  240. {
  241. if (!string.IsNullOrEmpty(columnNameOrIndex))
  242. {
  243. int columnIndex;
  244. if (int.TryParse(columnNameOrIndex, out columnIndex) && columnIndex <= _columns.Count)
  245. {
  246. result = _columns[columnIndex - 1];
  247. return true;
  248. }
  249. foreach (var column in _columns)
  250. {
  251. if (columnNameOrIndex.Equals(column.Name, StringComparison.InvariantCultureIgnoreCase)
  252. || columnNameOrIndex.Equals(column.Alias, StringComparison.InvariantCultureIgnoreCase))
  253. {
  254. result = column;
  255. return true;
  256. }
  257. }
  258. }
  259. else
  260. {
  261. var sqlIndex = beginToken.SqlIndex;
  262. var sqlLength = (endToken != null ? endToken.SqlIndex + endToken.Length : Sql.Length) - beginToken.SqlIndex;
  263. var text = Sql.ToString(sqlIndex, sqlLength);
  264. foreach (var column in _columns)
  265. {
  266. if (text.Equals(column.Name, StringComparison.InvariantCultureIgnoreCase) ||
  267. text.Equals(column.Alias, StringComparison.InvariantCultureIgnoreCase) ||
  268. text.Equals(Sql.ToString(column.SqlIndex, column.SqlLength), StringComparison.InvariantCultureIgnoreCase))
  269. {
  270. result = column;
  271. return true;
  272. }
  273. }
  274. }
  275. result = null;
  276. return false;
  277. }
  278. public class ColumnDefinition
  279. {
  280. public string Name { get; private set; }
  281. public string Alias { get; private set; }
  282. public int SqlIndex { get; private set; }
  283. public int SqlLength { get; private set; }
  284. public bool InSelectClause { get; private set; }
  285. internal ColumnDefinition(int sqlIndex, int sqlLength, string name, string alias, bool inSelectClause)
  286. {
  287. this.SqlIndex = sqlIndex;
  288. this.SqlLength = sqlLength;
  289. this.Name = name;
  290. this.Alias = alias;
  291. this.InSelectClause = inSelectClause;
  292. }
  293. public override string ToString()
  294. {
  295. if (this.Name == null) return this.Alias;
  296. return this.Name != this.Alias
  297. ? this.Name + " AS " + this.Alias
  298. : this.Name;
  299. }
  300. }
  301. public class OrderDefinition
  302. {
  303. public ColumnDefinition Column { get; private set; }
  304. public bool IsDescending { get; private set; }
  305. internal OrderDefinition(ColumnDefinition column, bool isDescending)
  306. {
  307. this.Column = column;
  308. this.IsDescending = isDescending;
  309. }
  310. public override string ToString()
  311. {
  312. return this.IsDescending
  313. ? this.Column + " DESC"
  314. : this.Column.ToString();
  315. }
  316. }
  317. }
  318. }