SQLite IntegrationというWordPressのプラグインがあります。

WordPressは一般的にMySQLでしか動かないので、レンタルサーバー会社はMySQLを使えるプランこそ「WordPress対応プラン」だとして推してくるわけですが、このプラグインを使えばSQLiteでも動かせるようになります。

具体的に言うと、さくらのレンタルサーバでいえばスタンダードプラン(515円)ではなくライトプラン(129円)、定期的なセキュリティインシデントに定評のある某社のレンタルサーバーでいえばライトプラン(324円)でなくエコノミープラン(108円)でもWordPressを動かせるようになる、という代物です。毎月216円から386円くらい浮かすことができ、1年でコンビニコーヒー24杯分の経済をもたらします。

MySQLと比べると負荷に弱いという面はあるものの、公式サイトで「小規模、中規模のウェブサイトに最適」とされていることからもわかる通り、3人寄れば潰れちゃうというほどよわよわではなく、ある程度なら耐えます。すごいやつだ。

最近のWordPressでSQLite Integrationを使うと設定が保存できない問題

そんな有能なプラグインなのですが、実は「設定画面の(一部を除く)チェックボックスをオフにできなくなる」という恐ろしい問題を抱えています。

GUIが開発されて以来、人類はチェックボックスを無為にオンにしたりオフにしたりすることに快感を覚えてきました。しかし本件は「へっへっへ、オンにしてやったぜ。次はオフにしてやるぜ……」と思ったが最後、オフにしようとしてもオフにならず(……くっ……なんだコイツ……オフにできねぇ……罠か……!?)となってしまうという、恐ろしい病です。まさかの一方通行。

今はまだこの件で検索してもさほど深刻に困っている人はいませんが、将来的に設定項目に「ブログの削除」というのが追加され、ログアウトするとこのブログを削除するなんてチェックボックスが設置されたら、とんでもないことになります

なぜそんな問題が発生するようになったのか?

残念ながら、SQLite Integrationはここ3年間、更新が行われていません。

その3年間で、WordPressの内部には変化が起こりました――可憐な少女が蝶々のように、制服に包むその中身を3年間で大きく変えてしまうように、です。

その変化は特にTKB周辺に顕著だと思うのですが、要するに大人になった少女には大人のアイテムが必要となるように、大人になったWordPressにも大人用のプラグインが必要になった、というだけのことなのです。例えばクラスの女友達に「私は透けるの気にしないからつけてないよ」とか言われたらあなたはどうしますか?「そりゃありがたいけどやっぱりつけようよ」と思ってしまうでしょう?

恋する少女たちのために、そして設定を保存したいユーザーのために

そこで今回私が開発したのが、SQLite化したWordPressでも、設定をむりやり保存することができるようになるプラグインです。

良かれ悪かれ、TKBが透けて見えるのは、胸を張って生きている証拠です。
誰にでもあることだし、全く恥ずかしいことではないと思います。見せびらかして友人を興醒めさせるのは一般的にはよくないことですが、互いに見せ合いっこするなら誰も傷つかないし、それは互いの個性を認めあう貴重な時間にもなります。

その一方でTKBが意図せず透けるのは大きな問題にもなりうることを我々は強く認識しており、透けるほどに育ったのならを当てればいいじゃない、ということなのです。それが今回で言うところのパッチ的プラグイン「update-option Patch」であり、TKBに関してはパッチは2つ必要ですが、今回はTKBではないのでパッチは1つでOKです。話のわかるやつだ。

そんなTKBはひとまず横に置いといて、プラグインは以下からダウンロード。

SQLite Integration update-option Patch

チェックボックスをオフにできなくなってしまうのを解消し、通常通りオフにできるようにするプラグインです。

ダウンロード

SQLite Integration update-option Patch ダウンロードページ

「1.8.1」というフォルダーの中にあるzipファイルをダウンロードしたら、展開して plugins ディレクトリに設置してください。そして管理画面から有効化。

なお、当プラグインは、2018年3月現在のSQLIte Integrationの最新バージョン・1.8.1専用です。もし今後バージョンアップなどで問題が解決したら、当プラグインはむしろ動作の妨げになる可能性があるので、削除してしまってください。

