You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

374 lines
12 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. #if MYSQL_6_10
  2. // Copyright © 2009, 2016 Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using Externals.MySql.Data.MySqlClient;
  24. using System;
  25. using System.Collections.Generic;
  26. using System.IO;
  27. using System.Linq;
  28. using System.Text;
  29. namespace Externals.MySql.Data.Common
  30. {
  31. internal class QueryNormalizer
  32. {
  33. private static readonly List<string> Keywords = new List<string>();
  34. private readonly List<Token> _tokens = new List<Token>();
  35. private int _pos;
  36. private string _fullSql;
  37. private string _queryType;
  38. static QueryNormalizer()
  39. {
  40. Keywords.AddRange(Resources.Keywords);
  41. }
  42. public string QueryType => _queryType;
  43. public string Normalize(string sql)
  44. {
  45. _tokens.Clear();
  46. StringBuilder newSql = new StringBuilder();
  47. _fullSql = sql;
  48. TokenizeSql(sql);
  49. DetermineStatementType(_tokens);
  50. ProcessMathSymbols(_tokens);
  51. CollapseValueLists(_tokens);
  52. CollapseInLists(_tokens);
  53. CollapseWhitespace(_tokens);
  54. foreach (Token t in _tokens.Where(t => t.Output))
  55. newSql.Append(t.Text);
  56. return newSql.ToString();
  57. }
  58. private void DetermineStatementType(List<Token> tok)
  59. {
  60. foreach (Token t in tok.Where(t => t.Type == TokenType.Keyword))
  61. {
  62. _queryType = t.Text.ToUpperInvariant();
  63. break;
  64. }
  65. }
  66. /// <summary>
  67. /// Mark - or + signs that are unary ops as no output
  68. /// </summary>
  69. /// <param name="tok"></param>
  70. private static void ProcessMathSymbols(List<Token> tok)
  71. {
  72. Token lastToken = null;
  73. foreach (Token t in tok)
  74. {
  75. if (t.Type == TokenType.Symbol &&
  76. (t.Text == "-" || t.Text == "+"))
  77. {
  78. if (lastToken != null &&
  79. lastToken.Type != TokenType.Number &&
  80. lastToken.Type != TokenType.Identifier &&
  81. (lastToken.Type != TokenType.Symbol || lastToken.Text != ")"))
  82. t.Output = false;
  83. }
  84. if (t.IsRealToken)
  85. lastToken = t;
  86. }
  87. }
  88. private static void CollapseWhitespace(List<Token> tok)
  89. {
  90. Token lastToken = null;
  91. foreach (Token t in tok)
  92. {
  93. if (t.Output &&
  94. t.Type == TokenType.Whitespace &&
  95. lastToken != null &&
  96. lastToken.Type == TokenType.Whitespace)
  97. {
  98. t.Output = false;
  99. }
  100. if (t.Output)
  101. lastToken = t;
  102. }
  103. }
  104. private void CollapseValueLists(List<Token> tok)
  105. {
  106. int pos = -1;
  107. while (++pos < tok.Count)
  108. {
  109. Token t = tok[pos];
  110. if (t.Type != TokenType.Keyword) continue;
  111. if (!t.Text.StartsWith("VALUE", StringComparison.OrdinalIgnoreCase)) continue;
  112. CollapseValueList(tok, ref pos);
  113. }
  114. }
  115. private void CollapseValueList(List<Token> tok, ref int pos)
  116. {
  117. List<int> parenIndices = new List<int>();
  118. // this while loop will find all closing parens in this value list
  119. while (true)
  120. {
  121. // find the close ')'
  122. while (++pos < tok.Count)
  123. {
  124. if (tok[pos].Type == TokenType.Symbol && tok[pos].Text == ")")
  125. break;
  126. if (pos == tok.Count - 1)
  127. break;
  128. }
  129. parenIndices.Add(pos);
  130. // now find the next "real" token
  131. while (++pos < tok.Count)
  132. if (tok[pos].IsRealToken) break;
  133. if (pos == tok.Count) break;
  134. if (tok[pos].Text == ",") continue;
  135. pos--;
  136. break;
  137. }
  138. // if we only have 1 value then we don't collapse
  139. if (parenIndices.Count < 2) return;
  140. int index = parenIndices[0];
  141. tok[++index] = new Token(TokenType.Whitespace, " ");
  142. tok[++index] = new Token(TokenType.Comment, "/* , ... */");
  143. index++;
  144. // now mark all the other tokens as no output
  145. while (index <= parenIndices[parenIndices.Count - 1])
  146. tok[index++].Output = false;
  147. }
  148. private void CollapseInLists(List<Token> tok)
  149. {
  150. int pos = -1;
  151. while (++pos < tok.Count)
  152. {
  153. Token t = tok[pos];
  154. if (t.Type != TokenType.Keyword) continue;
  155. if (t.Text != "IN") continue;
  156. CollapseInList(tok, ref pos);
  157. }
  158. }
  159. private static Token GetNextRealToken(List<Token> tok, ref int pos)
  160. {
  161. while (++pos < tok.Count)
  162. {
  163. if (tok[pos].IsRealToken) return tok[pos];
  164. }
  165. return null;
  166. }
  167. private static void CollapseInList(List<Token> tok, ref int pos)
  168. {
  169. Token t = GetNextRealToken(tok, ref pos);
  170. // Debug.Assert(t.Text == "(");
  171. if (t == null)
  172. return;
  173. // if the first token is a keyword then we likely have a
  174. // SELECT .. IN (SELECT ...)
  175. t = GetNextRealToken(tok, ref pos);
  176. if (t == null || t.Type == TokenType.Keyword) return;
  177. int start = pos;
  178. // first find all the tokens that make up the in list
  179. while (++pos < tok.Count)
  180. {
  181. t = tok[pos];
  182. if (t.Type == TokenType.CommandComment) return;
  183. if (!t.IsRealToken) continue;
  184. if (t.Text == "(") return;
  185. if (t.Text == ")") break;
  186. }
  187. int stop = pos;
  188. for (int i = stop; i > start; i--)
  189. tok.RemoveAt(i);
  190. tok.Insert(++start, new Token(TokenType.Whitespace, " "));
  191. tok.Insert(++start, new Token(TokenType.Comment, "/* , ... */"));
  192. tok.Insert(++start, new Token(TokenType.Whitespace, " "));
  193. tok.Insert(++start, new Token(TokenType.Symbol, ")"));
  194. }
  195. private void TokenizeSql(string sql)
  196. {
  197. _pos = 0;
  198. while (_pos < sql.Length)
  199. {
  200. char c = sql[_pos];
  201. if (LetterStartsComment(c) && ConsumeComment())
  202. continue;
  203. if (Char.IsWhiteSpace(c))
  204. ConsumeWhitespace();
  205. else if (c == '\'' || c == '\"' || c == '`')
  206. ConsumeQuotedToken(c);
  207. else if (!IsSpecialCharacter(c))
  208. ConsumeUnquotedToken();
  209. else
  210. ConsumeSymbol();
  211. }
  212. }
  213. private bool LetterStartsComment(char c)
  214. {
  215. return c == '#' || c == '/' || c == '-';
  216. }
  217. private bool IsSpecialCharacter(char c)
  218. {
  219. return !Char.IsLetterOrDigit(c) && c != '$' && c != '_' && c != '.';
  220. }
  221. private bool ConsumeComment()
  222. {
  223. char c = _fullSql[_pos];
  224. // make sure the comment starts correctly
  225. if (c == '/' && ((_pos + 1) >= _fullSql.Length || _fullSql[_pos + 1] != '*')) return false;
  226. if (c == '-' && ((_pos + 2) >= _fullSql.Length || _fullSql[_pos + 1] != '-' || _fullSql[_pos + 2] != ' ')) return false;
  227. string endingPattern = "\n";
  228. if (c == '/')
  229. endingPattern = "*/";
  230. int startingIndex = _pos;
  231. int index = _fullSql.IndexOf(endingPattern, _pos);
  232. if (index == -1)
  233. index = _fullSql.Length - 1;
  234. else
  235. index += endingPattern.Length;
  236. string comment = _fullSql.Substring(_pos, index - _pos);
  237. if (comment.StartsWith("/*!", StringComparison.Ordinal))
  238. _tokens.Add(new Token(TokenType.CommandComment, comment));
  239. _pos = index;
  240. return true;
  241. }
  242. private void ConsumeSymbol()
  243. {
  244. char c = _fullSql[_pos++];
  245. _tokens.Add(new Token(TokenType.Symbol, c.ToString()));
  246. }
  247. private void ConsumeQuotedToken(char c)
  248. {
  249. bool escaped = false;
  250. int start = _pos;
  251. _pos++;
  252. while (_pos < _fullSql.Length)
  253. {
  254. char x = _fullSql[_pos];
  255. if (x == c && !escaped) break;
  256. if (escaped)
  257. escaped = false;
  258. else if (x == '\\')
  259. escaped = true;
  260. _pos++;
  261. }
  262. _pos++;
  263. _tokens.Add(c == '\''
  264. ? new Token(TokenType.String, "?")
  265. : new Token(TokenType.Identifier, _fullSql.Substring(start, _pos - start)));
  266. }
  267. private void ConsumeUnquotedToken()
  268. {
  269. int startPos = _pos;
  270. while (_pos < _fullSql.Length && !IsSpecialCharacter(_fullSql[_pos]))
  271. _pos++;
  272. string word = _fullSql.Substring(startPos, _pos - startPos);
  273. double v;
  274. if (Double.TryParse(word, out v))
  275. _tokens.Add(new Token(TokenType.Number, "?"));
  276. else
  277. {
  278. Token t = new Token(TokenType.Identifier, word);
  279. if (IsKeyword(word))
  280. {
  281. t.Type = TokenType.Keyword;
  282. t.Text = t.Text.ToUpperInvariant();
  283. }
  284. _tokens.Add(t);
  285. }
  286. }
  287. private void ConsumeWhitespace()
  288. {
  289. _tokens.Add(new Token(TokenType.Whitespace, " "));
  290. while (_pos < _fullSql.Length && Char.IsWhiteSpace(_fullSql[_pos]))
  291. _pos++;
  292. }
  293. private static bool IsKeyword(string word)
  294. {
  295. return Keywords.Contains(word.ToUpperInvariant());
  296. }
  297. }
  298. internal class Token
  299. {
  300. public TokenType Type;
  301. public string Text;
  302. public bool Output;
  303. public Token(TokenType type, string text)
  304. {
  305. Type = type;
  306. Text = text;
  307. Output = true;
  308. }
  309. public bool IsRealToken => Type != TokenType.Comment &&
  310. Type != TokenType.CommandComment &&
  311. Type != TokenType.Whitespace &&
  312. Output;
  313. }
  314. internal enum TokenType
  315. {
  316. Keyword,
  317. String,
  318. Number,
  319. Symbol,
  320. Identifier,
  321. Comment,
  322. CommandComment,
  323. Whitespace
  324. }
  325. }
  326. #endif