sql-hint.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: http://codemirror.net/LICENSE
  3. (function(mod) {
  4. if (typeof exports == "object" && typeof module == "object") // CommonJS
  5. mod(require("../../lib/codemirror"), require("../../mode/sql/sql"));
  6. else if (typeof define == "function" && define.amd) // AMD
  7. define(["../../lib/codemirror", "../../mode/sql/sql"], mod);
  8. else // Plain browser env
  9. mod(CodeMirror);
  10. })(function(CodeMirror) {
  11. "use strict";
  12. var tables;
  13. var defaultTable;
  14. var keywords;
  15. var CONS = {
  16. QUERY_DIV: ";",
  17. ALIAS_KEYWORD: "AS"
  18. };
  19. var Pos = CodeMirror.Pos;
  20. function getKeywords(editor) {
  21. var mode = editor.doc.modeOption;
  22. if (mode === "sql") mode = "text/x-sql";
  23. return CodeMirror.resolveMode(mode).keywords;
  24. }
  25. function match(string, word) {
  26. var len = string.length;
  27. var sub = word.substr(0, len);
  28. return string.toUpperCase() === sub.toUpperCase();
  29. }
  30. function addMatches(result, search, wordlist, formatter) {
  31. for (var word in wordlist) {
  32. if (!wordlist.hasOwnProperty(word)) continue;
  33. if (Array.isArray(wordlist)) {
  34. word = wordlist[word];
  35. }
  36. if (match(search, word)) {
  37. result.push(formatter(word));
  38. }
  39. }
  40. }
  41. function nameCompletion(cur, token, result, editor) {
  42. var useBacktick = (token.string.charAt(0) == "`");
  43. var string = token.string.substr(1);
  44. var prevToken = editor.getTokenAt(Pos(cur.line, token.start));
  45. if (token.string.charAt(0) == "." || prevToken.string == "."){
  46. //Suggest colunm names
  47. if (prevToken.string == ".") {
  48. var prevToken = editor.getTokenAt(Pos(cur.line, token.start - 1));
  49. }
  50. var table = prevToken.string;
  51. //Check if backtick is used in table name. If yes, use it for columns too.
  52. var useBacktickTable = false;
  53. if (table.match(/`/g)) {
  54. useBacktickTable = true;
  55. table = table.replace(/`/g, "");
  56. }
  57. //Check if table is available. If not, find table by Alias
  58. if (!tables.hasOwnProperty(table))
  59. table = findTableByAlias(table, editor);
  60. var columns = tables[table];
  61. if (!columns) return;
  62. if (useBacktick) {
  63. addMatches(result, string, columns, function(w) {return "`" + w + "`";});
  64. }
  65. else if(useBacktickTable) {
  66. addMatches(result, string, columns, function(w) {return ".`" + w + "`";});
  67. }
  68. else {
  69. addMatches(result, string, columns, function(w) {return "." + w;});
  70. }
  71. }
  72. else {
  73. //Suggest table names or colums in defaultTable
  74. while (token.start && string.charAt(0) == ".") {
  75. token = editor.getTokenAt(Pos(cur.line, token.start - 1));
  76. string = token.string + string;
  77. }
  78. if (useBacktick) {
  79. addMatches(result, string, tables, function(w) {return "`" + w + "`";});
  80. addMatches(result, string, defaultTable, function(w) {return "`" + w + "`";});
  81. }
  82. else {
  83. addMatches(result, string, tables, function(w) {return w;});
  84. addMatches(result, string, defaultTable, function(w) {return w;});
  85. }
  86. }
  87. }
  88. function eachWord(lineText, f) {
  89. if (!lineText) return;
  90. var excepted = /[,;]/g;
  91. var words = lineText.split(" ");
  92. for (var i = 0; i < words.length; i++) {
  93. f(words[i]?words[i].replace(excepted, '') : '');
  94. }
  95. }
  96. function convertCurToNumber(cur) {
  97. // max characters of a line is 999,999.
  98. return cur.line + cur.ch / Math.pow(10, 6);
  99. }
  100. function convertNumberToCur(num) {
  101. return Pos(Math.floor(num), +num.toString().split('.').pop());
  102. }
  103. function findTableByAlias(alias, editor) {
  104. var doc = editor.doc;
  105. var fullQuery = doc.getValue();
  106. var aliasUpperCase = alias.toUpperCase();
  107. var previousWord = "";
  108. var table = "";
  109. var separator = [];
  110. var validRange = {
  111. start: Pos(0, 0),
  112. end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
  113. };
  114. //add separator
  115. var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
  116. while(indexOfSeparator != -1) {
  117. separator.push(doc.posFromIndex(indexOfSeparator));
  118. indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);
  119. }
  120. separator.unshift(Pos(0, 0));
  121. separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
  122. //find valid range
  123. var prevItem = 0;
  124. var current = convertCurToNumber(editor.getCursor());
  125. for (var i=0; i< separator.length; i++) {
  126. var _v = convertCurToNumber(separator[i]);
  127. if (current > prevItem && current <= _v) {
  128. validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) };
  129. break;
  130. }
  131. prevItem = _v;
  132. }
  133. var query = doc.getRange(validRange.start, validRange.end, false);
  134. for (var i = 0; i < query.length; i++) {
  135. var lineText = query[i];
  136. eachWord(lineText, function(word) {
  137. var wordUpperCase = word.toUpperCase();
  138. if (wordUpperCase === aliasUpperCase && tables.hasOwnProperty(previousWord)) {
  139. table = previousWord;
  140. }
  141. if (wordUpperCase !== CONS.ALIAS_KEYWORD) {
  142. previousWord = word;
  143. }
  144. });
  145. if (table) break;
  146. }
  147. return table;
  148. }
  149. CodeMirror.registerHelper("hint", "sql", function(editor, options) {
  150. tables = (options && options.tables) || {};
  151. var defaultTableName = options && options.defaultTable;
  152. defaultTable = (defaultTableName && tables[defaultTableName] || []);
  153. keywords = keywords || getKeywords(editor);
  154. var cur = editor.getCursor();
  155. var result = [];
  156. var token = editor.getTokenAt(cur), start, end, search;
  157. if (token.end > cur.ch) {
  158. token.end = cur.ch;
  159. token.string = token.string.slice(0, cur.ch - token.start);
  160. }
  161. if (token.string.match(/^[.`\w@]\w*$/)) {
  162. search = token.string;
  163. start = token.start;
  164. end = token.end;
  165. } else {
  166. start = end = cur.ch;
  167. search = "";
  168. }
  169. if (search.charAt(0) == "." || search.charAt(0) == "`") {
  170. nameCompletion(cur, token, result, editor);
  171. } else {
  172. addMatches(result, search, tables, function(w) {return w;});
  173. addMatches(result, search, defaultTable, function(w) {return w;});
  174. addMatches(result, search, keywords, function(w) {return w.toUpperCase();});
  175. }
  176. return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
  177. });
  178. });