詳しい仕様

アクションフックupdate-optionで設定の変更を監視し、値がNULLに変更されようとしている項目について、データベースファイルに直接接続して該当箇所を書き換えます。
なお wp_options テーブルの options_value は NULL が書き込めなくなっており、矛盾(?)が発生しているので、一通り検証した上でNULLの代わりに空文字列を書き込んでいます。

データベースに直に書き込むという、私的にかなりアバンギャルドなことをやっているプラグインなので、上記の仕様に至った経緯を解説してみます。
技術面は別にどうでもいいや、という人は読み飛ばしてOK。

詳しい動作の解説

動作のかなめupdate-optionというアクションフックです。

フックとは何か? というのは細かくは説明しませんが、簡単に言うと「基本的な動作をしたタイミングで割り込み動作をさせる機能」です。新学期らしい例えをするならば、朝起きるというのが基本的な動作だとしたら、枕元の採尿カップをトイレに持っていっておしっこを注ぎ、提出するアレで吸い上げてラベルを貼るという動作がフックに該当します。いつもの動作の前や後に、別の動作を追加するわけです。(余談だけど私の場合、義務教育時代は四角いたれびんだったので、近代化された時に時代は変わったんだと思った)(どうでもいい)

でそのupdate-optionが何なのかというと、設定が保存される直前に、保存される前の情報と保存される情報を知らせてくれる、というフックなのです。

1. 何が問題なのか確認

管理画面上で発生している状況を見るに、「あぁ、チェックボックスをオフにした時、その値が保存されてないんだろうなぁ」という予想はできます。
で、調べてみたところ実際そのようだったらしく、

  • チェックボックスがオフの場合、値はNULLとなる。
  • しかし保存は行われず、元の値のままとなる。
  • 保存された後のアクションフックupdated-optionも呼ばれない。つまり実際に保存処理が行われていない。

という感じです。

どこに問題が起こっているのかは正直よくわかりませんが、通常使われる関数ではデータベースに正常に書き込まれなくなってしまっている、というのは確かなようです。
なるほど。それならWordPressを通さずにSQLiteに直接接続して、該当のチェックボックスのレコードにNULLを書き込んじゃおう。

2. NULLを書き込むとエラーが発生

ちなみに私は普段SQLを直接いじるコードは書かないため、餅は餅屋ということで、一部コードをSQLite Integrationから拝借しました。
同プラグインはGPLライセンスなので、ああ、このプラグインもGPLにのだなあ……というプログラマにはわかってもらえるであろうある種の情動が起こりますが、それは横に置いといて。

完成したコードでいざNULLを書き込もうとすると、以下のエラーが発生。

NOT NULL constraint failed: wp_options.option_value

これは「NOT NULL制約」といって、どうやら「option_valueにNULLは書き込めない」ようになっているようです。
WordPressはNULLを入れたいと言っている。しかしデータベースはNULLを入れるなと言っている。なるほどチェックボックスがオフにならなかったのはこれが原因か。

……ちょっと待って、じゃあフツーのWordPressは何で問題なく書き込めてるんだろうか? NULLを書き込もうとしたら、勝手に別の値を書き込んでくれるような親切機能があるとか?

3. MySQLの親切仕様から、状況をうすうす把握

そう思ってしばらく調べていると、こんな情報が。(下線は私が付けました)

明示的な DEFAULT 句のない NOT NULL カラムに対するデータエントリでは、(中略)UPDATE ステートメントがカラムを NULL に設定する場合、MySQL はその時点で有効な SQL モードに従ってカラムを処理します。

  • 厳密モードが有効でない場合、MySQL はカラムデータ型の暗黙的なデフォルト値にカラムを設定します。

引用元:MySQL :: MySQL 5.6 リファレンスマニュアル :: 11.6 データ型デフォルト値
https://dev.mysql.com/doc/refman/5.6/ja/data-type-defaults.html

