SQL编辑器自动提醒实现

  市场上已经有很多成熟的数据库客户端工具。像toad、navicat等都可以很方便的访问、查询数据库。这种工具最核心的作用就是连接数据库、添删改查表或表记录,使用率最高的应该是查询功能,查询最得意的地方就是提供自动提醒功能..输入表名后立即会出现表字段的列表。

极大提高了写sql的效率。当你希望把这些功能搬到web上时,可能在实现上会遇到问题。比如怎么获取表名、别名,字段别名、子查询等等。最近在开发对hive的ad-hoc工具,web页面支持用户编写sql进行查询.演示效果不错。用户也提出了新的需求,比如已经用习惯的功能自动提醒能不能在这工具上实现。回到工作中,开始研究..

1、 准备工作:

1.1 准备2个ajax方法..1、通过数据库名获得所有表名 2、通过表名获得表字段 

1.2 前端我是使用了ace.js一个代码编辑器,这个后续单独介绍,关键的是它提供了提示数据库关键字的方法,我们可以改变传入的keyword实现提醒,修改js:ext-language_tools.js

2、 怎样获取表名,在这里的表名有几种情况:实实在在的表名、表名后的别名、子查询后的别名

实现思路:

// 定义2个数组和conf

var realy_table = {}; // {table/db xxx,xxx,xxx,xxx}
var alias_relation = {};// 别名 {别名:真实名字}
var realy_table_id={}; // 存储表的id标识

var conf = {
  db_url:"",
  table_url:"xxxx",
  col_url:"xxxxx"
};



当用户输入"."时,触发脚本, 获取“.”前面的字符

/**
 * query 
 * pos 点的位置
 * select a.b   ,    a.d from dwa.xxx a;
 * 当分析失败,返回null,不进行自动提醒
 */
function getPointFontWord(query, pos) {
    var reg = /s{2,}/g;
    var res = "";
    var alias = getNameByPos(query, pos, res);
    return alias;
}

// o, pos.column
// 从当前位置往前找,遇到空格或“,”号说明遍历完成
function getNameByPos(query, pos, res) {
    var be = query.substring(pos, pos-1);
    if(pos <= 0) {
        return res;
    }
    if(be==" " || be == ",") {
        return res;
    } else {
        res = be + res;
        return getNameByPos(query, pos-1, res);
    }
}

如果为数据库,首先判断是否获取过,否则调用之前准备的ajax方法获取表组装之前定义的数组

if("你的数据库名" == a1) { // 数据库
// 判断是否获取过
                if(alias_relation[al] != undefined && realy_table[alias_relation[al]] != undefined) {
                    console.log("db cache...");
                    tbs = realy_table[alias_relation[al]];
                }  else {
                    tbs = getTableByDb(al); // 远程获取、组装、返回
                }
}
// 远程获取、组装、返回

function getTableByDb(db) {
    if(realy_table[db] != undefined) 
        return realy_table[db];
    getHiveTableByDb(db);
    return realy_table[db];
    
}

/**
 * get table
 * 数组1 id <=>表名
 * 数组2 表名<=>字段 或 db<=>表名
 * 
 * */
function getHiveTableByDb(db) {
    jquery.ajax({
          url : conf.table_url,
          type : 'POST',
          async : false,
          data : db,
          ok : function(res, textStatus, jqXHR) {
              var tas = res.data;
              var tables="";
              for(var j=0;j<tas.length;j++){
                  tid = tas[j].TBL_ID;
                  tname = tas[j].TBL_NAME;
                  alias_relation[tname] = tname;
                  realy_table_id[tname] = tid;
                  tables += tname + ",";
              }
              if(tables.indexOf(",")>-1)
                  tables = tables.substring(0,tables.length-1);
              if(tables != "")
                  alias_relation[db] = db;
                  realy_table[db] = tables;
          }
    });
}

如果不是数据库名

// 不是db,那就是别名或者表名
                var hql = session.getValue();
                // 已存在的缓存
                if(alias_relation[al] != undefined && realy_table[alias_relation[al]] != undefined) {
                    console.log("table cache...");
                    tbs = realy_table[alias_relation[al]];
                } else {
                    tbs = findColumnsByKey(hql, al);
                }
    
                try {
                    tbs = realy_table[alias_relation[al]];
                } catch(e) {
                }

查找表名、别名对应的column,获取后组装到数组中....

/**
 * 一输入"点"就开始重置keyword,先获得前面的key,
 * 然后indexOf( key ),然后从这个位置往前找..
 * 1. 如果第一个字符为) 说明为子查询
 * 2. 如果不是则是单表查询..
 */
function findColumnsByKey(hql, key) {
    try {
        // 首先格式化query 替换	 
 
 
        hql = $.trim(hql.replace(/[
|
]/g,' '));
        var reg = /s{2,}/g;
        // 多个空格变成一个
        hql = hql.replace(reg, " ");
     // 获得表别名的位置
var tabInx = getTabInx(hql, key); var inx = tabInx.inx; var t_key = tabInx.key; var c_h = $.trim(hql.substring(0, inx)); var t_h = $.trim(hql.substring(0, inx+t_key.length));
    // 判断是普通查询还是子查询  isSub
= checkType(c_h.substring(c_h.length-1, c_h.length), ")"); if(isSub) { // 子查询 var res = ""; c_h = c_h.replaceAll("\( ","\(").replaceAll(" \)",")"); res = $.trim(getSub(c_h, c_h.length, res)); res = $.trim(res.substring(0, res.length-1)); findTable2(res, key); } else { var fromlen = t_h.replaceAll("from", "FROM").lastIndexOf("FROM"); var t_tab = $.trim(t_h.substring(fromlen + 4, t_h.length)); makeT(t_tab.split(" ")); return realy_table[alias_relation[key]]; } }catch(e) { console.log("find columns by key is error..return"); } }

 实现效果:

整体思路就是这样的..js的效率估计不高..功能可以实现,慢慢优化..

遗留问题:

  现在单纯的子查询可以实现提醒,如 select  a. from (select c1,c2,c3 from tb1) a

  考虑到效率和体验,使用纯js提醒不依赖于后端,所以像sql解析的开源代码没利用上,稍复杂的子查询提醒未实现.比如.当子查询中有函数的使用然后再别名时,提醒子查询的字段失效.. 

  如 select subquery.   from (select udf_a(col1,col2,col3) as col4 from abc) subquery,有好的方法或思路hi我...

感兴趣或者正在做这块,大家一块讨论.

工作方向: 大数据、数据仓库、 hadoop、hive、Hbase、 python、ad-hoc、scala、数据工具研发
邮 箱    :zhangkai081@gmail.com
原文地址:https://www.cnblogs.com/smallbaby/p/SQL_IDE.html