Hello Mr.J——SQL分类

来源:互联网 发布:ug编程自动给转速 编辑:程序博客网 时间:2024/06/05 17:56

  看了很长很长时间的建立连接的源码,NIO,SOCKET,连接这部分确实是我的短处,到现在都没看懂。

  恩,换了一部分研究,就看了一下解析SQL的过程,这个过程在mycat的源码贡献者——http://www.hashzhang.com/frontend/html/index.html 这大兄弟这里是归类到了路由模块中。

  既然人家是源码贡献者,站在巨人的肩膀上,顺着他的分类继续分析吧。

  一个SQL语句从应用程序中发送到mycat上,mycat首先需要判断这个SQL语句是哪种。比如SELECT语句和SHOW语句就属于不同的大分类。

  解析第一个词的方法在这,其实就是判断了一下第一个字符是哪个字母,然后case不同的方法进行继续判断。这里就不写多余的注释了,很简单。

    public static int parse(String stmt) {int length = stmt.length();//FIX BUG FOR SQL SUCH AS /XXXX/SQLint rt = -1;for (int i = 0; i < length; ++i) {switch (stmt.charAt(i)) {case ' ':case '\t':case '\r':case '\n':continue;case '/':// such as /*!40101 SET character_set_client = @saved_cs_client// */;if (i == 0 && stmt.charAt(1) == '*' && stmt.charAt(2) == '!' && stmt.charAt(length - 2) == '*'&& stmt.charAt(length - 1) == '/') {return MYSQL_CMD_COMMENT;}case '#':i = ParseUtil.comment(stmt, i);if (i + 1 == length) {return MYSQL_COMMENT;}continue;case 'A':case 'a':rt = aCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'B':case 'b':rt = beginCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'C':case 'c':rt = commitOrCallCheckOrCreate(stmt, i);if (rt != OTHER) {return rt;}continue;case 'D':case 'd':rt = deleteOrdCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'E':case 'e':rt = explainCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'I':case 'i':rt = insertCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'M':case 'm':rt = migrateCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'R':case 'r':rt = rCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'S':case 's':rt = sCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'T':case 't':rt = tCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'U':case 'u':rt = uCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'K':case 'k':rt = killCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'H':case 'h':rt = helpCheck(stmt, i);if (rt != OTHER) {return rt;}continue;case 'L':case 'l':rt = lCheck(stmt, i);if (rt != OTHER) {return rt;}continue;default:continue;}}return OTHER;}
  在判断第一个字母之后,会调用不同的方法进行继续判断,比如,第一个字母是S或者s,那么可能会有Select、show、start、savepoint等等语句,这样还需要进行第二个字母的判断。

static int sCheck(String stmt, int offset) {if (stmt.length() > ++offset) {switch (stmt.charAt(offset)) {case 'A':case 'a':return savepointCheck(stmt, offset);case 'E':case 'e':return seCheck(stmt, offset);case 'H':case 'h':return showCheck(stmt, offset);case 'T':case 't':return startCheck(stmt, offset);default:return OTHER;}}return OTHER;}
  第二个字母之后有很多语句可以确定了,有的还不能确定,比如Select和Set,这个时候还需要继续判断,第三个判断我就不贴了,假装看完了,然后选择Select语句的处理方法看看吧。

static int selectCheck(String stmt, int offset) {if (stmt.length() > offset + 4) {char c1 = stmt.charAt(++offset);char c2 = stmt.charAt(++offset);char c3 = stmt.charAt(++offset);char c4 = stmt.charAt(++offset);if ((c1 == 'E' || c1 == 'e')&& (c2 == 'C' || c2 == 'c')&& (c3 == 'T' || c3 == 't')&& (c4 == ' ' || c4 == '\t' || c4 == '\r' || c4 == '\n'|| c4 == '/' || c4 == '#')) {return (offset << 8) | SELECT;}}return OTHER;}
  进入Select的语句检查后,会逐一判断select后面的几个字母,最后会将传入的表示第几个字符做一个移位运算再与表示SELECT的数值做一个与运算,然后返回去。其实并不理解为什么要这么做。。。

  在判断了语句属于哪个大类之后,mycat会把语句交给专门处理这种大类语句的类来进行后续的处理。

public void query(String sql) {ServerConnection c = this.source;if (LOGGER.isDebugEnabled()) {LOGGER.debug(new StringBuilder().append(c).append(sql).toString());}//解析SQL语句int rs = ServerParse.parse(sql);//与运算int sqlType = rs & 255;switch (sqlType) {//explain sqlcase ServerParse.EXPLAIN:ExplainHandler.handle(sql, c, rs >>> 8);break;//explain2 datanode=? sql=?case ServerParse.EXPLAIN2:Explain2Handler.handle(sql, c, rs >>> 8);break;case ServerParse.SET:SetHandler.handle(sql, c, rs >>> 8);break;case ServerParse.SHOW:ShowHandler.handle(sql, c, rs >>> 8);break;case ServerParse.SELECT:SelectHandler.handle(sql, c, rs >>> 8);break;
  后面太长了,就不截了,都是一样的调用某个大类语句的处理。

  在进入Select语句处理类之后,mycat还会对第二个词进行分析,有些语句不需要访问后面的数据库返回数据,直接返回的是mycat模拟的mysql服务端的信息。

  而需要处理的语句,就会执行路由查询,获取分片信息,执行数据库的查询。

public final class SelectHandler {public static void handle(String stmt, ServerConnection c, int offs) {int offset = offs;switch (ServerParseSelect.parse(stmt, offs)) {case ServerParseSelect.VERSION_COMMENT:SelectVersionComment.response(c);break;case ServerParseSelect.DATABASE:SelectDatabase.response(c);break;case ServerParseSelect.USER:SelectUser.response(c);break;case ServerParseSelect.VERSION:SelectVersion.response(c);break;case ServerParseSelect.SESSION_INCREMENT:SessionIncrement.response(c);break;case ServerParseSelect.SESSION_ISOLATION:SessionIsolation.response(c);break;case ServerParseSelect.LAST_INSERT_ID:// offset = ParseUtil.move(stmt, 0, "select".length());loop:for (int l=stmt.length(); offset < l; ++offset) {switch (stmt.charAt(offset)) {case ' ':continue;case '/':case '#':offset = ParseUtil.comment(stmt, offset);continue;case 'L':case 'l':break loop;}}offset = ServerParseSelect.indexAfterLastInsertIdFunc(stmt, offset);offset = ServerParseSelect.skipAs(stmt, offset);SelectLastInsertId.response(c, stmt, offset);break;case ServerParseSelect.IDENTITY:// offset = ParseUtil.move(stmt, 0, "select".length());loop:for (int l=stmt.length(); offset < l; ++offset) {switch (stmt.charAt(offset)) {case ' ':continue;case '/':case '#':offset = ParseUtil.comment(stmt, offset);continue;case '@':break loop;}}int indexOfAtAt = offset;offset += 2;offset = ServerParseSelect.indexAfterIdentity(stmt, offset);String orgName = stmt.substring(indexOfAtAt, offset);offset = ServerParseSelect.skipAs(stmt, offset);SelectIdentity.response(c, stmt, offset, orgName);break;            case ServerParseSelect.SELECT_VAR_ALL:                SelectVariables.execute(c,stmt);                break;case ServerParseSelect.SESSION_TX_READ_ONLY:SelectTxReadOnly.response(c);break;default:c.execute(stmt, ServerParse.SELECT);}}}
  最后的c.execute就是执行数据库查询,剩下的都是不用查询的- -

  ok,这里就写到这了,大概是懂了SQL的解析过程了,其实可以看做是一个树形结构,广度优先搜索。从周更变成月更了。不应该呀,兄dei。

0 0
原创粉丝点击