NULLが書き込めない場所にNULLを書き込もうとした場合は、暗黙的なデフォルト値で書き込まれる」という衝撃の事実。脳の皺が一本増えました。ためになったねー。

で、ここでいう「暗黙的なデフォルト値」とは何かというと、

暗黙的なデフォルトは次のように定義されます。
(中略)
ENUM ではない文字列型のデフォルト値は空の文字列です。ENUM のデフォルトは、最初の列挙値です。

引用元:同上

だそうです。

つまりMySQLで運用されているWordPressの場合、wp_optioinsにNULLを書き込もうとすると、エラーにせずに、自動で空文字列を書き込んでくれていたのだ。
一方のSQLiteはそういうのがないので(?)、NULLを書き込むんじゃねえというエラーが出た、と。

4. nullの代わりに空文字列を書き込むべしと結論

どうやら「SQLiteの場合もNULLの場合は空文字列を書き込め」という結論になりそうですが、本当にそれでいいのかい、後悔はないのかい――と俺の中のSQLiteの妖精が囁いている……

じゃあもう結局は実地検分しかねえな、ということで、「実際に運用されているデータベース」をこっそり覗いて、チェックボックスがオフの時、値が何になっているかを調べることにしました。「何も手を付けていないSQLiteのデータベース」と比較して様子を見てみようという作戦です。

すべてのチェックボックスを調べるのは疲れるので、チェックボックスがたくさんある「設定→ディスカッション」ページの分だけ、オン・オフがどのような値で保存されているのかを確認。
結果は以下の表の通り。

option_name option_value
未変更
(SQLite)
一回以上保存
(MySQL)
require_name_email 1 1
comments_notify 1 1
default_pingback_flag 1 空文字列
comment_moderation 0 空文字列
moderation_notify 1 1
comment_whitelist 1 1
comment_registration 0 1
show_avatars 1 空文字列
close_comments_for_old_posts 0 空文字列
thread_comments 1 1
page_comments 0 空文字列
  • 未変更の場合(デフォルト値)、オンで1、オフで0が入っている
  • 一回以上保存されている場合、オンで1、オフで空文字列が入っている

同じオフなのに、デフォルトでは0、変更後は0でなく空文字列

MySQLの親切機能のおかげなのか、スクリプトが裏でごにょごにょやってくれてるのかまではわかりませんが、少なくとも「チェックボックスがオフなら空文字列」というのは間違いなさそうだし、少なくとも今回扱う書き換えの範囲では、空文字列の一択で問題なさそう。

というわけで、「チェックボックスをオフにした場合は、NULLの代わりに空文字列で保存される」という仕様になった、ということです。

拙作プラグインについては以上です。

いつまでもSQLite Integrationが使えるとは限らないので、いざ使えなくなったらどうするかを考える

「手間を省いてくれる」とか「楽しい」とか、そういうプラグインはたくさんありますが、直接的に金銭的コストを削減してくれるプラグインなんてそうそうないわけで、そういう意味でとても貴重な同プラグイン。
その一方で、人(とブログの性質)を選ぶプラグインであることも確かです。負荷対策としてキャッシュプラグインを併用する必要があったり、使えないアドオンがあれば代替を探したり、ある日突然全てが吹っ飛ぶことを警戒して毎日データベースのバックアップをとったり……etc。

またこれから先、新しいバージョンのWordPressでも同プラグインが使えるという保証は全くないわけで、そういう意味ではある日突然が来ても何とかできる人でないと難しいかなとも思います。

そもそも「突然動かなくなる」なんてあるのか?

SQLite Integrationの最重要部分は、データベースとのやりとり部分をオーバーライドできる機構です。

インストールの際にdb.phpを wp-content/ にコピーすると思いますが、これは wp-includes/ の中にあるwp-db.phpをオーバーライドするためです。
ファイルを置くだけで仕事が完了するということは、セキュリティホールになりやすいとも考えられるはずで、この部分を利用した大規模な攻撃が起こったりすると、あんまり使われてないしこの機構は廃止するね、なんて風潮になりかねません。

