openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

sql-hint.js (9090B)


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