vimから直接DB触ったりSQL補完したり

vimから直接データベースを触る場合は、`dbext.vim`を使用すれば良いとのこと。
今回接続したいのDBはSQLiteだけだが、`dbext.vim`はその他多くのDB(Oracle, Sybase, Microsoft, MySQL, DBI,..)に対応しているとのこと。

# dbextのインストール

```vim:.vimrc
NeoBundle 'vim-scripts/dbext.vim', '18.0'
```

[公式](http://www.vim.org/scripts/script.php?script_id=356)のバージョンは20.0だが、[github](https://github.com/vim-scripts/dbext.vim)には19.0までが上がっている。
ただし19.0で加えられた修正に少し問題があったため、ここでは特にこだわりもないため18.0を使用した。
(具体的には`cmd_terminator`の定義に問題あり。明示的に`cmd_terminator`を指定するなどで回避はできる)

# 接続設定

接続先の指定方法には何種類かの方法があるが、
接続パラメータをまとめた接続プロファイルを使用するのが簡単かと思う。

* 接続パラメータを個別に`.vimrc`に設定
* 接続プロファイルを`.vimrc`に設定
* ウィザート的に接続パラーメタを指定(`<Leader>sbp`で利用可能)
* Modelineを使用して接続パラメータを設定

## 接続プロファイルの設定

プロファイルは好きなだけ作成可能。
詳細はヘルプに用例がたくさんあるのでそちらを参照。

```vim:.vimrc
" 環境に合わせてクライアントプログラムを変更
" (ver.20.0 からはデフォルトでsqlite3)
let g:dbext_default_SQLITE_bin = 'sqlite3'
" プロファイルの定義
let g:dbext_default_profile_MySQL_test = 'type=SQLSRV:integratedlogin=1:dbname=myDB'
let g:dbext_default_profile_SQLServer_test = 'type=SQLSRV:integratedlogin=1:dbname=myDB'
let g:dbext_default_profile_test = 'type=SQLITE:dbname=/dbpath/test.db'
"デフォルトで使用するプロファイルを指定
let g:dbext_default_profile = 'test'
```

## プロファイルの切り替え

プロファイルを切り替える場合は `:DBSetOption profile=test`として直接指定するか、
`<Leader>sbp`によるウィザートから選択する。

![スクリーンショット 2014-08-02 14.07.37.png](https://qiita-image-store.s3.amazonaws.com/0/14471/daf3ac3e-d59a-b625-e9d9-34b1f19d4cd6.png "スクリーンショット 2014-08-02 14.07.37.png")

# DBアクセス

## テーブル一覧取得

`<Leader>slt`とすれば画面下部のResultウィンドウに接続先DBのテーブル一覧が表示される。

```vim:Result
Connection: T(SQLITE)  D(/dbpath/test.db)   at 03:33
Tables
------
Alphabeticallistofproducts
OrdersQry                 
Categories
ProductSalesfor1997       
CategorySalesfor1997
Products                  
CurrentProductList
```

## テーブル内容表示

テーブル名にカーソルを置いて`<Leader>st`にてテーブル内容が表示される。
SQL文字列上で`<Leader>se`でもSQLが実行されその結果が表示される。

```vim:Result
Connection: T(SQLITE)  D(/dbpath/test.db)   at 03:35
TerritoryID  TerritoryDescription              RegionID  
-----------  --------------------------------  ----------
01581        Westboro                          1         
01730        Bedford                           1         
01833        Georgetow                         1         
02116        Boston                            1         
02139        Cambridge                         1         
02184        Braintree                         1         
02903        Providence                        1         
```

この時Javascriptファイルなどで、以下のように結合された文字列でも、`<Leader>se`にてちゃんとSQLとして解釈し実行してくれる。
なお、この場合はプレースホルダの値を入力するプロンプトが表示される。

![スクリーンショット 2014-08-09 23.18.02.png](https://qiita-image-store.s3.amazonaws.com/0/14471/b51b950f-707f-cf62-36a3-71a296062460.png "スクリーンショット 2014-08-09 23.18.02.png")

## 他のコマンド

他にもいろいろできますが、以下に一部だけ掲載。
テーブル以外にもViewやProcedureにも対応しているので利用したい場合はヘルプ参照。

デフォルトでのマッピングはすべて`<Leader>s`で開始される。
これは`g:dbext_map_prefix`で変更可能。

### normalモード

キー           | コマンド                       | 意味
-------------- | ------------------------------ | ---------------
\<Leader>sbp    | DBPromptForBufferParameters    | 接続先設定ウィザート
\<Leader>sh     | DBHistory                      | 実行履歴表示
\<Leader>slt    | DBListTable                    | テーブルリスト表示
\<Leader>sdt    | DBDescribeTable                | カーソル上のテーブル定義表示
\<Leader>sdta   | DBDescribeTableAskName         | 任意のテーブル定義表示
\<Leader>st     | DBSelectFromTable              | カーソル上のテーブルデータ表示
\<Leader>sta    | DBSelectFromTableAskName       | 任意のテーブルデータ表示
\<Leader>stw    | DBSelectFromTableWithWhere     | カーソル上テーブルデータ表示。Where条件指定可能
\<Leader>sq     | DBExecSQL                      | 任意のSQL実行
\<Leader>se     | DBExecSQLUnderCursor           | カーソル上のSQL実行

### visualモード

キー           | コマンド                       | 意味
-------------- | ------------------------------ | ---------------
\<Leader>sdt    | DBDescribeTable                | 選択テーブル定義表示
\<Leader>st     | DBSelectFromTable              | 選択テーブルの内容を表示
\<Leader>se     | DBExecVisualSQL                | 選択SQLの実行
 
# 補完

SQLファイルを開き、
例えば`O`を入力した状態で`<C-c>t`を押すと、Oで始まるテーブルリストが表示される。

![スクリーンショット 2014-08-09 23.21.52.png](https://qiita-image-store.s3.amazonaws.com/0/14471/f65c078f-0b82-e54b-decc-d81750ba0eac.png "スクリーンショット 2014-08-09 23.21.52.png")

この状態で`<Right>`を押すとそのテーブルのカラムリストが表示される。
![スクリーンショット 2014-08-09 23.22.42.png](https://qiita-image-store.s3.amazonaws.com/0/14471/4ed02c61-bd9e-a59f-e18b-bda7f59ae43a.png "スクリーンショット 2014-08-09 23.22.42.png")

単純に`<C-c>c`だけでもカラムリストが表示される。
その場合のテーブルは前回選択したテーブルとなるが、これはSQLの文脈によって動的に切り替わる。（テーブル別名に合わせて表示されるカラムリストが変わる）

![スクリーンショット 2014-08-09 23.22.57.png](https://qiita-image-store.s3.amazonaws.com/0/14471/6b49f3e2-ada4-d86e-07a8-1da40e56ef0f.png "スクリーンショット 2014-08-09 23.22.57.png")

ちなみにテーブルリストで`<C-c>L`押すとカラムを一気に出力できる。
この時の表別名`OD`は自動で生成され、出力時に表示されるプロンプト画面にて変更も可能。

![スクリーンショット 2014-08-09 23.23.36.png](https://qiita-image-store.s3.amazonaws.com/0/14471/306aff7e-f83a-6b73-bf03-932976b966f6.png "スクリーンショット 2014-08-09 23.23.36.png")

あるいはカーソルがテーブル上にある状態で`:DBListColumn`か`<Leader>slc`とすればカラム一覧がコピーされるので、それを貼り付けても同じようなことができる。

## キーマッピング

補完に関するキーマッピンはこんな感じ。インサートモードです。

キー         | 補完対象
------------ | -----------------
\<C-c>a       | syntax
\<C-c>k       | sqlKeyword
\<C-c>f       | sqlFunction
\<C-c>o       | sqlOption
\<C-c>T       | sqlType
\<C-c>s       | sqlStatement
\<C-c>t       | table
\<C-c>p       | procedure
\<C-c>v       | view
\<C-c>c       | column
\<C-c>l       | column_csv
\<C-c>R       | キャッシュクリア
\<Right>      | テーブルリストで押すとカラムリストへ
\<Left>       | カラムリストで押すとテーブルリストへ
\<C-c>L       | テーブルリストで押すとカラム一覧出力

### キーマッピング変更

`<Right>``<Left>`は遠いのでこれも変更可能。

```vim:.vimrc
let g:ftplugin_sql_omni_key = '<C-c>'
let g:ftplugin_sql_omni_key_right = '<C-d>'
let g:ftplugin_sql_omni_key_left  = '<C-u>'
```

## Javascriptからの利用

上記の補完はSQLファイルでしか使用できず、
Javascriptファイルにて補完したかったのでちょっと考えてみた。

### ファイルタイプの変更

単純に`:set ft=sql`でファイルタイプを変更すれば補完できる。
これはヘルプ(`:h ft_sql`)にも書いてある方法なのだが、後で戻す必要もありよろしくない。

### omnifuncの設定

JavascriptでSQLの`omnifunc`を利用できれば良いので以下のように設定する。
[neocomplete](https://github.com/Shougo/neocomplete.vim)を使用している前提。

```vim:.vimrc
autocmd FileType javascript setlocal omnifunc=sqlcomplete#Complete
if !exists('g:neocomplcache_omni_functions')
  let g:neocomplete#sources#omni#functions = {}
endif
let g:neocomplete#sources#omni#functions.javascript = ['javascriptcomplete#CompleteJS']
```

こうすれば普段はneocompleteによるJavascriptのオムニ補完がされ、
`<C-x><C-o>`にて明示的にオムニ補完した場合にはSQLの補完が行われる。

ただこの方法では`<C-c>t`などの補完は使えないので。

### SQLの補完を直接呼び出す

シンプルに必要な補完は明示的に割り当ててやる方法も採用した。
これでSQLの場合と同じ方法で補完が利用できる。

```vim:.vimrc
exec 'inoremap <silent> '.g:ftplugin_sql_omni_key_right.' <C-R>=sqlcomplete#DrillIntoTable()<CR>'
exec 'inoremap <silent> '.g:ftplugin_sql_omni_key_left.'  <C-R>=sqlcomplete#DrillOutOfColumns()<CR>'
exec 'inoremap <buffer> '.g:ftplugin_sql_omni_key.'t <C-\><C-O>:call sqlcomplete#Map("table")<CR><C-X><C-O>'
exec 'inoremap <buffer> '.g:ftplugin_sql_omni_key.'c <C-\><C-O>:call sqlcomplete#Map("column")<CR><C-X><C-O>'
exec 'inoremap <buffer> '.g:ftplugin_sql_omni_key.'l <C-\><C-O>:call sqlcomplete#Map("column-csv")<CR><C-X><C-O>'
exec 'inoremap <buffer> '.g:ftplugin_sql_omni_key.'R <C-\><C-O>:call sqlcomplete#Map("resetCache")<CR><C-X><C-O>'
```

# まとめ

個人的な用途としては十分利用可能になった。
ただJavascriptからSQLite接続でしか利用していないものの多少問題もある。

例えば..

* シングルクォーテーションで囲ったSQL文字列は実行できない(ダブルクオーテーションならOK)
* テーブル定義にてカラム名が`[ ]`で囲まれているとカラムリストが正しく生成されない

シングルクォーテーションの問題は、
コマンド呼び出しを少し修正してプラグインに渡す前に置換することで対応した。
また`[ ]`で囲ったテーブルは現在使用していないので今のところは関係ない。

最悪、DB毎で解析に使用される関数が以下のように分離されているので、
案外簡単に修正できそうなため問題が生じればその時に対応を考える感じでいく。

#### SQLITEの場合

* DB_SQLITE_describeProcedure
* DB_SQLITE_describeTable
* DB_SQLITE_execSql
* DB_SQLITE_getDictionaryProcedure
* DB_SQLITE_getDictionaryTable
* DB_SQLITE_getDictionaryView
* DB_SQLITE_getListColumn
* DB_SQLITE_getListProcedure
* DB_SQLITE_getListTable
* DB_SQLITE_getListView
* DB_SQLITE_stripHeaderFooter