Blog
Posted on:
Many of the blogs are written in Japanese.
Items written in English are in the English tag.
- Introduction to the Go library
tcellansi. Repository: https://github.com/noborus/tcellansi
- Configuring PAGER in psql Turning PAGER on/off In psql, you can set the PAGER to on/off/always using the pset value. When set to always, the PAGER will always start even if the output fits on the screen when psql starts. To temporarily turn off PAGER with an option, use the following (you can specify on or always instead of off):

- Background An issue with specifying column numbers in ORDER BY gained attention through tom__bo’s article about behavioral changes in prepared statements in 8.0.22. In the article, the following was mentioned: Details For a prepared statement of the form SELECT expr1, expr2, … FROM table ORDER BY ?, passing an integer value N for the parameter no longer causes ordering of the results by the Nth expression in the select list; the results are no longer ordered, as is expected with ORDER BY constant.

- For a long time now, Unix-like commands and CLI have been outputting width-aligned output. ls, ps, df, etc… Such output can be produced by a printf format specification. If this format specification is known, it can be read by scanf, but it is not easy to recognize and read columns from “just the output”. If multiple spaces are considered as delimiters, it is possible to divide the output into columns, but headers and values may cause unnecessary division. We have created a library/tool to guess the column width so that this can be read by humans.

- PostgreSQLにおける列数0のSQL文とテーブルの扱いについて

- 手っ取り遅くSQLを学ぶ 「手っ取り遅くSQLを学ぶ」とは?すぐにSQLを使えるよう学ぶのではなく、まわりくどくSQLを理解して学んでいく方法で解説します。 「手っ取り早くSQLを学ぶ」と言ったら、よく使うSQLをなるべくシンプルな形にして、実際に試しながら、徐々に応用していく方法だと考えています。 「手っ取り遅くSQLを学ぶ」はその逆で、SQLの記法は後回しにして考え方を解説していきます。 まずはテーブル SQLは主にリレーショナル・データベース(RDBMS)で使用されるデータ操作のための言語です。 リレーショナル・データベースは「テーブル」をデータの集合として使用しているので、SQLを学ぶには、まずテーブルを理解する必要があります。 テーブルとは、訳すと「表」なので、コンピューターと関係なくても日常でも溢れている表現方法です。 名簿であったり、一覧表など一般に、よく目にします。 SQLで扱うテーブルは、表全般の中でもう少しルールが定まったものを対象とします。 他のプログラミング言語と同じく「型を持った値」があります。数値の123や文字列の’abc’等。 この「型を持った値」を複数持つことができます。これをレコードと言ったり、「行」と言ったりします。 このレコードが含んでいる値の数と型が一致している集合がテーブルです。 通常は、単純な数合わせではなく、型が一致しているだけでもなく、同じ意味を表しています。これをカラムと言ったり、「列」と言ったりします。 このカラムには名前を付けられることが多いです。「格納」する場合は、あとで取り出す必要があるので名前が必須になります。 この集合をレコードの配列と考えても良いかもしれませんが、この時点では順序は不動なことに注意が必要です。 他のプログラミングからは、2次元配列で表される場合もありますが、プログラミング言語を理解している人は、構造体の配列と考えた方が良いでしょう。実際にSQLで操作したデータをプログラミング言語で受け取るときには、構造体の配列に変換することがよくあります。 そして、テーブルの行も列も理論上は0以上無限まで可能です。 実際には上限はデータベースの仕様やコンピューターの限界値に左右されますが、下限は0から可能です。 行が0というのは、たとえば名簿リストを作成したとして該当者が存在しないことはあるのでよくある話だと思いますが、列が0というのは理論上存在しても現実にはないので、定義はできないようにしているデータベースもあります。 この「テーブル」単位にしておけば、自分の欲しいデータを取得できると考えて作られたのがリレーショナル・データベース(RDBMS)で、そのための記法がSQLです。 SQLとは? リレーショナル・データベースは、SQLを実行できるだけでなくテーブルを効率よく管理するためにいろいろな機能が備わっていますが、主要なSQLはテーブルを操作するため、少し切り離して考えることができます。 SQLはテーブル(複数でも可)から「1つの」テーブルを作ることを目的としています。 SQLをすでに知っている人からすると、テーブルを作ると言ったら「CREATE TABLE」と思うかもしれません。 データベースにテーブルを作るという狭い意味ではそうなのですが、その結果がテーブルになるという意味で広い意味では、テーブルを作るという目的であると言えます。 INSERT/UPDATE/DELETEなどの更新系SQL文は、実行した結果がデータベースに反映されます。

