02. Hive源碼 — processFile&processLine

Hive文件執(zhí)行的方法主要是 CliDriver 中的 processFile撑柔,語句執(zhí)行的主要在 processLine 方法中饼煞,這一篇主要看這兩個方法中主要做了什么蜜猾?

1. processFile

processFile 中最主要的代碼如下:
rc = processReader(bufferReader);

我們可以看到其實 processReader 只是把文件讀進來泣特,去掉注釋語句,然后對其調(diào)用processLine 方法脱衙,所以我們主要需要分析 processLine 方法。

public int processReader(BufferedReader r) throws IOException {
  String line;
  StringBuilder qsb = new StringBuilder();
  while ((line = r.readLine()) != null) {
    // Skipping through comments
    if (! line.startsWith("--")) {
      qsb.append(line + "\n");
    }
  }
  return (processLine(qsb.toString()));
}

2. processLine

2.1 整體代碼結(jié)構(gòu)
  • processLine中的主要代碼如下:
public int processLine(String line, boolean allowInterrupting) {
   ...............
   // we can not use "split" function directly as ";" may be quoted
   int lastRet = 0, ret = 0;
   List<String> commands = splitSemiColon(line);
   String command = "";
   for (String oneCmd : commands) {
     if (StringUtils.endsWith(oneCmd, "\\")) {
       command += StringUtils.chop(oneCmd) + ";";
       continue;
     } else {
       command += oneCmd;
     }
     if (StringUtils.isBlank(command)) {
       continue;
     }
     ret = processCmd(command);
     command = "";
     lastRet = ret;
   }

   ................
}

從以上代碼可以看出例驹,processLine 會把我們寫的所有SQL語句用分號 ; 拆開然后對每一句執(zhí)行 processCmd方法捐韩,有一句不成功就會返回失敗

  • processCmd 方法
