📖

VimのBigQuery簡易プラグインを作ってる(1)

2022/10/15に公開

作りたいもの・機能

  • (Must)vim上で編集しているStandard SQLの必要な処理バイト数がわかる
  • (Must)vim上で編集しているStandard SQLがエラーが返るかどうかわかる
  • (Must)エラー文を手元で確認できるようにする
  • (Want)Standard SQLの補完機能(関数やDBのカラム名)

この記事で書く内容

  • (Must)vim上で編集しているStandard SQLの必要な処理バイト数がわかる
  • (Must)vim上で編集しているStandard SQLがエラーが返るかどうかわかる

現在のプラグイン全貌(bqrunner.vim)

├── README.md
├── test.bq
├── doc
├── autoload # こっちに主に処理を書く
│   └── bqrunner.vim
└── plugin # こっちはプラグインのコマンド定義
    └── bqrunner.vim

現段階ではプラグインにするほどのものではないが,今後Standard SQLの補完機能などを実装するとなるともうちょっとコードが大きくなりそう.

DryRunの実行と結果の格納

autoload/bqrunner.vim
fun! bqrunner#dry_run(file) abort
  let l:command = 'cat ' . a:file . ' | bq query --dry_run --use_legacy_sql=false --'
  let l:result = system(l:command)
  let w:bqrunner_is_query_success = s:is_query_success(l:result)
  if w:bqrunner_is_query_success
    let l:bytes = s:get_bytes_from_result(l:result)
    let w:bqrunner_dry_run_bytes = s:convert_bytes_unit(l:bytes)
  else
    let w:bqrunner_error_msg = l:result
  endif
endf

fun! s:is_query_success(result) abort
  return match(a:result, "successfully") != -1 ? 1 : 0
endf

fun! s:get_bytes_from_result(result) abort
  return matchstr(a:result, '[0-9]\+')
endf

fun! s:convert_bytes_unit(bytes) abort
  if type(a:bytes) == 1
    let l:num_bytes = str2float(a:bytes)
  else
    let l:num_bytes = a:bytes
  endif
  let l:thousand = 0
  while l:num_bytes > 1000.0
    let l:num_bytes = l:num_bytes / 1000.0
    let l:thousand += 1
  endw
  let l:units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
  return printf('%.2f %s', l:num_bytes, l:units[l:thousand])
endf

やっていることはすごく単純で,bqコマンドを使って編集しているファイルの内容でDry Runを実行&結果を現在のウィンドウ範囲の変数に格納している.

変数の定義はplugin/bqrunner.vimで行っている.

plugin/bqrunner.vim
if exists('g:bqrunner_loaded')
  finish
endif
let g:bqrunner_loaded = 1
let w:bqrunner_is_query_success = -1
let w:bqrunner_dry_run_bytes = ''
let w:bqrunner_error_msg = ''

command! DryRun call bqrunner#dry_run(@%)
augroup bqrunner_dryrun
  autocmd!
  autocmd BufRead,BufWritePost *.bq DryRun
augroup END

ここまでで「クエリが成功しているか」と「必要になる処理バイト数」(あとエラー文)を変数に格納できたので,あとは任意の場所に表示するだけ.
今回はvim-airlineというPowerlineの右端に表示してみる.

ステータスと処理バイト数の表示(vim-airline拡張)

とりあえず先に現段階の完成図.

クエリが成功した場合

クエリが失敗した(存在しないカラムの指定)の場合

今回はvim-airlineの拡張として特定ディレクトリ配下にファイルを作った.(これはvim-airlineのexampleとして形式がおいてあったのでそれにしたがって作った)

vim-airline/autoload/airline/extensions/bqrunner.vim
" MIT License. Copyright (c) 2013-2021 Bailey Ling et al.
" vim: et ts=2 sts=2 sw=2

scriptencoding utf-8

if !get(g:, 'bqrunner_loaded', 0)
  finish
endif

let s:spc = g:airline_symbols.space

" First we define an init function that will be invoked from extensions.vim
function! airline#extensions#bqrunner#init(ext)

  " Here we define a new part for the plugin.  This allows users to place this
  " extension in arbitrary locations.
  call airline#parts#define_raw('dryrun', '%{airline#extensions#bqrunner#get_dryrun_status()}')

  " Next up we add a funcref so that we can run some code prior to the
  " statusline getting modifed.
  call a:ext.add_statusline_func('airline#extensions#bqrunner#apply')

  " You can also add a funcref for inactive statuslines.
  " call a:ext.add_inactive_statusline_func('airline#extensions#example#unapply')
endfunction

" This function will be invoked just prior to the statusline getting modified.
function! airline#extensions#bqrunner#apply(...)
  " First we check for the filetype.

  " Let's say we want to append to section_c, first we check if there's
  " already a window-local override, and if not, create it off of the global
  " section_c.
  let w:airline_section_x = get(w:, 'airline_section_x', g:airline_section_x)

  " Then we just append this extenion to it, optionally using separators.
  let w:airline_section_x = '%{airline#extensions#bqrunner#get_dryrun_status()}' . s:spc.g:airline_right_alt_sep.s:spc . w:airline_section_x
endfunction

" Finally, this function will be invoked from the statusline.
function! airline#extensions#bqrunner#get_dryrun_status()
  let l:is_query_success = get(w:, 'bqrunner_is_query_success', -1)
  if l:is_query_success == 1
    return "\<Char-0xf058>  " . get(w:, 'bqrunner_dry_run_bytes', '')
  elseif l:is_query_success == 0
    return "\<Char-0xf071>  "
  else
    return ""
  endif
endfunction

(g|w):airline_section_xというのがPowerline右側のファイルタイプ表示の部分で,この拡張によってそこの左に「✅ 〇〇 KB」のような感じで表示される.

次やること

  • (Must)エラー文を手元で確認できるようにする

たぶんQuickfixあたりに表示して,トグルで表示切り替えできるようにするのが便利そうかな.

Discussion