そういう仕様変更は大抵は事前告知されますが、そもそもあんまり使われてない仕様の廃止が積極的にアナウンスされるとはあまり思えないので、「なんとなくバージョンアップしたら突然動かなくなった」となる可能性はぶっちゃけ低くはない気がします。

万一そうなったら、我々はどこに脱出エクソダスすればいいのでしょう?
ここでは4つの派閥に分類してみます。

1. おとなしく先祖帰りしてテキストエディターで作るよ派

我々の先祖は、テキストエディターでウェブサイトを作っていました。
現在でも、一見ブログ風なのに、毎日HTMLを手入力して更新しているであろうサイトはわずかにあります。ろじっくぱらだいすとか。

必要なのは、サイトをシンプル仕様に変更する決断と、それをするだけのモチベーションです。WYSIWYGに慣れた身体では<br>や<p>をいちいち打つのはきついし、定期的に<pre>の誘惑と戦わなければならないのもつらいです。

2. Movable Typeを使うよ派

私が個人的に好きなCMS、Movable Typeなら、SQLiteで動かせます。

バージョン5になった時点で公式には非対応ということになったのですが、なぜかバージョン6までこっそり対応されており、次期バージョンの7でもインストールウィザードに選択肢が出るので、使えるかもしれません。(もっともこないだベータ版をテストしようとしたらエラーが出てインストールできませんでしたが……

それ以外にも、Movable Typeを使うメリットがたくさんあります。

  • 再構築でhtmlファイルやphpファイルが生成されるのは見ていて楽しい
  • サーバーサイドスクリプトを介さないので表示速度が圧倒的に速い
  • 本体やプラグインは自動更新されないので、勝手に動かなくなることはない
  • プラグイン数が限定されているので、迷わずに済む
  • プラグイン作成にはそれなりのスキルが必要で、粗造乱造が防がれている
  • プログラミングスキルが必要ない(HTMLに似た独自仕様で、敷居が低い)
  • 記事のエディタはシンプルで誰でも使いやすい

しかしもしこれを真に受けて(嘘ではないですが)、WordPressユーザーがMovable Typeに移行してしまうと、

  • 再構築でhtmlファイルやphpファイルの生成にいちいち時間がかかる
  • サーバーサイドスクリプトを介さないので動的な情報を表示できない
  • 本体やプラグインの更新は、ファイルを手動で上書きなのでめんどくさい
  • プラグイン数が少なく、欲しい機能が手に入らない
  • プラグイン作成にスキルが必要なので、自分で作ろうにも作れない
  • HTMLに似た独自仕様なので、覚えるのが面倒
  • 記事のエディタはかゆいところに手が届かない

……という感じで、軽く地獄を見ることになります。

ただ、「WordPressはインストールしたままの素で使っていて、Movable Typeもデフォルトでできる範囲で何も問題なく、記事が移行できればOK」という人は、すんなり移行できるかもしれません。
再構築だって、基本は放っておけばいいだけなので。

3. 無料ブログサービスを使ってみるよ派

身も蓋もない話、いろいろな制限に目をつぶることができ、あまり多くを望まない、ということであれば、無料ブログサービスという手もあります。
少なくともはてなブログについては、検索してみるとWordPressからの移行方法がいくらか紹介されているので、現実的な選択肢じゃないかなぁと思います。

そんな人はこのページをそもそも見てないだろう……とは思うのですが、一方で人間というのは刻々と変化するもので、ブログを開設した数年前には「広告は絶対にヤダ! レイアウトもこれじゃないとヤダ! 独自ドメインじゃないとヤダ! SEOがー!」……と言っていたのが、年を取ってくるといろいろと緩んで「ラクなのが一番じゃ」と思い始めたりすることもあるでしょう。

無料サービスなのにメンテナンスもやってくれるなんて最強じゃないですか。
我々がWordPressに費やしている時間と情熱は、QOL的に適正なものなのだろうか?

4. MySQLを使えるプランに切り替えるよ派

恥も外聞もかなぐり捨て、プランを一段階上げる方法もあります。

さくらのレンタルサーバを年払いで使っている場合、プランを「ライト」から「スタンダード」に上げるには、年当たり3,599円が必要です。
しかしいきなりドカンと3,600円を捻出しようとなると、えーと思うのが人情。

ところがどっこい、12ヶ月や365日で割り算してみると「月当たり299.9円、1日当たり9.9円」となります。これならなんとなく、どうにかできそうな気がしてきます。

例えばあからさまにTKBが透けているシャツをバイト先の店長の前で脱ぎ捨て、恍惚の表情を浮かべながら1日9.9円の賃上げを要求するのはどうでしょうか。こういうのはインパクト勝負で、速やかな賃上げが必要だという差し迫ったを感じさせる必要があるので、リアクションが薄い場合は効果音と共に白目を剥き、底抜けAIR-LINEのテクノ体操の基本動作を繰り返しながらSHOP99のテーマを歌い続けるなどのディープインパクトを披露すれば、さらに確度が高まります。

より完璧を期すのであれば、決行の前日までに

「店長、PとRの間にあるアルファベットって何でしたっけ?」
「店長、日本の一番左にある島って何州でしたっけ?」
「10ひく1、10ひく1……あれー? 店長、何ですっけー?」

などと頻繁に問いかけを行い、「きゅう」というワードを脳に刷り込んでおけばもう盤石です。きっと「キュウ……キュウ……ふむ、よし!」となり、9.9円の賃上げが約束されます。
ただし失敗した場合はあだ名がTKBになります。

ライトプランとスタンダードプラン、その価格差は1日わずか9.9円(税込)――そんな事実をお伝えしたところで、本稿は終わりです。

ご静聴ありがとうございました。

追記(2018年3月20日)

当投稿のオリジナル版本文の芸術性が高すぎたためか、なかなか検索エンジンにインデックスされない状況を鑑み、一部をDAI語による表現に変更しました。ご了承ください。

追記(2018年3月21日)

WP_DEBUGをtrueにしていても(画面遷移のタイミングの問題なのか)エラー表示がページ上に出なかったので諦めていたのですが、WordPressのデバッグログはファイルに出力できることを今更知りました(WP_DEBUG_LOG)。これならエラー見放題。

実際に出力して確認したところ、以下のようなエラーが出ているらしい。

Error occurred at line 692 in Function execute_query.
Error message was: Error while executing query! Error message was: SQLSTATE[23000]: Integrity constraint violation: 19 NOT NULL constraint failed: wp_options.option_value
#0 PDOEngine->get_error_message() called at [/home/foobar/wordpress/wp-content/plugins/sqlite-integration/pdodb.class.php:254]
#1 PDODB->query(UPDATE `wp_options` SET `option_value` = NULL WHERE `option_name` = 'comment_registration') called at [/home/foobar/wordpress/wp-includes/wp-db.php:2166]
#2 wpdb->update(wp_options, Array ([option_value] => Array ([value] => ,[format] => %s,[charset] => ,[length] => )), Array ([option_name] => Array ([value] => comment_registration,[format] => %s,[charset] => ,[length] => ))) called at [/home/foobar/wordpress/wp-includes/option.php:369]
#3 update_option(comment_registration, ) called at [/home/foobar/wordpress/wp-admin/options.php:215] ] UPDATE `wp_options` SET `option_value` = NULL WHERE `option_name` = 'comment_registration'

そしてpdodb.class.phpを覗いてみたところ、値がNULLの時にそういうSQL文を組み立てているっぽい箇所がありました。2141行目。

foreach ( $data as $field => $value ) {
	if ( is_null( $value['value'] ) ) {
		$fields[] = "`$field` = NULL";
		continue;
	}
	$fields[] = "`$field` = " . $value['format'];
	$values[] = $value['value'];
}

ということは、ここを書き換えればNULLの代わりに空文字列を書き込む、という動作に変更できそうですが、実際は$fieldがoption_valueの時だけ(カラムがNOT NULLの時だけ)にすべきなんだろうなぁと思います。

ただ、私はもうプラグインで暫定的に解決しておこうと思います。内部でエラーは起こってるわけですが、設定保存時だけですもんね……