public int processCmd(String cmd) {
  CliSessionState ss = (CliSessionState) SessionState.get();
  ss.setLastCommand(cmd);
  ss.updateThreadName();
  // Flush the print stream, so it doesn't include output from the last command
  ss.err.flush();
  String cmd_trimmed = cmd.trim();
  String[] tokens = tokenizeCmd(cmd_trimmed);
  int ret = 0;
  // 處理退出命令
  if (cmd_trimmed.toLowerCase().equals("quit") || cmd_trimmed.toLowerCase().equals("exit")) {
    ss.close();
    System.exit(0);
  } else if (tokens[0].equalsIgnoreCase("source")) { // 處理source 開頭語句
    String cmd_1 = getFirstCmd(cmd_trimmed, tokens[0].length());
    cmd_1 = .........
    .........
    ret = processFile(cmd_1); // source filename; 最終回歸到processFile方法
    .........
  } else if (cmd_trimmed.startsWith("!")) { // !開頭的命令則調(diào)用ShellCmdExecutor中的 execute方法執(zhí)行
    String shell_cmd = cmd_trimmed.substring(1);
    shell_cmd = .........
    .........
    ShellCmdExecutor executor = new ShellCmdExecutor(shell_cmd, ss.out, ss.err);
    ret = executor.execute();
    .........
  }  else { // local mode
    ..........
    CommandProcessor proc = CommandProcessorFactory.get(tokens, (HiveConf) conf);
    ret = processLocalCmd(cmd, proc, ss); // 這個是大多數(shù)sql的主要執(zhí)行方法
    ..........
}

從以上代碼可以看到最需要關(guān)注的方法是 processLocalCmd

  • processLocalCmd 方法
int processLocalCmd(String cmd, CommandProcessor proc, CliSessionState ss) {
  .............
  int ret = 0;
  .............
      if (proc != null) {
        if (proc instanceof Driver) {
          Driver qp = (Driver) proc;
          .........
          ret = qp.run(cmd).getResponseCode();  // 當 proc 屬于 Driver對象的時候主要執(zhí)行的方法
          .........
        } else {
          String firstToken = tokenizeCmd(cmd.trim())[0];
          String cmd_1 = getFirstCmd(cmd.trim(), firstToken.length());
          .........
          CommandProcessorResponse res = proc.run(cmd_1); // 當 proc 為非 Driver對象的時候主要執(zhí)行的方法
          .........
          ret = res.getResponseCode();
        }
  .............
  return ret;
}

從上面代碼可以看出,proc這個對象有Driver類和非Driver類兩種鹃锈,所以重點需要關(guān)注什么時候得到的是 Driver對象荤胁,什么時候得到的是非Driver對象,才能知道什么樣的sql執(zhí)行Driver類中的run方法屎债,什么樣的sql執(zhí)行非Driver類中的run方法仅政。
所以接下來就具體看proc這個對象是如何獲取的。

2.2 proc的獲取(CommandProcessor對象)

proc的獲取是通過processCmd中的如下這句代碼獲取的盆驹,其中tokens是一句SQL語句用空白符分隔開的字符串數(shù)組(即組成SQL的一個一個詞組成的數(shù)組)
CommandProcessor proc = CommandProcessorFactory.get(tokens, (HiveConf) conf);
工廠中對應(yīng)的get方法如下:

public static CommandProcessor get(String[] cmd, HiveConf conf) {
    CommandProcessor result = getForHiveCommand(cmd, conf);
    if (result != null)  return result;
    .........
    if (conf == null)  return new Driver();
    Driver drv = mapDrivers.get(conf);
        if (drv == null) {
            drv = new Driver();
            mapDrivers.put(conf, drv);
        } else {
            drv.resetQueryState();
        }
        drv.init();
        return drv;
    }
}

從代碼中可知除了getForHiveCommand(cmd, conf)能返回非空對象的情況外圆丹,其他情況得到的都是Driver對象,并且Driver對象可復(fù)用躯喇。

所以要看getForHiveCommand方法辫封,這個方法中就簡單調(diào)用了 getForHiveCommandInternal 方法,代碼如下

public static CommandProcessor getForHiveCommandInternal(String[] cmd, HiveConf conf, boolean testOnly)
    HiveCommand hiveCommand = HiveCommand.find(cmd, testOnly);
    if (hiveCommand == null || isBlank(cmd[0])) {
        return null;
    }

    Set<String> availableCommands = new HashSet<String>();
    // HIVE_SECURITY_COMMAND_WHITELIST默認值:set,reset,dfs,add,list,delete,reload,compile
    for (String availableCommand : conf.getVar(HiveConf.ConfVars.HIVE_SECURITY_COMMAND_WHITELIST).split(",")) {
        availableCommands.add(availableCommand.toLowerCase().trim());
    }
    if (cmd.length > 1 && "reload".equalsIgnoreCase(cmd[0]) && "function".equalsIgnoreCase(cmd[1])) {
        // special handling for SQL "reload function"
        return null;
    }
    switch (hiveCommand) {
        case SET:
            return new SetProcessor();
        case RESET:
            return new ResetProcessor();
        case DFS:
            SessionState ss = SessionState.get();
            return new DfsProcessor(ss.getConf());
        case ADD:
            return new AddResourceProcessor();
        case LIST:
            return new ListResourceProcessor();
        case DELETE:
            return new DeleteResourceProcessor();
        case COMPILE:
            return new CompileProcessor();
        case RELOAD:
            return new ReloadProcessor();
        case CRYPTO:
            return new CryptoProcessor(SessionState.get().getHdfsEncryptionShim(), conf);
        .........
    }
}

