Mukai Systems

PostgreSQL version 15の非互換な修正について

PostgreSQLのバージョン更新作業の影響調査をしていたら、以下のエラーが出ることが判明した。

org.postgresql.util.PSQLException: ERROR: "$3w"またはその近辺でパラメータの後に余分な文字

該当するJavaのコードは次のような感じだったんだけど、どこが問題かお分かりだろうか。

String query =
    "update t " +
    "set x = ?, y = ?, z = ?" +
    "where v = ? and w = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, "foo");
stmt.setString(2, "bar");
// ...

このコードによって、以下の文字列が構築される。

update t set x = ?, y = ?, z = ?where v = ? and w = ?

で、PreparedStatementによって次のようなSQLが構築されるっぽい。

update t set x = $1 y = $2 z = $3where v = $3 and w = $4

version 15のRelease Notesを確認すると、以下の非互換な変更がはいっている。

Prevent numeric literals from having non-numeric trailing characters (Peter Eisentraut)

Previously, query text like 123abc would be interpreted as 123 followed by a separate token abc.

従来は$3whereという文字列は(奇跡的に)$3whereにtokenizeされていたわけだけども、version 15からはエラーになると。

今回の件ではJavaのコードからは予想だにしてなかったのでちょっと面食らった。

ちなみに、コミットはこれ

コミット前のファイルはこれ

コミット直後のファイルはこれ

修正前は、Dollar-Quoted String Constants(ドル記号で引用符付けされた文字列定数)に関連するトークンの定義は次のようになっている。

integer			{digit}+
param			\${integer}

意訳するとこんな定義。

・「integer」とは「数値の列」からなるトークンである。

・「param」とは「文字$の後にintegerがくる」トークンである。

修正後は、新たにparam_junkという、「文字$の後に続く整数列の後に識別子が続く」トークンが定義されている。

param_junk		\${integer}{ident_start}

{param_junk}	{
          mmfatal(PARSE_ERROR, "trailing junk after parameter");
        }

このトークン定義によってエラーして扱われるというわけ。

Conclusion

まとめるとこんな感じ。

  1. JavaでPreparedStatementを使用している場合は、変換後のSQLに「ドル記号で引用符付けされた文字列定数」が含まれている。
  2. version 15のPostgreSQLから「整数リテラルに続く文字」がエラーとなることがRelease Notesで明示されている。
  3. Release Notesで明示されていないものの「ドル記号で引用符付けされた文字列定数に続く文字」もエラーになるようになった。
  4. JavaのPreparedStatementでバインド変数?の直後に文字が続いていてもversion 15以前は動作していたが、version 15からは動作しなくなる。

今回の件はコードの見た目と、構築される文字列とに違いがあるっていうのが根本原因だと思っている。JEP: Text Blocksが浸透してくれば、この手の問題は減っていくんだろうけど。

例示したような、従来のトークンの定義に依存したコードっていっぱいあるだろうから、困る人たくさんいるだろうなー。

More info