ThunderbirdでGmail送信ができなくなったらSMTPの認証設定を確認しよう

自分は長いことメーラーとしてはずっとThunderbirdを使っていて、仕事場でも自宅でもGmailThunderbird経由で使っています。さらに大昔になると Becky とか使ってたのですが、一時期macユーザーになった時にApple純正のメーラーが余りにも使いにくかったので、Windowsによくあるようなメーラーを探していたところThunderbirdが良かろうということで使うようになり、以来Windowsに戻ってもThunderbirdを使っているという経緯です。

で、家の方で先程Thunderbirdからメールを送信しようとしたら認証で引っ掛かりました。パスワードが正しいのにどうして?そういや大分前からGmailサードパーティーツールとの連携にはOAuth2になったからパスワード入力する時点でおかしいのでは?と気付きSMTP認証設定を開いてみたところ [認証方式]通常のパスワード認証 になっていました…。元々Gmailへの連携設定はThunderbirdのウィザード経由で行っていたのですが、それが大分昔のことだったので古い設定にままになっていたのですねえ。

正しくは次のように OAuth2 に設定する必要があります。

ThunderbirdのSMTP認証設定ダイアログ
ThunderbirdSMTP認証設定ダイアログ

この設定を行って改めてメール送信するとGoogleの認証ダイアログが開き、認証することで送信可能になります。

しかしGmailサードパーティー連携にOAuth2を要求するようになったのは大分前の話のはずで、次の記事にあるように2022年5月時点ではそうなっていました。

support.mozilla.org

つまり長いことプライベート用途でメールを送信する機会がなく、送られてくるメールを読むだけだったのですね。仕事ですらメールを使うことはぐっと減りましたし、本当にメールを使う機会が減ったんだなあと改めて実感することになりましたよ。

人生初の大腸内視鏡検査を受けてきた

健康診断の便検査で2つ提出したサンプルのうち片方で潜血が出たため、精密検査として人生初の大腸内視鏡検査を受けてきたのでそのメモでも。 ちなみに昨年の健康診断ではこれまた人生初の胃カメラを体験してたりします。

検査は午後で、午前中は腸洗浄を行います。これは2時間くらいを掛けて特別な下剤を 2リットル も飲み、腸の中身を全部吐き出すというものです。終わってみればこれが一番大変だった。病院で行うことも多いようですが自分の場合はこれを自宅でやってから病院へ行くことになりました。

検査は鎮静剤を投入して行いました。昨年の胃カメラでは何を考えたのか鎮静剤なしでやったら非常に辛かったので今回はあり一択。 鎮静剤は寝てしまうことはなく、ぼうっとした感じになるというもので、どちらかというと恐怖心を和らげる効果の方が大きかったように思います。検査の間も「何かやっているなあ」とぼんやり感じている程度で、気付いたら終わっていました。

もっともこの点は個人差が大きいようで、他の患者には検査後にいびきをかいて寝ている人もいましたね。自分は検査後の休憩時間も完全に寝ることはなかったです。

検査の結果は特に問題がなかったようでほっとしました。休憩中に他の患者が「大きいポリープは今回取れなかったので別途入院です」とか「直腸ガンですね」とか言われているのが耳に入って流石に怖かった😨

今回検査を受けた病院は色んな動物や魚を飼っていました。よかったら見てやってください。

病院で飼っている大型淡水魚たち(タイガーシャベルノーズ、ダトニオプラスワン、アジアアロワナ)
病院で飼っている大型淡水魚たち

病院で飼っている2匹のデグー。片方は回し車で遊んでいる。
デグー

病院で飼っている古代魚のポリプテルス。水槽に下に説明書きが貼られている。
ポリプテルス

病院で飼っているイグアナ
イグアナ

病院で飼っているハッカン。「老夫婦のため体が衰えています。どうかそっと静かに見守ってください!」の注意書きが貼られている。
ハッカン(白鷴)

Iceaxeを使ってTsurugiのSQLダンプを出力する

このエントリは Tsurugi Advent Calendar 2024 の22日目のエントリです。前日は septigram さんによる「 劔"Tsurugi"をZabbixで監視する 」でした。

以下の前回のエントリではTsurugiに用意されているダンプ、ロード方法を紹介しました。

aoe-tk.hatenablog.com

前回のエントリの最後に書いたのですが、他のRDBMSではSQL文によるダンプを行えるものがあります。 現時点でTsurugiにはそのような機能はありませんが、Iceaxeに用意されている機能をうまく活用することで、SQLファイルとしてデータをダンプすることができます。今回はその方法について案内します。

なお、今回説明するコードの全体はgistにアップしているので、そちらをご参照ください。

SQLファイルとしてTsurugi上のテーブルデータをダンプするコード · GitHub

