市場上已經有很多成熟的數據庫客戶端工具。像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我...
感興趣或者正在做這塊,大家一塊討論.