こんにちは。9月にDigitization部データ化グループに入社した山内です。
PythonのORMライブラリであるSQLAlchemyのバグを業務中に見つけました。
今回はそのバグを修正してOSSにコントリビュートした過程を紹介します。
JSONカラムのデフォルト値に JSON_OBJECT() を使うとSystaxエラーが発生した
- | バージョン |
---|---|
MySQL | 8.0.13 >= |
Python | 3.11 |
SQLAlchemy | 1.4.54 |
実際のコードではないですが、
以下のようにJSONカラムのデフォルト値に JSON_OBJECT() を設定すると、
テーブルを生成する過程でSystaxエラーが発生してしまいました。
user = Table( "user", MetaData(), Column("id", Integer, primary_key=True), Column("data", JSON, server_default=func.json_object()) )
SQLAlchemyのversionが古かったため、最新まで上げることで解消するかもしれないと思い、
手元で最新verまで上げて試してみたものの同じくSystaxエラーが発生しました。
SQLAlchemyを利用するのは初めてだったのもあり雰囲気でSQLを書いているようなものだったため、
この際にちゃんと勉強しておこうと思い、バグを調査して修正を行いました。
式や関数が括弧で括られていなかった
dataカラムのDDLは
data JSON DEFAULT (JSON_OBJECT()),
にならないといけなかったのですが、
data JSON DEFAULT JSON_OBJECT(),
が発行されておりました。
上記より JSON_OBJECT() が括弧で括られていないのが原因でSystaxエラーが発生していることが分かりました。
また、同じ事象で発生しているバグがissueで報告されているのを見つけました。
この時点ではまだ誰もこのバグを修正するために着手していなかったので自分が修正しようと思いました。
修正対象のコードはどうやって見つけたのか
普段開発しているシステムならまだしも、
業務で使っているライブラリを直接メンテナンスする機会は殆どないので、
どこを修正すればこのバグが直るんだろうという対象を見つけるハードルが高いようにも見えますが、
探す対象の要点を絞れば比較的簡単に見つかることもあります。
今回の場合は、
- server_defaultの値を使いDEFAULT値のDDLを生成している箇所
を目的地にして探すことにしました。
「"DEFAULT 」と検索して探していると
MySQLDDLCompilerというclassの中でDEFAULT値を生成していそうな箇所を見つけました。
MySQLDDLCompilerという命名からしてここなのではないかと当たりをつけて修正を始めることにしました。
修正する前にまず確認したこと
私はSQLAlchemyのコアコミッターではないので、
できる限り既存実装に従って修正を行いたいと思いました。
まずはこの周辺のコードを読んで
- 何の実装をしているのか
- どんな実装をしているのか
など、開発者の思想面をくまなく汲み取った上で修正を行う努力を行いました。
また、OSSではコミッター向けの規範などが定義されたコミュニティガイドが公開されているリポジトリが多いのでそちらも確認し目を通しました。
あとは署名付きcommitしか受け付けないOSSのリポジトリもあったりするので、
そうでなくても最初から事前に設定しておくことをおすすめします。
修正を行う
まずOSSでバグ修正を行う場合、基本的には該当のリポジトリをフォークして取り掛かることになります。
フォーク先のリポジトリで修正を行って、それをフォーク元のリポジトリでPull Requestとして提出するのが一般的です。
また、Pull Requestを作成する際はテンプレートが用意されていることもあるので、
そちらに従って作成を行います。
PR上でのコミュニケーションで心がけていること、勉強になったこと
私の業務で使わせてもらっているライブラリなので、
リスペクト精神を持ってコミュニケーションすることを心がけました。
また、修正を行っていく中で開発者よりMySQL5.7だとテストが落ちてしまうという共有をもらい、
私が行っている修正は特定のverからサポートされたものだとわかりました。
修正を行ったMySQLDDLCompilerではMySQLだけでなく、
MariaDBのDDLも生成していたことからそれぞれでサポートされるようになったverを調査してテストも追加修正しました。
そして、無事に2.0.36にてこのバグが修正されました。
システム上の暫定対応として
システムで扱っているSQLAlchemyはまだ1.x系で古いのと、
今回修正したバグは1.x系へはバックポートされなかったため、
暫定対応としてはDDLへ反映されるserver_defaultを使わず、
defaultにfunc.json_object()を設定して、
SQLAlchemy側での制御に委ねることにしました。
今後2.x系へアップデートする際には今回のバグ修正も含まれているverまでは上げて、
server_defaultでの設定でDDLへ反映させたいと思います。
まとめ
業務の中でも個人開発を行う中でも使っているライブラリのバグを見つけた際には、
誰かが直してくれるまで待とうではなく、自分が修正を行おうという姿勢を今後も続けたいと思います。
またライブラリの中をコードリーディングすることで、
普段書いている処理がどんな風に制御されているのかを知るというのも面白い点だと感じています。
今回はシステムの都合上、暫定対応しかできませんでしたが、
今後のアップデートでは確実に恒久対応を行いたいと思います。
ありがとうございました。