// hiveCommand的獲取廉丽,HiveCommand是一個枚舉類倦微,
// 包含枚舉對象 SET(),RESET(),DFS(),CRYPTO(true),ADD(),LIST(),RELOAD(),DELETE(),COMPILE();
public static HiveCommand find(String[] command, boolean findOnlyForTesting) {
  if (null == command){
    return null;
  }
  String cmd = command[0];
  if (cmd != null) {
    cmd = cmd.trim().toUpperCase();
    if (command.length > 1 && "role".equalsIgnoreCase(command[1])) {
      // special handling for set role r1 statement
      return null;
    } else if(command.length > 1 && "from".equalsIgnoreCase(command[1])) {
      //special handling for SQL "delete from <table> where..."
      return null;
    } else if(command.length > 1 && "set".equalsIgnoreCase(command[0]) && "autocommit".equalsIgnoreCase(command[1])) {
      return null;//don't want set autocommit true|false to get mixed with set hive.foo.bar...
    } else if (COMMANDS.contains(cmd)) {
      HiveCommand hiveCommand = HiveCommand.valueOf(cmd);
      return hiveCommand;
    }
  }
  return null;
}

// COMMANDS的來源
private static final Set<String> COMMANDS = new HashSet<String>();
static {
  for (HiveCommand command : HiveCommand.values()) {
    COMMANDS.add(command.name());
  }
}

由上述代碼可以看到不同SQL會得到不同的CommandProcessor對象proc,主要有如下這些情況:

SQL語句 返回的CommandProcessor對象
set .... 并且不是 set role ..../ set autocommit... new SetProcessor()
reset .... new ResetProcessor()
dfs .... new DfsProcessor(ss.getConf())
crypto .... new CryptoProcessor(SessionState.get().getHdfsEncryptionShim(), conf)
add .... new AddResourceProcessor()
list .... new ListResourceProcessor()
reload .... new ReloadProcessor()
delete .... 并且不是 delete from ...... new DeleteResourceProcessor()
compile .... new CompileProcessor()
其他語句 Driver對象

3. 總結(jié)

由上面源碼的分析可以得出正压,除了set ...欣福、dfs ...、add ... 這種不是對表中數(shù)據(jù)進行操作的sql語句焦履,其他都是通過Driver類對象的run方法執(zhí)行劣欢。
下一節(jié)主要看Driver中的run方法如何運行sql語句棕诵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市凿将,隨后出現(xiàn)的幾起案子校套,更是在濱河造成了極大的恐慌,老刑警劉巖牧抵,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笛匙,死亡現(xiàn)場離奇詭異,居然都是意外死亡犀变,警方通過查閱死者的電腦和手機妹孙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來获枝,“玉大人蠢正,你說我怎么就攤上這事∈〉辏” “怎么了嚣崭?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長懦傍。 經(jīng)常有香客問我雹舀,道長,這世上最難降的妖魔是什么粗俱? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任说榆,我火速辦了婚禮,結(jié)果婚禮上寸认,老公的妹妹穿的比我還像新娘签财。我一直安慰自己,他們只是感情好偏塞,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布荠卷。 她就那樣靜靜地躺著,像睡著了一般烛愧。 火紅的嫁衣襯著肌膚如雪油宜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天怜姿,我揣著相機與錄音慎冤,去河邊找鬼。 笑死沧卢,一個胖子當著我的面吹牛蚁堤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播但狭,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼披诗,長吁一口氣:“原來是場噩夢啊……” “哼撬即!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呈队,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤剥槐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宪摧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粒竖,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年几于,在試婚紗的時候發(fā)現(xiàn)自己被綠了蕊苗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡沿彭,死狀恐怖朽砰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喉刘,我是刑警寧澤瞧柔,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站饱搏,受9級特大地震影響非剃,放射性物質(zhì)發(fā)生泄漏置逻。R本人自食惡果不足惜推沸,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望券坞。 院中可真熱鬧鬓催,春花似錦、人聲如沸恨锚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽猴伶。三九已至课舍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間他挎,已是汗流浹背筝尾。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留办桨,地道東北人筹淫。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像呢撞,于是被迫代替她去往敵國和親损姜。 傳聞我的和親對象是個殘疾皇子饰剥,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345