- Aggregate json with trdsql I agree that the aggregation of jq described in the Introducing zq is not easy. I’ve seen A Practical Example of zq, zq was not easy for me. SQL is not easy for everyone, but it is a language that many people can use. I am one of them. Of course, it is difficult to process all JSON with SQL. But what about using them in combination?

- 「作ったツール紹介」というタイトルで発表しました https://pgunconf.connpass.com/event/240528/ 自分で作った以下のツールを紹介してます。 trdsql ov pgsp jpug-doc-tool また、ovの関連でページャーとして、lessとpspgも紹介してます。 とくに、lessはまだ正式リリース版ではないですが、ヘッダーオプションが追加されるということで、PostgreSQLに限らず全DBのCLIを使っている人にとって、朗報だと思います。 また、ヘッダー固定が可能なことを前提にすると他のアプリケーションの作り方も変わっていくものだと思っています。 jpug-doc-toolでみんなの自動翻訳@TexTraを使用できるようにしていることを紹介しました。 みんなの自動翻訳の説明はだいぶ省略しましたが、対訳語の登録や対訳集を学習させる等によりカスタマイズエンジンをみんなで育てることができれば、PostgreSQL向けの翻訳精度が上がっていくと思うので、利用者を増やしたいところです。

- PostgreSQLの日本語マニュアルのヘルパーツール PostgreSQLのマニュアルの日本語への翻訳に参加していますが、その翻訳に役立つツールとしてjpug-doc-toolを公開しました。 基本的な方針 PostgreSQL日本語マニュアルについては前に書いた手順と大きく変わっていませんが、SGMLの拡張子のままですがXMLとして解釈されるように変わっています。 XMLのライブラリを利用して読むことは可能になっていますが、PostgreSQLに入っているドキュメントは自動フォーマットされてレポジトリに入っていないため、XMLの処理系で処理して書き出すと元ドキュメントの比較が難しくなってしまいます。 そのため、XMLライブラリは使用せず、正規表現を使用し、必要なら最小限の書き換えをする作りになっています。 使い方 サブコマンドにより文書のチェックや抽出、書き換えができます。 $ jpug-doc-tool jpug-doc の翻訳を補助ツール。 前バージョンの翻訳を新しいバージョンに適用したり、 翻訳のチェックが可能です。 Usage: jpug-doc-tool [command] Available Commands: check 文書をチェックする completion Generate the autocompletion script for the specified shell extract 英語と日本語翻訳を抽出する help Help about any command list 辞書から英語と日本語訳のリストを出力する mt APIを使用して文字列を翻訳する replace 英語のパラグラフを「<!–英語–>日本語翻訳」に置き換える Flags: –config string config file (default is $HOME/.jpug-doc-tool.yaml) -h, –help help for jpug-doc-tool -t, –toggle Help message for toggle Use "jpug-doc-tool [command] –help" for more information about a command. jpug-doc-toolはpostgresql/doc/src/sgml/で実行します。拡張子sgmlのファイルを指定するとそのファイルを対象にします。指定しなかった場合はドキュメントを全て対象とします。

- mdtsql v0.0.3をリリースしました mdtsql v0.0.3をリリースしました。 Markdown テーブルに対してSQLを実行できるツールです。 README.mdやGitHubのWikiなどで、Markdownのテーブルを書くことがありますが、ドキュメント翻訳担当リスト14.0のようにテーブルが大きくなる場合に手で編集するのも大変ですが、探し出したり、集計したり、更新したり、といった作業が面倒になることがあります。 そこでtrdsqlのモジュールを使って、Markdown Tableに対してもSQLを実行できるようにしました。 trdsqlは既に様々なフォーマットに対して実行できるようになっているため、Markdownを追加しても良いわけですが、一緒にするにはMarkdown用のオプションが必要になるのもどうかと思って、別にしてあります。 mdtsqlはMarkdownファイルに対して実行すると解析してテーブルがあればテーブル情報を表示します。markdownにテーブルは複数含むことができるため、Markdown内のテーブルを指定するテーブル名をここで得ます。 「ドキュメント翻訳担当リスト13.1.md」というファイルに実行してみます。 $ mdtsql ドキュメント翻訳担当リスト13.1.md Table Name: [ドキュメント翻訳担当リスト13.1] +————-+——+ | column name | type | +————-+——+ | ファイル名 | text | | 担当者 | text | | 進捗 | text | | 備考 | text | +————-+——+ Table Name: [ドキュメント翻訳担当リスト13.1_2] +————————+——+ | column name | type | +————————+——+ | お名前 | text | | マニュアルへの表記 | text | | マニュアルへの記載可否 | text | | 主な貢献内容 | text | +————————+——+ テーブル名がわかったら、-q SQL文でSQLを実行できます。