このプログラムでは外部からDBのURL dbUrl 、テーブル名 tableName 、出力先ディレクトoutputDir を受け取ります。 まず、出力先ファイル名とそのパス、DB接続のための TsurugiConnector オブジェクト、データ取得SQLを準備します。

var fileName = "insert_" + tableName + ".sql";
var filePath = outputDir.resolve(fileName);
var connector = TsurugiConnector.of(dbUrl);
var sql = "select * from " + tableName;

続いてDBへのセッション確立とファイルオープンを try-with-resources 文のtry句の中で行います。

try (TsurugiSession session = connector.createSession();
        var writer = new PrintWriter(Files.newBufferedWriter(filePath, WRITE, CREATE, TRUNCATE_EXISTING))) {

以降は PrintWriter#println() メソッドを使って、出力先ファイルにSQLを書き込んで行くことになります。 ファイルの先頭にLTXでトランザクションを開始するSQLを記述します。

    writer.println("start long transaction write preserve " + tableName + ";");

Iceaxeの TsurugiSession#findMetadata() メソッド を使うとテーブルのメタデータ情報 TgTableMetadata を取得することができます。

    var metadata = session.findTableMetadata(tableName).orElseThrow();

このオブジェクトの getLowColumnList() メソッドでテーブルの全ての列のメタデータ情報 com.tsurugidb.sql.proto.SqlCommon.Column を取得できます。このオブジェクトに列の型情報や名称が含まれています。

    List<? extends SqlCommon.Column> columnList = metadata.getLowColumnList();
    var columnNameList = columnList.stream().map(SqlCommon.Column::getName).toList();

テーブル列のメタデータ情報が取得できたので、実際にデータを取得し、その結果を使ってinsert文を組み立てることにします。

    var tm = session.createTransactionManager(TgTxOption.ofRTX());
    var resultList = tm.executeAndGetList(sql);
    for (TsurugiResultEntity entity : resultList) {
        var builder = new StringBuilder("insert or replace into ");
        builder.append(tableName).append(" (").append(String.join(",", columnNameList)).append(") values (");
        var valuesString = columnList.stream().map(column -> getValueString(entity, column)).toList();
        builder.append(String.join(",", valuesString)).append(");");
        writer.println(builder);
    }

取得したレコードごとに insert or replace 文を組み立てます。

先に取得した列名称のリスト columnNameList を使って、 insert or replace into <テーブル名>value 句の間に記述する列名称を出力している点に留意してください。

        builder.append(tableName).append(" (").append(String.join(",", columnNameList)).append(") values (");

value 句の値の出力は次の部分で行っています。

        var valuesString = columnList.stream().map(column -> getValueString(entity, column)).toList();
        builder.append(String.join(",", valuesString)).append(");");

列情報のメタデータ columnList の情報を使って、列に対応した値を TsurugiResultEntity から取得して、値の文字列を出力しています。 具体的な処理は次の getValueString() メソッドの中で行っています。

private String getValueString(TsurugiResultEntity entity, SqlCommon.Column column) {
    var atomType = column.getAtomType();
    var columnName = column.getName();
    return switch (atomType) {
        case INT4 -> entity.findInt(columnName).map(value -> Integer.toString(value)).orElse("NULL");
        case INT8 -> entity.findLong(columnName).map(value -> Long.toString(value)).orElse("NULL");
        case FLOAT4 -> entity.findFloat(columnName).map(value -> Float.toString(value)).orElse("NULL");
        case FLOAT8 -> entity.findDouble(columnName).map(value -> Double.toString(value)).orElse("NULL");
        case DECIMAL -> entity.findDecimal(columnName).map(BigDecimal::toPlainString).orElse("NULL");
        case CHARACTER -> entity.findString(columnName).map(value -> "'" + value + "'").orElse("NULL");
        case DATE -> entity.findDate(columnName).map(value -> "date '" + value + "'").orElse("NULL");
        case TIME_POINT -> entity.findDateTime(columnName).map(value -> "timestamp '" + value + "'").orElse("NULL");
        case TIME_POINT_WITH_TIME_ZONE ->
                entity.findOffsetDateTime(columnName).map(value -> "timestamp with time zone '" + value + "'")
                        .orElse("NULL");
        default -> "NULL";
    };
}

SqlCommon.Column#getAtomType() メソッドで列の型情報を取得できます。 型情報を元に TsurugiResultEntity の適切なデータ取得呼び出しを行い、型に応じたリテラルを出力しているのが分かると思います *1

date 型や timestamp 型の場合は演算子を付与している点にも注意してください。また、TsurugiのSQLにおける日付、時刻リテラルはISO-8601のフォーマットに対応しているので、 toString() の結果をそのまま出力しても大丈夫です。

このようにして作成したプログラムで出力したSQLダンプは次のようになります (見やすいように適宜改行していますが、insert文は実際には一行です) 。

start long transaction write preserve dump_test;
insert or replace into dump_test (
  c_bigint,
  c_int,
  c_float,
  c_double,
  c_decimal,
  c_varchar,
  c_date,
  c_timestamp,
  c_timestamp_tz
)
values (
  100000000,
  100,
  100.001,
  100000.00001,
  20241010.111111,
  'test',
  date '2024-12-20',
  timestamp '2024-12-20T23:36:17.417184',
  timestamp with time zone '2024-12-20T14:37:35.324142Z'
);
commit;

出力したSQLファイルはそのままtgsqlを使って実行可能です。

$ tgsql -c tcp://localhost:12345 --script insert_dump_test.sql
[main] INFO com.tsurugidb.tgsql.core.TgsqlRunner - establishing connection: tcp://localhost:12345
[main] INFO com.tsurugidb.tgsql.core.TgsqlRunner - start processing script
[main] INFO com.tsurugidb.tgsql.core.executor.report.BasicReporter - transaction started. option=[
  type: LTX
  write_preserve: "dump_test"
]
[main] INFO com.tsurugidb.tgsql.core.executor.report.BasicReporter - (1 row merged)
[main] INFO com.tsurugidb.tgsql.core.executor.report.BasicReporter - transaction commit(DEFAULT) finished.
[main] INFO com.tsurugidb.tgsql.core.TgsqlRunner - script execution was successfully completed

一度プログラムとして作っておけば意外と色んな場面で使えると思います。テーブルではなく任意のSQLを渡せるようにしてもいいでしょう。是非参考にしてみてください。

*1:Javaに新しく入ったswitch式便利ですね

Tsurugiでのダンプ・ロード方法色々

このエントリは Tsurugi Advent Calendar 2024 の19日目のエントリです。前日は「 【劔"Tsurugi" Tips集】distinctの代わりにgroup byの使用を検討する (1.1.0ワークアラウンド)【プログラミングTips】 」でした。

データベースを運用、活用する上でデータのダンプ、ロード機能は必須です。運用時のデータバックアップ、リストアもそうですが、データベースに登録しているデータの一部分を抜き出してPandasやExcelなどを使ってデータの分析を行いたいこともあるでしょう。このエントリではTsurugiに用意されているデータのダンプ、ロード機能について紹介します。

Tsurugiが用意しているダンプ・ロード機能

現時点でTsurugiが用意しているデータのダンプ、ロード機能には次のようなものがあります。

  • Belayer
  • tgdumpコマンド
  • Tsubakuroのダンプ、ロードAPI
  • tgsqlのSQL実行モード
    • これは厳密にはダンプ機能ではないのですが、簡易的なダンプ機能として使えるところがあるので紹介します

Belayer

Tsurugiの各種管理機能をWeb APIとして提供する Belayer には ダンプ取得API 及び データロードAPI が存在します。現時点でTsurugi側として最も推奨するダンプ、ロード手段がこちらになります。

ファイル形式としてはParquetとCSVの2種類があります。Tsurugiが用意しているダンプ、ロード機能のうち、CSVファイルも扱えるのは現時点でこの方法のみです。

これらAPIはTsurugi、Belayerが稼働しているサーバーのローカルディレクトリ (厳密にはBelayerの設定環境変数である $BELAYER_STORAGE_ROOT 下のディレクトリ) を対象にファイルの読み書きを行うことに注意する必要があります。 そのため実際に利用するときは ファイルアップロードAPI 及び ファイルダウンロードAPI と併用することになります。

tgdumpコマンド

Tsurugiにはコマンドラインによるテーブルダンプツール tgdump がインストールされています。

コマンドの名前が示すようにサポートしているのはダンプのみで、データロードには対応していません。ファイルの出力形式はParquetです。また次のような注意点があります。

  • Tsurugiがインストールされているサーバー上でのみ実行可能
  • ファイルを出力するディレクトリにはTsurugiの実行ユーザーがアクセス可能であること

Tsubakuroのダンプ、ロードAPI

TsurugiのJava言語向け低レベルAPIである Tsubakuro にはデータのダンプ、ロードを行うためのAPIが用意されています。前述のツールも内部ではこのTsubakuroのAPIを利用して機能を提供しています。

詳しい使い方は 昨年のTsurugi Advent Calendar の次のエントリにて解説されています。

zenn.dev

この方法ではParquetに加えてArrow IPC形式のファイルが扱えます。また、テーブル単位ではなく任意のSQLの出力結果をダンプすることができます。

またこの方法でもtgdumpと同様の制約があります (下に再掲) 。ダンプ、ロード処理をTsurugi本体が実行するためです。

  • Tsurugiがインストールされているサーバー上でのみ実行可能
  • ファイルを出力するディレクトリにはTsurugiの実行ユーザーがアクセス可能であること

tgsqlのSQL実行モード

Tsurugi SQLコンソールである tgsql には引数にSQL文を渡して即時実行するSQL実行モードがあります。SQL実行モードでSQLの実行を行った場合、SQLの出力結果はJSONとして標準出力に出力されるため、これを利用して簡易ダンプツールとして使うことができます。Pandasなどで読み込んで扱えます。

tgsqlをSQL実行モードで実行するには次のようにオプション引数 --exec をセットし、実行するSQL文を渡します。標準出力をファイルにリダイレクトしておきましょう。

$ tgsql -c ipc:tsurugi --exec "select * from customer" > customer.json

リダイレクト先のファイルには次のようにSQLの実行結果がJSONで出力されています。

$ cat customer.json
// {"columns":[{"name":"c_id","label":"c_id","type":"INT8","dimension":0},{"name":"c_name","label":"c_name","type":"CHARACTER","dimension":0},{"name":"c_age","label":"c_age","type":"INT4","dimension":0}]}
{"c_id":1,"c_name":"Hello","c_age":10}
{"c_id":2,"c_name":"World","c_age":20}

各実行方法のPros & Cons

これまでに挙げた実行方法の長所と短所は次のようになります。

  • Belayer
    • Pros
      • 公式が推奨する方式
      • リモートやTsurugi実行ユーザーとは別のユーザーで利用可能
      • Parquetに加えてCSVが利用可能
    • Cons
      • Tsurugiと同じホストにBelayerのインストールが必要であるため、公式提供のDockerイメージから起動した環境では使えない
      • curlコマンド等のWebアプリケーションクライアントを用意する必要がある
  • tgdumpコマンド
    • Pros
    • Cons
      • Tsurugiがインストールされているサーバー上でのみ実行可能
      • ファイルを出力するディレクトリにはTsurugiの実行ユーザーがアクセス可能である必要がある
      • ロードはできない
  • Tsubakuroのダンプ、ロードAPI
    • Pros
      • テーブル単位だけでなく任意のSQLの出力結果をダンプすることが可能
      • Parquetに加えてArrow IPC形式のファイルが扱える
    • Cons
      • tgdumpと同様、Tsurugiがインストールされているサーバー上でのみ実行可能
      • さらに同様にファイルを出力するディレクトリにはTsurugiの実行ユーザーがアクセス可能である必要がある
  • tgsqlのSQL実行モード
    • Pros
      • JSON形式でダンプができる
      • リモートからも実行可能
    • Cons
      • ロードはできない

以上になります。

ですが、他のRDBMSではSQL文によるダンプを行えるものがありますよね。次のエントリではIceaxeを使ってSQL文によるダンプを行う簡易プログラムを書いてみたいと思います。

HTMLとHTTPの変質について

仰々しいタイトルですがただの雑談です。下記の記事を見て色々思ったことを。

wirelesswire.jp

ここで全力で首を振ってしまいました。

この社説の特徴は、ベテランブロガーのジェイソン・コトキーが指摘するようにその平易な言葉遣いと情報密度の高さにあります。この簡潔な社説は、過去数カ月に公開されているニューヨーク・タイムズ紙の27本もの論説記事をリンクしており、それを辿ってその27本の記事をじっくり読み、社説の主張の正しさを確かめることができます。コトキーはこの情報密度の高さを、「しばしば見落とされ、軽視されているハイパーテキストの真の強み」だと賞賛しました。

そう、リンクで結びつけられるハイパーテキスト群こそが "World Wide Web" の本質なのよ。 文書をインターネット越しに結びつけて情報網 (Web) の世界を作ることがHTMLとHTTPの元々の目的だったのですよね。

しかし今やHTMLはGUIを構築するための技術として用いられるようになり (WordやTeXユーザーインターフェースを作る気になりますか?と思うわけですがHTMLではそれが当たり前になっている) 、HTTPはファイアウォール越しにRPCを行うための基盤プロトコルになってしまった。 まあテクノロジーが当初の思惑とは全く異なる目的に使われるようになることはよくあることだし、いいことでもあるのですが、当初の目的が忘れられると色々寂しくなるなあ、こんなことを書くようになった自分も歳を取ったなあと思うわけでw

ちなみに冒頭で引用した記事の本論はSNSについてですが、 先日書いたエントリ でも述べたように、今や様々なSNSに人が離散してしまい、自分も様々なSNSを横飛びしながら使うようになったので、同じ内容を複数のSNSクロスポストするくらいならblogを再活用すべきではないかと思うようになりました。というわけで複数のSNSに投げたくなるようなちょっとした雑談はこんな感じでblogに書くようにしてみようかなと。