sql-hint.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. ;(function (mod) {
  4. if (typeof exports == 'object' && typeof module == 'object')
  5. // CommonJS
  6. mod(require('../../lib/codemirror'), require('../../mode/sql/sql'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror', '../../mode/sql/sql'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. var tables
  15. var defaultTable
  16. var keywords
  17. var identifierQuote
  18. var CONS = {
  19. QUERY_DIV: ';',
  20. ALIAS_KEYWORD: 'AS',
  21. }
  22. var Pos = CodeMirror.Pos,
  23. cmpPos = CodeMirror.cmpPos
  24. function isArray(val) {
  25. return Object.prototype.toString.call(val) == '[object Array]'
  26. }
  27. function getKeywords(editor) {
  28. var mode = editor.doc.modeOption
  29. if (mode === 'sql') mode = 'text/x-sql'
  30. return CodeMirror.resolveMode(mode).keywords
  31. }
  32. function getIdentifierQuote(editor) {
  33. var mode = editor.doc.modeOption
  34. if (mode === 'sql') mode = 'text/x-sql'
  35. return CodeMirror.resolveMode(mode).identifierQuote || '`'
  36. }
  37. function getText(item) {
  38. return typeof item == 'string' ? item : item.text
  39. }
  40. function wrapTable(name, value) {
  41. if (isArray(value)) value = { columns: value }
  42. if (!value.text) value.text = name
  43. return value
  44. }
  45. function parseTables(input) {
  46. var result = {}
  47. if (isArray(input)) {
  48. for (var i = input.length - 1; i >= 0; i--) {
  49. var item = input[i]
  50. result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
  51. }
  52. } else if (input) {
  53. for (var name in input) result[name.toUpperCase()] = wrapTable(name, input[name])
  54. }
  55. return result
  56. }
  57. function getTable(name) {
  58. return tables[name.toUpperCase()]
  59. }
  60. function shallowClone(object) {
  61. var result = {}
  62. for (var key in object) if (object.hasOwnProperty(key)) result[key] = object[key]
  63. return result
  64. }
  65. function match(string, word) {
  66. var len = string.length
  67. var sub = getText(word).substr(0, len)
  68. return string.toUpperCase() === sub.toUpperCase()
  69. }
  70. function addMatches(result, search, wordlist, formatter) {
  71. if (isArray(wordlist)) {
  72. for (var i = 0; i < wordlist.length; i++) if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
  73. } else {
  74. for (var word in wordlist)
  75. if (wordlist.hasOwnProperty(word)) {
  76. var val = wordlist[word]
  77. if (!val || val === true) val = word
  78. else val = val.displayText ? { text: val.text, displayText: val.displayText } : val.text
  79. if (match(search, val)) result.push(formatter(val))
  80. }
  81. }
  82. }
  83. function cleanName(name) {
  84. // Get rid name from identifierQuote and preceding dot(.)
  85. if (name.charAt(0) == '.') {
  86. name = name.substr(1)
  87. }
  88. // replace duplicated identifierQuotes with single identifierQuotes
  89. // and remove single identifierQuotes
  90. var nameParts = name.split(identifierQuote + identifierQuote)
  91. for (var i = 0; i < nameParts.length; i++) nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote, 'g'), '')
  92. return nameParts.join(identifierQuote)
  93. }
  94. function insertIdentifierQuotes(name) {
  95. var nameParts = getText(name).split('.')
  96. for (var i = 0; i < nameParts.length; i++)
  97. nameParts[i] =
  98. identifierQuote +
  99. // duplicate identifierQuotes
  100. nameParts[i].replace(new RegExp(identifierQuote, 'g'), identifierQuote + identifierQuote) +
  101. identifierQuote
  102. var escaped = nameParts.join('.')
  103. if (typeof name == 'string') return escaped
  104. name = shallowClone(name)
  105. name.text = escaped
  106. return name
  107. }
  108. function nameCompletion(cur, token, result, editor) {
  109. // Try to complete table, column names and return start position of completion
  110. var useIdentifierQuotes = false
  111. var nameParts = []
  112. var start = token.start
  113. var cont = true
  114. while (cont) {
  115. cont = token.string.charAt(0) == '.'
  116. useIdentifierQuotes = useIdentifierQuotes || token.string.charAt(0) == identifierQuote
  117. start = token.start
  118. nameParts.unshift(cleanName(token.string))
  119. token = editor.getTokenAt(Pos(cur.line, token.start))
  120. if (token.string == '.') {
  121. cont = true
  122. token = editor.getTokenAt(Pos(cur.line, token.start))
  123. }
  124. }
  125. // Try to complete table names
  126. var string = nameParts.join('.')
  127. addMatches(result, string, tables, function (w) {
  128. return useIdentifierQuotes ? insertIdentifierQuotes(w) : w
  129. })
  130. // Try to complete columns from defaultTable
  131. addMatches(result, string, defaultTable, function (w) {
  132. return useIdentifierQuotes ? insertIdentifierQuotes(w) : w
  133. })
  134. // Try to complete columns
  135. string = nameParts.pop()
  136. var table = nameParts.join('.')
  137. var alias = false
  138. var aliasTable = table
  139. // Check if table is available. If not, find table by Alias
  140. if (!getTable(table)) {
  141. var oldTable = table
  142. table = findTableByAlias(table, editor)
  143. if (table !== oldTable) alias = true
  144. }
  145. var columns = getTable(table)
  146. if (columns && columns.columns) columns = columns.columns
  147. if (columns) {
  148. addMatches(result, string, columns, function (w) {
  149. var tableInsert = table
  150. if (alias == true) tableInsert = aliasTable
  151. if (typeof w == 'string') {
  152. w = tableInsert + '.' + w
  153. } else {
  154. w = shallowClone(w)
  155. w.text = tableInsert + '.' + w.text
  156. }
  157. return useIdentifierQuotes ? insertIdentifierQuotes(w) : w
  158. })
  159. }
  160. return start
  161. }
  162. function eachWord(lineText, f) {
  163. var words = lineText.split(/\s+/)
  164. for (var i = 0; i < words.length; i++) if (words[i]) f(words[i].replace(/[`,;]/g, ''))
  165. }
  166. function findTableByAlias(alias, editor) {
  167. var doc = editor.doc
  168. var fullQuery = doc.getValue()
  169. var aliasUpperCase = alias.toUpperCase()
  170. var previousWord = ''
  171. var table = ''
  172. var separator = []
  173. var validRange = {
  174. start: Pos(0, 0),
  175. end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length),
  176. }
  177. //add separator
  178. var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV)
  179. while (indexOfSeparator != -1) {
  180. separator.push(doc.posFromIndex(indexOfSeparator))
  181. indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator + 1)
  182. }
  183. separator.unshift(Pos(0, 0))
  184. separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length))
  185. //find valid range
  186. var prevItem = null
  187. var current = editor.getCursor()
  188. for (var i = 0; i < separator.length; i++) {
  189. if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
  190. validRange = { start: prevItem, end: separator[i] }
  191. break
  192. }
  193. prevItem = separator[i]
  194. }
  195. if (validRange.start) {
  196. var query = doc.getRange(validRange.start, validRange.end, false)
  197. for (var i = 0; i < query.length; i++) {
  198. var lineText = query[i]
  199. eachWord(lineText, function (word) {
  200. var wordUpperCase = word.toUpperCase()
  201. if (wordUpperCase === aliasUpperCase && getTable(previousWord)) table = previousWord
  202. if (wordUpperCase !== CONS.ALIAS_KEYWORD) previousWord = word
  203. })
  204. if (table) break
  205. }
  206. }
  207. return table
  208. }
  209. CodeMirror.registerHelper('hint', 'sql', function (editor, options) {
  210. tables = parseTables(options && options.tables)
  211. var defaultTableName = options && options.defaultTable
  212. var disableKeywords = options && options.disableKeywords
  213. defaultTable = defaultTableName && getTable(defaultTableName)
  214. keywords = getKeywords(editor)
  215. identifierQuote = getIdentifierQuote(editor)
  216. if (defaultTableName && !defaultTable) defaultTable = findTableByAlias(defaultTableName, editor)
  217. defaultTable = defaultTable || []
  218. if (defaultTable.columns) defaultTable = defaultTable.columns
  219. var cur = editor.getCursor()
  220. var result = []
  221. var token = editor.getTokenAt(cur),
  222. start,
  223. end,
  224. search
  225. if (token.end > cur.ch) {
  226. token.end = cur.ch
  227. token.string = token.string.slice(0, cur.ch - token.start)
  228. }
  229. if (token.string.match(/^[.`"'\w@][\w$#]*$/g)) {
  230. search = token.string
  231. start = token.start
  232. end = token.end
  233. } else {
  234. start = end = cur.ch
  235. search = ''
  236. }
  237. if (search.charAt(0) == '.' || search.charAt(0) == identifierQuote) {
  238. start = nameCompletion(cur, token, result, editor)
  239. } else {
  240. var objectOrClass = function (w, className) {
  241. if (typeof w === 'object') {
  242. w.className = className
  243. } else {
  244. w = { text: w, className: className }
  245. }
  246. return w
  247. }
  248. addMatches(result, search, defaultTable, function (w) {
  249. return objectOrClass(w, 'CodeMirror-hint-table CodeMirror-hint-default-table')
  250. })
  251. addMatches(result, search, tables, function (w) {
  252. return objectOrClass(w, 'CodeMirror-hint-table')
  253. })
  254. if (!disableKeywords)
  255. addMatches(result, search, keywords, function (w) {
  256. return objectOrClass(w.toUpperCase(), 'CodeMirror-hint-keyword')
  257. })
  258. }
  259. return { list: result, from: Pos(cur.line, start), to: Pos(cur.line, end) }
  260. })
  261. })