- pgsp は PostgreSQLの pg_stat_progress viewを監視して表示するCLIツールです。 pg_stat_progress PostgreSQLでは、いくつか時間がかかる処理に対して進捗状況が見られるViewがあります。 Viewの名前は pg_stat_progressではじまり、analyze, cluster, create_index, vacuum, basebackup (version 14からは copyが追加される予定)などが取得できます。 詳しくは progress-reportingを参照してください。 これらのViewは処理が始まったときにレコードが追加されて、変化する処理状況(フェーズや処理した数)を更新していき、終了するとレコードが消えます。SQLで簡単に確認できるので便利ですが、常に更新されていくため、状況を逐一見たいときにはpsqlでは\watch等を利用してSELECTを繰り返して見る必要があります。 SELECT * FROM pg_stat_progress_analyze; pgsp pgspはこれらのViewを監視して表示する専用のCLIツールです。Go製です。 やっていることはシンプルでpg_stat_progress_* のViewを定期的にSELECTで取得し、レコードが追加、更新されたら進捗に相当する数値でプログレスバーを更新します。 PostgreSQLに普通に接続にいくので接続情報(ホスト、ポート、ユーザー、パスワード等)が必要です。 –dsnで設定してください。 pgsp –dsn 'host=ホスト名 port=ポート番号 user=ユーザー名 password=パスワード' UNIXドメインソケットを使用する場合はhostにpathを書きます。

- 概要 goのTUIについての2020年の最終更新版です。 goでTUI(text user interface)を作成する場合にライブラリを使用するのが一般的です。 goのTUIライブラリはだいたい以下に分類されます。 termbox-go系 tcell系 bubbletea系 その他 goのTUIライブラリはtermbox-go系、tcell系の利用が多かったですが、彗星のごとくbubbleteaが登場しました。 bubbleteaはThe Elm Architectureに基づいて作られているというフレームワークで、追加のコンポーネントとしてbubblesもあり、もう一つの系統として選択肢になると思います。 TUIライブラリを謳っている場合は、だいたい上記3つを元に実装されている場合が多いです。 TUIはエスケープシーケンスを使用すれば、ライブラリを使用しなくても実現できますが、端末によりエスケープシーケンスが変わっていたりするので、マルチプラットフォームで動作するのは難しくなります。 そのため、独自に一から作成するよりは、これらのライブラリの上に便利な機能を足す形になります。 termbox-go系 termbox-goは、老舗で現在も多く使われていますが、開発は停滞傾向で、termbox-goにもそれほど保守しない方向だと書かれています。 termbox-goを使用して、より高度なウィジットを実装したライブラリにgocuiがあります。 termbox-go gocui termui termbox-goのimported by tcell系 tcellは、termbox-goよりも新しくtermbox-goを意識して開発され、今も開発も続いています。 tcellは基本的な機能しか提供しませんが、tcell/viewsには、少し高度なウィジットがあります。

- cbindとは? cbindはtcellのキーイベントとイベントハンドラを結びつけるライブラリです。 tcellのキーイベント tcell ではキー入力がイベントの1つとして取得できます。 tviewでもtcellのイベントを使用しているので、同じ様にイベントとして取得します。 tcellのキーイベントを取得するのは以下のようにswitch caseでキーを判別して、イベントハンドラを呼び出すのが一般的です。 ev := screen.PollEvent() switch ev := ev.(type) { case *tcell.EventKey: switch ev.Key() { case tcell.KeyEscape: close(quit) return } case tcell.KeyEnter: action() return } ここのtcell.KeyEscapeは constの数値として定義されています(キーボードに存在する英数字などの文字はruneで入ってきます)。 キー割り当てが少ないうちは、このまま追加していけば機能を増やせるので分かりやすいですが、キー割り当てが多くなってくると以下のような問題が出てきます。 修飾キー(CTRL、ALT…)が押された場合に動作が変わる場合はさらに分岐する キー割り当てをドキュメント化するのが大変になる キー割り当てのヘルプが必要になる キー割り当てを人によって変更したくなる ドキュメント化やヘルプはコードで実装した後、手間を掛けて書いていけばなんとか解決できますが、キー割り当ての変更に対応するには、元のままのコードでは不可能です。 cbindを使用 そこで使用したいのがcbindです。

