Go言語でマルチDB対応ターミナルクライアント「SQL-Bless」を開発しています
その昔、Oracle の標準クライアント SQL*Plus に触れたとき、一通り必要な機能が手堅く実装されているものの、編集につらさを感じたものでした。当時、OS/2 用のコマンドラインシェルを開発していたこともあり、より高度な編集機能をもったクライアントを作りたいと思いましたが、ハードルの高さと忙しさで結局どうにもなりませんでした。
その後、幾星霜、エコシステムも信じられないほど充実してきました。時間もあったことから、開発に着手し、最近、ようやくそれなりに使えるものができてきました。それが SQL-Bless です。

特徴
SQL*Plus を手本とした、ターミナル用マルチDB対応のデータベースクライアントです。
快適な編集機能と、そして、本番環境の使用に耐える安全性・再現性の両立を目指しています。
1. 快適な SQL 編集(マルチライン編集)
編集には Emacs 風の編集キーが使え、テーブル名・カラム名などの補完も可能です。
それだけでなく、複数行にまたがる SQL のシームレスな編集を実現しています。わざわざ外部エディターを起動しなくても、↑キーや Ctrl-P でSQL中の1行目まで戻って修正できます。また、それよりさらに上に移動しようとすると、普通の Readline のヒストリのように過去の入力内容を呼び出すこともできます。
2. スプレッド風テーブル編集 (edit文)
ターミナル用CSVエディター(csvi)を内蔵しており、ターミナルにいながら、テーブルのデータをスプレッド風に編集することができます。データを更新する SQL は 編集終了時に個別にユーザの確認の上で実行しますので 「気づかないうちに更新されていた」というような事故を防げます。
更新はトランザクションの中で行いますので、ロールバックも可能です。
3. 5種類のDBに対応
Oracle だけでなく、SQLite3、MySQL、PostgreSQL、Microsoft SQL Server に対応しています。
4. 高い安全性・記録性
最初からオートコミット「オフ」の状態で始まります。ユーザが UPDATE や INSERT などの DML を発行した時点で自動的にトランザクションが始まるため、「開始を忘れてうっかり編集してしまう」といったミスを防ぎます。
また、作業のログは SPOOL 文でがとることができます。
ログ出力例
# (2023-04-17 22:52:16)
# select *
# from tab
# where rownum < 5
TNAME,TABTYPE,CLUSTERID
AQ$_INTERNET_AGENTS,TABLE,<NULL>
AQ$_INTERNET_AGENT_PRIVS,TABLE,<NULL>
AQ$_KEY_SHARD_MAP,TABLE,<NULL>
AQ$_QUEUES,TABLE,<NULL>
# (2023-04-17 22:52:20)
# history
0,2023-04-17 22:52:05,spool hoge
1,2023-04-17 22:52:16,"select *
from tab
where rownum < 5"
2,2023-04-17 22:52:20,history
- 各SQL とその発行した日時、結果が添えられます
- SELECT 結果は最初から CSV データ化されます
- 画面上では CSV エディターが起動しているが、バックグランドで読み込みとSPOOL記録は進みます
-
edit文については出力の量が莫大になりがちなので、今のところINSERT/DELETE/UPDATEなどのSQL履歴に限定しています
- 過去の
SPOOLログをうっかり上書きしないよう、常に追記です - SPOOL中はプロンプトの前に
Spooling to 'debug.lst' nowなどとスプール先ファイル名が常に表示されますので、「今、ちゃんとスプールされてるか?」という不安がありません
5. 設定ファイルレス・単品動作
すべての設定はコマンドラインオプションで指定します。これは、どこにあるとも分からない設定ファイルに影響されて予期しない動作を避けるためです。とはいえ、データベースの接続先の指定も結構なパラメータが必要となるので、通常は接続先ごとに、シェルのエイリアスかバッチファイルなどをご用意していただく形になると思います。
例: run-oracle.bat
@rem Sample script to launch SQL-Bless with Oracle DB
@setlocal
@set PROMPT=$G$S
@rem $ sqlbless oracle://USERNAME:PASSWORD@HOST:PORT/SERVICE
sqlbless %* oracle://scott:tiger@localhost:1521/xepdb1
このように、環境変数や設定ファイルを必要とせず、接続文字列を明示的に扱えるため、動作の再現性を保ちやすくなっています。さらに、実行ファイルは Go 言語製の単体バイナリなので、コピーするだけですぐに使えます。
苦労
データベース間の仕様の違いは "database/sql" がある程度吸収してくれますが、それでも汎用ツールとなるとたいへんです。
- テーブル一覧・カラム一覧の取り方
- 各DBでディクショナリー(メタ情報)テーブルの仕様が違うのは当然で、専用のSQL構文を使うものもある(例: SQLite3 の
PRAGMA TABLE_INFO(テーブル名))。
- 各DBでディクショナリー(メタ情報)テーブルの仕様が違うのは当然で、専用のSQL構文を使うものもある(例: SQLite3 の
- プレースホルダー・バインド変数の書式
-
?,$1,:varnameといった表現の違いだけでない。必ずsql.Namedで名前と値をペアにしなければいけない場合(MS SQL Server)もあれば、ペアそのものがない場合もある
-
- 日付・時刻型のバリエーション
- 明確な日時型があるDBもあれば、単なる列属性のヒントにすぎないDBもある。
2006-01-02 15:04:05と2006-01-02T15:04:05Zと違う表現なのに同じものを意味するケースにはたいへん困った。
- 明確な日時型があるDBもあれば、単なる列属性のヒントにすぎないDBもある。
- トランザクションの扱い
- 強制コミットする SQL などの前に警告を出す、プロンプトでトランザクション中かどうかを出すような機能を実装したのはよいが、強制コミットする SQL は当然ながらバラバラ
最終的には、各DBの違いを扱う構造体を定義し、そこで一元管理するようにしました。その内容については、またいずれ別途記事を設けて解説したいと思います。
一方、本ツールの目玉であるマルチライン編集やスプレッド編集については、前々から汎用ツールやライブラリとして各パッケージの協力者と共に育ててきたモジュールを活用しているため、導入に大きな苦労はありませんでした。
SQL-Bless の開発では、それらの既存モジュールをさらに発展させ、全体をクリーンアーキテクチャ化しました。その過程で、端末からのキー入力も次のような interface 仕様で仮想化し、テスト時に差し替えできるようにしました。
type Tty interface {
Open(onResize func(int, int)) error
GetKey() (string, error)
Size() (int, int, error)
Close() error
}
これにより、edit コマンドを含む全操作の自動テストが可能となり、現在は SQLite3 のメモリDB を対象とした疑似操作テストが GitHub Actions 上で動作しています。
開発のこれから
SQL-Bless は、まだ「完成品」というよりも「これから育てていきたいツール」です。自分自身が長年 Oracle に慣れ親しんできたこともあり、操作感はどうしても Oracle 寄りになっています。
その一方で、MySQL や PostgreSQL、SQLite などの文化に根ざした使い勝手については、まだ改善の余地があります。
また、最近はデータベースを日常的に扱う仕事から離れてしまっていることもあり、 「本番運用での使い勝手」や「業務でのドッグフーディング」が足りません。
もしこの記事を読んで「試してみようかな」と思ってくださる方がいれば、ぜひ声を聞かせてください。 Pull Request や Issue はもちろん、軽い感想でも大歓迎です。安全性と再現性を両立しつつ、現場で本当に使える SQL クライアントへと育てていきたいと考えています。
Discussion