市场上已经有很多成熟的数据库客户端工具。像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 替换\t \n \r hql = $.trim(hql.replace(/[\n|\r]/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我...
感兴趣或者正在做这块,大家一块讨论.