- これまで goのTUIについて tcellについて イベント tcellのイベントは、NewScreen()で作成したスクリーンのPollEvent()で取得できます。 その名の通り、イベントが起こるまでポーリング(polling)して待つので、起こらない限り止まったままになります。 PollEvent()でイベントが起こったらイベントに応じて処理し、SetContent()でセットし、次のイベントが起こる前にDraw()で描画する。 というのが、実際のメインルーチンになります。 このメインルーチンをgoroutineで動かし、終了のイベントがきたらchannelに通知して通知を受信したらFini()を実行して終了するのが一般的な流れです。 キーイベント イベントの中でも重要でよく使用するのがキーイベントです。 以下のプログラムは左上に打ったキーが表示されます。ESCキー又はEnterキーで終了します。 package main import ( "log" "time" "github.com/gdamore/tcell" ) func main() { screen, err := tcell.NewScreen() if err != nil { log.Fatal(err) } if err = screen.Init(); err != nil { log.Fatal(err) } defer screen.Fini() screen.SetContent(0, 0, '_', nil, tcell.StyleDefault) quit := make(chan struct{}) go func() { for { screen.Show() ev := screen.PollEvent() switch ev := ev.(type) { case *tcell.EventKey: switch ev.Key() { case tcell.KeyEscape, tcell.KeyEnter: close(quit) return case tcell.KeyRune: screen.SetContent(0, 0, ev.Rune(), nil, tcell.StyleDefault) time.Sleep(time.Millisecond * 100) } } } }() <-quit }

- SetContent() goのTUIについてで書いたようにtcellのSetContent()は1文字設置していくのでASCIIの範囲内だと簡単ですが、Unicodeの世界では注意すべき点があります。 まず日本語などの全角幅の文字と半角幅の文字が混在すると全角幅のときには、次の文字は1つとばして設置するといったことが必要になります。 単純に実装する場合はrunewidth.RuneWidth()を使用すれば、runeの文字幅を0,1,2で返してくれるので、その分xをずらせば表示されます。以下が実装例です。文字列を渡せるsetContents()で処理しています。 package main import ( "log" "github.com/gdamore/tcell" "github.com/mattn/go-runewidth" ) func setContents(screen tcell.Screen, x int, y int, str string, style tcell.Style) { for _, r := range str { screen.SetContent(x, y, r, nil, style) x += runewidth.RuneWidth(r) } } func main() { screen, err := tcell.NewScreen() if err != nil { log.Fatal(err) } if err = screen.Init(); err != nil { log.Fatal(err) } defer screen.Fini() setContents(screen, 0, 10, "あいうえお", tcell.StyleDefault) screen.Show() quit := make(chan struct{}) go func() { for { ev := screen.PollEvent() switch ev.(type) { case *tcell.EventKey: close(quit) } } }() <-quit } 上記では結合文字は無視されて表示されます。結合文字を出したい場合は、文字列をループしている中でruneが結合文字のコード範囲だった場合には、前の文字のcombcに入れた上でSetContent()を実行する必要があります。

- 概要 ライブラリの状況を鑑みてgoのTUIについて2020年最終版に更新しました。 goでTUI(text user interface)を作成する場合にライブラリを使用するのが一般的です。 goのTUIライブラリはだいたい以下に分類されます。 termbox-go系 tcell系 その他 TUIライブラリを謳っている場合は、だいたい上記2つを元に実装されている場合が多いです。 TUIはエスケープシーケンスを使用すれば、ライブラリを使用しなくても実現できますが、端末によりエスケープシーケンスが変わっていたりするので、マルチプラットフォームで動作するのは難しくなります。 そのため、独自に一から作成するよりは、これらのライブラリの上に便利な機能を足す形になります。 termbox-go系 termbox-goは、老舗で現在も多く使われていますが、開発は停滞傾向で、termbox-goにもそれほど保守しない方向だと書かれています。 termbox-goを使用して、より高度なウィジットを実装したライブラリにgocuiがあります。 termbox-go gocui termui termbox-goのimported by tcell系 tcellは、termbox-goよりも新しくtermbox-goを意識して開発され、今も開発も続いています。 tcellは基本的な機能しか提供しませんが、tcell/viewsには、少し高度なウィジットがあります。 また、より高度なウィジットを実装したライブラリとしてtviewがあり、よく使用されています。 tcell tcell/views tview gowid goban tcellのimported by その他 termbox-goとtcellはいずれも端末画面をまるまる使用することを前提に作られています。起動すると現在の端末画面は消えて(終了時に戻すことは可能)、新しい画面が表示されます。

- 結論 gnome-terminalを使用している場合は、設定の「曖昧幅の文字(W)」と環境変数RUNEWIDTH_EASTASIANを一致させよう。 Ambiguous width(曖昧幅) ターミナル上のアプリケーション(TUI)では、GUIと違って文字単位で描画されます。 そして1文字の幅は固定されていて、アルファベットは1文字分とすると日本語などは2文字分使用する、いわゆる半角全角の世界です。 ただし、既にUnicode(UTF-8)が標準となっているので、バイト数と文字幅は関係しないようになっています。 Unicodeでは幅が決まっている文字がほとんどですが、一部に「Ambiguous; 曖昧」とされている文字があります。 以前は英語圏のアプリケーションではASCIIの範囲内のみを使用していて「Ambiguous」な範囲の文字を使用するのは、それ以外の地域の人だったため、全角幅で問題になることは無かったのですが、Unicodeの使用が拡大するにつれて英語圏の方が作るアプリケーションでも「Ambiguous」な幅の文字が使用されることが増えてきました。 特にTUIアプリケーションでは、罫線「┌ ├ ─ ┘等」を使用して枠線を表現することがあります。これが「Ambiguous」な幅として、英語圏では1文字幅で表示できる様になっているため、2文字幅と解釈して表示しようとすると表示が崩れてしまいます。 ターミナル上で罫線を使用したプログラムがズレる場合はこれが原因です。 そのための対応として、gnome-terminalではPreferencesから「曖昧幅の文字(W)」を半角/全角で変更出来るようになっています。 これを半角にすれば、罫線が1文字幅で表示されるため、表示が直ります。 例えば、psqlの \pset linestyle unicodeをgnome-terminalで使用するには、ここを半角にしておかないと縦の線が揃わなくなります。

- 前提 LOAD DATA INFILEはMySQLサーバーがファイルを読み取ってデータベースのテーブルにインポートする構文ですが、LOAD DATA LOCAL INFILEはクライアント側のファイル(の内容)をサーバー側に送信してインポートします。 このLOCAL指定ですが、セキュリティ上の問題を抱えているため、最近のバージョンだとデフォルトで使用できない設定に変更されたりしています。 そもそも LOAD DATA LOCAL INFILE の仕組みは、MySQLのLOAD DATA構文を(クライアント側ではパースして解釈しないので)サーバー側に送ってLOCAL INFILEの場合ファイル名をクライアントに伝えて、クライアントがそのファイル(の中身)をサーバー側に送信するようになっています。 サーバー側からLOAD DATA LOCAL INFILEに書いてあったファイル名とは違うファイル名を伝えられてもそのファイルを送信してしまう可能性があるため、セキュリティのリスクがあります。 LOAD DATA LOCAL INFILE(go) goのmysqlドライバでは、LOAD DATA LOCAL INFILE supportにあるように mysql.RegisterLocalFile(filepath)やmysql.RegisterReaderHandler(name, handler)という関数が追加されていてセキュリティ上の問題を解決するような拡張がされています。 mysql.RegisterLocalFile(filepath)は、LOAD DATA LOCAL INFILEを実行する前にあらかじめ送信するファイル名を登録しておいて、登録してあるファイルのみを送信することでリスクを軽減しています。 mysql.RegisterLocalFile("/tmp/test.csv") db.Exec("LOAD DATA LOCAL INFILE '/tmp/test.csv' INTO TABLE test") また、mysql.RegisterReaderHandler(name, handler)では、あらかじめクライアントプログラム側がファイルを開く等してできたio.Readerインターフェイスを<name>と共に登録しておき、LOAD DATA LOCAL INFILE ‘Reader::<name>’ INTO TABLE テーブル名 によりio.ReaderからReadしてサーバー側に送信します。
