MySQLのLIMIT句の入れ子による面白い挙動と将来リリースでの修正予定

とみたさんから、MySQLの次の次のバージョンで挙動が変更になる話を教えてもらったので、記録。

mysql> use mysql
mysql> SELECT user FROM user LIMIT 2;
+------------------+
| user             |
+------------------+
| mysql.infoschema |
| mysql.session    |
+------------------+
2 rows in set (0.00 sec)

 適当なテーブルから LIMIT句を使って2件のデータを取得したもの。これはまぁ普通の挙動。
次に、これを入れ子にしてみる。

mysql> (SELECT user FROM user LIMIT 2) LIMIT 3;
+------------------+
| user             |
+------------------+
| mysql.infoschema |
| mysql.session    |
| mysql.sys        |
+------------------+
3 rows in set (0.00 sec)

 こんな記述方法があるのか、という驚きはとりあえず横に置いておいて、いったん内側で 2件に絞ったものを、さらに3件に絞ろうとする、という指示に対して、内側の指示が無視されて3件返ってくる動作。
 この動作がMySQL 8.0.31 で変更される(ちゃんと(?) 2件のみが返ってくるようになる)というのが、リリースノート差分ウォッチャーとしての ご情報でした。

 ちなみに、私が「普通の書き方」だと考える、サブクエリを明示した記述方法なら、入れ子にしても2件のみが「正しく」返ってくる。

mysql> SELECT * FROM (SELECT user FROM user LIMIT 2) t LIMIT 3;
+------------------+
| user             |
+------------------+
| mysql.infoschema |
| mysql.session    |
+------------------+
2 rows in set (0.00 sec)


 以下は、お遊び。

もっと入れ子にしてみたり、

mysql> ((SELECT user FROM user LIMIT 2) LIMIT 3) LIMIT 4;
+------------------+
| user             |
+------------------+
| mysql.infoschema |
| mysql.session    |
| mysql.sys        |
| root             |
+------------------+
4 rows in set (0.00 sec)


内側にORDER BY をつけても変わらないことを確認したり、

mysql> (SELECT user FROM user ORDER BY user LIMIT 2) LIMIT 3;
+------------------+
| user             |
+------------------+
| mysql.infoschema |
| mysql.session    |
| mysql.sys        |
+------------------+
3 rows in set (0.00 sec)


内側と外側に ORDER BY をつけると、2件のみになることを確認したり、

mysql> (SELECT user FROM user ORDER BY user LIMIT 2) ORDER BY user DESC LIMIT 3;
+------------------+
| user             |
+------------------+
| mysql.session    |
| mysql.infoschema |
+------------------+
2 rows in set (0.00 sec)

いやそもそも、ORDER BY は外側だけでも2件になってくれることを確認したり、

mysql> (SELECT user FROM user LIMIT 2) ORDER BY user DESC LIMIT 3;
+------------------+
| user             |
+------------------+
| mysql.session    |
| mysql.infoschema |
+------------------+
2 rows in set (0.00 sec)


入れ子は少なくとも20個程度はちょろいもんらしい、ということを確認したり、

mysql> ((((((((((((((((((SELECT priv FROM global_grants LIMIT 2) LIMIT 3) LIMIT 4) LIMIT 5) LIMIT 6) LIMIT 7) LIMIT 8) LIMIT 9) LIMIT 10) LIMIT 11) LIMIT 12) LIMIT 13) LIMIT 14) LIMIT 15) LIMIT 16) LIMIT 17) LIMIT 18) LIMIT 19) LIMIT 20;
+----------------------------+
| priv                       |
+----------------------------+
| SYSTEM_USER                |
| AUDIT_ADMIN                |
| BACKUP_ADMIN               |
| BINLOG_ENCRYPTION_ADMIN    |
| CLONE_ADMIN                |
| CONNECTION_ADMIN           |
| INNODB_REDO_LOG_ENABLE     |
| PERSIST_RO_VARIABLES_ADMIN |
| REPLICATION_APPLIER        |
| SERVICE_CONNECTION_ADMIN   |
| SESSION_VARIABLES_ADMIN    |
| SYSTEM_USER                |
| SYSTEM_VARIABLES_ADMIN     |
| SYSTEM_USER                |
| APPLICATION_PASSWORD_ADMIN |
| AUDIT_ADMIN                |
| BACKUP_ADMIN               |
| BINLOG_ADMIN               |
| BINLOG_ENCRYPTION_ADMIN    |
| CLONE_ADMIN                |
+----------------------------+
20 rows in set (0.00 sec)

いろいろと遊びましたとさ。
MySQL 8.0.31 (順当にいけば 2022年10月リリース)でこれらがどう変化するのか、リリースされたら試してみたいと思います(覚えていたら)。

MySQLリリースノート勉強会8.0.28開催しました

MySQL リリースノートでわいわい言う勉強会 8.0.28 を開催しました。
今年は、今までの常識に囚われずに色々試していこうということで、平日18時からの開催という変則的なトライアルでしたが、たくさんの人に参加していただき、ありがとうございました。主に喋っていたのは6人くらいだったでしょうか。たくさんの事を教えていただき、とても勉強になりました。

mysql.connpass.com


 今回は、何はなくともコレを世間に広く知らしめるべし、と感じたので、最初に貼っておきます。@yyamasaki1 さんがプレゼン資料の中で紹介してくれていた1ページです。要約すると、「MySQL配布ファイルの署名用GPGキーの有効期限が切れて、リポジトリからのアップデートに失敗します。最新に更新してね」というものです。

f:id:sakaik:20220223005833p:plain
MySQL8.0.28以降へのリポジトリからのアップデートでエラーが発生したら?

※資料配付元:公開資料・出展レポート - オープンソースカンファレンス2022 Osaka

今回の主なテーマ

 今回は、幹事がピックアップしたテーマを中心に進めました。あまりリリースノートの一言一句を読むという感じではなく。 リリースから1ヶ月以上も経過していることもあってか、各テーマに対してそれぞれの知っていることや体験などを聞かせていただきました。私が用意したテーマは、こんな感じ。
f:id:sakaik:20220223010610p:plain

主な話題(私が興味を持ったもの)

 今回は、会の中で教えてもらったり後で試してみたりしたことなどをTwitterに書いたので、ツイートを貼っておきます。



MySQL2021~地理情報(spatial)機能の進化

この記事は、『RDBMS-GIS(MySQL,PostgreSQLなど) Advent Calendar 2021』の1日目のエントリーです。


2021年のMySQLは、以下の5つのバージョンがリリースされました。

MySQL 8.0.23 (2021/01 リリース)
MySQL 8.0.24 (2021/04 リリース)
MySQL 8.0.25 (2021/05 リリース)
MySQL 8.0.26 (2021/07 リリース)
MySQL 8.0.27 (2021/10 リリース)

全体として、地理情報機能(spatial/GIS)の観点からは、どちらかというと地味な進化の一年だったという印象でした。各バージョンでの進化を以下にまとめます。


MySQL 8.0.23 (2021/01 リリース)

ST_HausdorffDistance() 関数と、ST_FrechetDistance() 関数が追加されました。
それぞれ、ハウスドルフ距離とフレシェ距離を求める関数なのですが、正直なところ私も使い所がよくわかっていません。わからないなりに調べながら、とりあえず挙動を紹介してみたのが、本ブログの以下の記事になります。

https://sakaik.hateblo.jp/entry/20210227/try_mysql_frechetdistance_and_haussdorffdistance

MySQL 8.0.24 (2021/04 リリース)

 GIS的には、今年最大の進化となったバージョンです。4つの関数の追加と、既存関数のGISへの拡張が行われました。

 追加されたのは以下の4つの関数。

ST_LineInterpolatePoint()
ST_LineInterpolatePoints()
ST_PointAtDistance()
ST_Collect()

最初の3つは、指定した条件に従ってLINESTRING上の経過点を返す関数です。本ブログでは以下の記事で紹介しました。
https://sakaik.hateblo.jp/entry/20210501/mysql_spatial_new_functions_8024_1

ST_Collect() は、POINTなどの単一型を、MULTIPOINTなどのマルチ型へとまとめる集約関数です。以下の記事で紹介しました。
https://sakaik.hateblo.jp/entry/20210512/mysql_spatial_new_functions_8024_3_ST_Collect


 さらに、MySQL 8.0.24では、CAST()、CONVERT()の2つの既存関数が、Spatial(GIS)型も扱えるよう拡張されました。この拡張により、POINT/LINESTRING/POLYGON および各マルチ型の各型どうしで変換できるパターンが格段に拡がりました。
 詳しくは、本ブログの以下の記事を参照ください。
https://sakaik.hateblo.jp/entry/20210515/mysql_spatial_new_functions_8024_4_CAST_Function

リリースに先立って、リファレンスマニュアルに機能変更の記述が加えられたことを受けての「超先出し(期待記事)」は以下のリンクとなります。
https://sakaik.hateblo.jp/entry/20210123/mysql_spatial_cast_function_in_8_0_24


MySQL 8.0.25 (2021/05 リリース)

 MySQL 8.0.24 に問題があったとのことで、緊急リリースです。GIS関連機能の変化はありません。


MySQL 8.0.26 (2021/07 リリース)

 いままで平面座標系のみを扱うことができた ST_Buffer()、ST_Difference()、ST_Union() の各関数が、多くのSRSに対応しました。平たく言うと「丸い地球」に対応しました(平たく言ったつもりなのに丸くなっちゃった)。
 あと、内部で使っていた関数が開放後のメモリを参照しちゃってた問題が、このバージョンで直されたっぽい。

MySQL 8.0.27 (2021/10 リリース)

 いままで平面座標系のみを扱うことができた ST_SymDifference()、ST_Intersection()の両関数が、他のSRSにも対応しました。平たく言..(略)



まとめ

 以上見てきたように、2021年のMySQL spatial(GIS)は、いくつかの関数追加と、いくつかの関数がSRS対応した、という進化でした。PostGIS並の関数充実に到達するために、もっと派手に関数が追加される1年になると予想していたので、ちょっと拍子抜けしたというのが正直な感想です。
 とは言え、放置されているわけではなく着実に進化はしているので、2022年も引き続きMySQLのspatial機能の変化を楽しみに、注目していきたいと思います。関係ないけど、2022年ってなんか文字集合っぽくて格好良いですね。

f:id:sakaik:20211130225559p:plain:w1

OSC2021 Online Hokkaido 参画

オープンソースカンファレンス北海道(オンライン)に参加してきました。

event.ospn.jp


オンラインイベントの良いところは、興味のある時間だけセミナーを聞いて、あとは日常生活を送れる点。
オフラインイベントの良いところは、会場にある意味物理的に拘束されてしまっているので、空いた時間に「少しだけ興味のある」セミナーを覗いたり、ブースで色々教えてもらえたりと、丸丸その時間全体をOSCでの吸収に当てられること。
個人的には、自分からご指名する程には興味はないのだけど何か面白そうなものに出会える場というのがOSCの本質だと思っているので、オンライン化の良い面は、私にとってのOSCとしては少し残念な変化でもあります。

 私、ラーメンが大好きなので、放っておくとラーメンばっかり食べるわけですが、やっぱり、自分が狙って食べるものだけでなく、他人に選んでもらったりその場の空気の中で違うものを選択したりすることで、いままで知らなかったものに出会えると思うのです。


MySQLユーザ会の発表

 日本MySQLユーザ会でひと枠をいただきいて、セミナーを開催しました。MySQLそのものの機能や使い方の最新情報は、比較的MySQLユーザ会のイベントなどでもよく聞けるものなので、考えた結果、やはりせっかくのOSC、つまりMySQLだけに興味があるわけじゃない人たちがいっぱい集まるイベント、ということで、『しばらくMySQLから離れていた人が最近のMySQL情報に触れて分かった気分になるための45分』と題して、MySQLの進化の歴史、最近のMySQLを理解するためのキーワードなどの紹介を含めて、ふんわりとしたお話をさせていただきました。  

発表資料を公開しています。
この資料をもとにしての「お話」をメインにしていますので、資料だけで理解できることは多くはないと思いますが、様々なキーワードとの「出会い」のきっかけとして活用していただけたらと思います。

www.slideshare.net

オンライン時代の発表ネタの難しさ

 オフラインイベントでOSCが開催されていた際には、同じテーマで少しだけ切り口を変化させながら全国のOSCで発表をしていたのですが、オンライン時代になって、主催は各地幹事でありながらも参加者自体は全国から、おそらく同じような人が、興味を持って参加してくれるようになりました。ネタの使い回しがしにくくなってきた感があります。つまり、毎回異なるテーマの発表を用意する必要があり、その結果、私自身のOSCへの参加頻度(発表頻度)も以前ほどではなくなってしまいました。(まぁもともと私は発表よりもブースでの交流を楽しみにOSCに参加していたので、オンライン化によりその楽しみの大部分を失ってしまったという面もあるのですが)
 実際のところ、運営の皆さん、参加される皆さんの感覚って、どうなんですかね。「これOSC北海道で聞いたよ。なんだよー」みたいな感じ(常に新ネタを期待している)のか、実のところ再演であってもあまり気にされていないのか。私としては今回発表したネタは、OSC的な雰囲気の中でなかなか良い感じに構成できたと感じているので、別の地域のOSCでの再演してもいいかもなぁと、少し考え始めているところです。

OSC北海道らしい北海道的なわけわからなさ

 自分の発表枠以外の時間は、自分の作業があり(スマホの機種変&設定だったんですけど)、あまり今回のOSCの全体像を追えていなかったのですが、なんか、実行委員長以下数人の方が、開催時間のあいだに道内を走り回っていたようです。いや、もう、率直に言ってわけわかんない。わけわからないのだけど、「椅子に縛り付けられるオンラインイベント」からの開放という試みとして、なんだかすごく面白そうなことをやっているような気もしました。地域名が冠してあっても、参加する側から見たら「タイムテーブルが公開されていて、時間になったら動画が流れてくる」だけの、変化のない勉強会。そんなオンラインイベントの弱い部分に対する挑戦、と捉えたら良いのかな。もう少し事前に意図を察知して、もう少しリアルタイムで追いかけていたらもっと私も盛り上がれたかなぁと、少々悔しい気分。

ということで

 タイミング(日程)が色々重なってしまって、自分の発表以外はあまり「参加感」を得られなかった今回のOSCですが(私が懇親会に顔も出さないなんて...)、事前に北海道のお菓子セットを取り寄せたりして、少しだけ北海道気分が盛り上がりました。せっかくの、地域名を冠したイベントに参加するのだから、自分の目の前で関心のある狭い領域の情報に触れるだけでなく、その地域に少しでも思いを寄せて、地域の文化や食や観光地(食、大事!!)に触れてみると、折角の「全国各地で」開催されているOSを一層楽しめるんじゃないかなと思います。
 あと、懇親会(YouTube Liveで少し拝見していました)でお話されていた事ですが、オンライン化によって、運営事務作業は別の地域のスタッフ経験者たちで回すことができるので、現地幹事の皆さんはコンテンツ作り(現地ならではの!)のほうに注力できるのでは、という考え方、良いと思いました。地域性が見えないオンラインイベントだからこそ、地域性の演出に楽しさが拡がりそうです。


 次回の北海道開催時には、現地にてまた皆さんと会い、ブースをうろうろして、おいしいもの食べて、無駄に寄り道をして、楽しみたいものです。



f:id:sakaik:20210624195824j:plain

MySQLをWindows(WSL)上でデバッグする環境を手に入れた(せじまさんの記事がすごい)

 WindowsのWSL上でMySQLをビルドし、VSCodeを使ってデバッグする方法を、せじまさんが公開してくださいました。
labs.gree.jp

私のこのエントリは、せじまさんの記事を読みながら実際に試してみた際に、考えたこと、苦労したこと、ちょっと変えてみたことなどの記録です。一番言いたいことは「せじまさんの記事すごい!リンク先ぜひ見て、ぜひ試してみて!」ですので、実際にやられる方はリンク先をご覧ください。
 リンクを貼った記事だけでなくその前後の記事も、少し異なる環境への対応がまとめられているので、参考になると思います。

私のスペック

  • Windows 10 Pro/20H2
  • i7-7770K
  • Memory: 32GB
  • WSL1 後に WSL2
  • WSL1 上に Ubuntu 29.94 環境インストール済
  • Visual Studio インストール済

WSL 環境(WSL2にしましょう)

 せじまさんの記事では WSL2 でということでしたが、私は WSL1 の環境で作業を始めました。大部分の工程をWSL1上で実施できましたが、最後の、VSCodeが起動された後に、どうしてもブレークポイントで止まらない、という事象に遭遇しました。そこで WSL2 にしたらブレークポイントで止まるようになったので、たぶん最初から WSL2 にしておくのが吉だと思います。


WSL1 から WSL2 への変更方法

 私がやったのは、以下の手順です。

  • PowerShellを 管理者として実行
  • PowerShell上で以下のコマンドを順次実行
    • wsl --set-default-version 2
    • wsl --list --verbose
      • で作成済みイメージのWSLバージョンを確認。私はubuntu-20.04を2にしたいので以下
    • wsl --set-version Ubuntu-20.04 2

gdb が必要

 私の環境にはまだ入れていなかったので、インストールしました。
記録取っていないのだけど、たぶん普通に

$ sudo apt-get install gdb 

とかやったのだと思う。

make オプションを少し変えてみた&メモリの使われ方

 せじまさんの記事では、make時にかなりメモリが使われるので、-j 4 のように絞ったほうがいいよ、とのことでしたが、私はとりあえずチャレンジングな心で "4" 指定なし(-jのみ)でmake実行して、無事最後まで通りました。
 タスクマネージャでメモリ使用量を目視していたところ、使用量の増減は結構あり、特に、進行82%くらいで20GB、84%付近で26GBくらい使用されたのがピークでした。(makeが終わったとき=常時=の使用量は9GB程度)

VSCode関連

 WSL[12]上の Ubuntu から、VSCodeを立ち上げることができるという事を知りませんでした。これすごい。指定したフォルダの情報を以て、VSCodeが立ち上がってくれるのですね。
 VSCode起動前に、launch.json と c_cpp_properties.json の2つのファイルを作成(せじまさんの記事からコピペ)する必要があるところ、私のポカで1つしか作らずにハマりました。ちゃんと読んで2つ作りましょう。

ブレークポイント

 とりあえずブレークポイントのお試しをするには、do_command() 関数(sql_parse.cc)が便利です。
F5を押してサーバを立ち上げた後、私は VSCode内の「ターミナル」で、mysqlコマンドを実行しました。

$ cd ~/mysql/mysql-8.0.25\bld
$ ./bin/mysql -uroot -p

 MySQLサーバに接続した「ターミナル」内で 適当なSQLコマンドを打つと、ブレークポイントで止まりました。あとは F10/F11 などを駆使して楽しめます。

まとまらないまとめ

 素晴らしい記事を公開してくださったせじまさんに、大大感謝です!
Windows上での MySQLビルドの際には、事前にソースコードにBOMを付ける作業が必要など、日本語環境では一手間ハードルがあったのですが、WSL2上での実施は、驚くほどスムーズでした(せじまさんが設定ファイル類を公開してくださっているおかげなのですが)。
 これで、私も久々に手元でのMySQLビルド&デバッグ環境が手に入ったので、さしあたって、@yoku0825 さんが作ってくれた PLEASE句パッチでも当てて遊んでみることにしますかね。



f:id:sakaik:20210524012009p:plain

MySQL8.0.24の新しいGIS関数(4)~CAST()を試す

 MySQL 8.0.24 では、Spatial機能(GIS機能)にもう一つの大きな進化がありました。CAST() 関数と CONVERT() 関数の Spatial型対応です。

CAST() のジオメトリ対応

 CAST()関数は、MySQL リファレンスマニュアルの以下の場所に記述があります。
MySQL :: MySQL 8.0 Reference Manual :: 12.11 Cast Functions and Operators

 様々なデータ型の変換(cast)を行う関数ですが、今回、ここに空間情報型(ジオメトリ型)の変換機能が追加されました。
 MySQL 8.0.24 以降の CAST() 関数で変換可能なジオメトリ型は、以下の図のとおりです。点線は、変換のための条件が結構厳しいもの(主観)、また、GEOMETRYCOLLECTION型へはすべての型からキャスト可能なので、* で記しました。

f:id:sakaik:20210515112345p:plain

 以下、それぞれの変換を試してみた結果を紹介します。


テーブルとデータの用意

 このエントリでは、以下のデータを使用します。(このエントリの途中で更に増えます)

CREATE TABLE t2 (id integer, g GEOMETRY);
INSERT INTO t2 VALUES (1, ST_GeomFromText('POINT(1 1)'));
INSERT INTO t2 VALUES (2, ST_GeomFromText('POINT(3 3)'));
INSERT INTO t2 VALUES (3, ST_GeomFromText('POINT(2 2)'));
INSERT INTO t2 VALUES (4, ST_GeomFromText('POINT(4 4)'));
INSERT INTO t2 VALUES (5, ST_GeomFromText('LINESTRING(1 2, 2 3, 3 4)'));
INSERT INTO t2 VALUES (6, ST_GeomFromText('LINESTRING(1 3, 1 4, 2 5, 1 3)'));
INSERT INTO t2 VALUES (7, ST_GeomFromText('LINESTRING(3 4, 4 2, 5 4)'));
INSERT INTO t2 VALUES (8, ST_GeomFromText('POLYGON((5 2, 7 3, 8 6, 6 5, 5 2))'));
INSERT INTO t2 VALUES (9, ST_GeomFromText('POLYGON((5 3, 7 4, 8 7, 6 7, 5 3),(6 4, 7 5, 6 5, 6 4))'));

 登録したデータを、ジオメトリ型とともに表示、確認してみます。

mysql> SELECT id, ST_AsText(g), ST_GeometryType(g) FROM t2;
+------+--------------------------------------------------+--------------------+
| id   | ST_AsText(g)                                     | ST_GeometryType(g) |
+------+--------------------------------------------------+--------------------+
|    1 | POINT(1 1)                                       | POINT              |
|    2 | POINT(3 3)                                       | POINT              |
|    3 | POINT(2 2)                                       | POINT              |
|    4 | POINT(4 4)                                       | POINT              |
|    5 | LINESTRING(1 2,2 3,3 4)                          | LINESTRING         |
|    6 | LINESTRING(1 3,1 4,2 5,1 3)                      | LINESTRING         |
|    7 | LINESTRING(3 4,4 2,5 4)                          | LINESTRING         |
|    8 | POLYGON((5 2,7 3,8 6,6 5,5 2))                   | POLYGON            |
|    9 | POLYGON((5 3,7 4,8 7,6 7,5 3),(6 4,7 5,6 5,6 4)) | POLYGON            |
+------+--------------------------------------------------+--------------------+

POLYGON型からのMULTIPOLYGON

 POLYGON型からMULTIPOLYGON型への最もシンプルな変換は、要素をひとつだけ持つ MULTPOLYGON型への変換です。

mysql> SELECT id, ST_AsText(g), 
    ->        ST_AsText( CAST(g AS MULTIPOLYGON)) MP
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON';
+------+--------------------------------------------------+---------------------------------------------------------+
| id   | ST_AsText(g)                                     | MP                                                      |
+------+--------------------------------------------------+---------------------------------------------------------+
|    8 | POLYGON((5 2,7 3,8 6,6 5,5 2))                   | MULTIPOLYGON(((5 2,7 3,8 6,6 5,5 2)))                   |
|    9 | POLYGON((5 3,7 4,8 7,6 7,5 3),(6 4,7 5,6 5,6 4)) | MULTIPOLYGON(((5 3,7 4,8 7,6 7,5 3),(6 4,6 5,7 5,6 4))) |
+------+--------------------------------------------------+---------------------------------------------------------+

 カッコが多くなってやや見にくいですが、確かに、元のPOLYGONデータ値をひとつだけ含む MULTIPOLYGON 型が生成されていることがわかります。

 ST_Collect() を使って集約して、MULTIPOLYGON 型にすることもできます。

mysql> SELECT ST_AsText( CAST(ST_Collect(g) AS MULTIPOLYGON)) MP
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON';
+---------------------------------------------------------------------------------+
| MP                                                                              |
+---------------------------------------------------------------------------------+
| MULTIPOLYGON(((5 2,7 3,8 6,6 5,5 2)),((5 3,7 4,8 7,6 7,5 3),(6 4,6 5,7 5,6 4))) |
+---------------------------------------------------------------------------------+
1 row in set (0.00 sec)

POLYGON型からMULTILINESTRING型へ

 これもイメージどおりの動作です。POLYGONとして記述したWKTの始点から終点までのLINESTRINGへと変換されます。中をくり抜く記述がされているPOLYGONは、外の要素、中の要素それぞれがの線で2要素の MULTILINESTRING になります。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS MULTILINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON';
+------+--------------------------------------------------+----------------------------------------------------------+
| id   | ORIGINAL                                         | CASTED                                                   |
+------+--------------------------------------------------+----------------------------------------------------------+
|    8 | POLYGON((5 2,7 3,8 6,6 5,5 2))                   | MULTILINESTRING((5 2,7 3,8 6,6 5,5 2))                   |
|    9 | POLYGON((5 3,7 4,8 7,6 7,5 3),(6 4,7 5,6 5,6 4)) | MULTILINESTRING((5 3,7 4,8 7,6 7,5 3),(6 4,6 5,7 5,6 4)) |
+------+--------------------------------------------------+----------------------------------------------------------+

POLYGON型からLINESTRING型へ

 これは、変換可能な条件がやや厳しいです。中をくりぬく記述のあるPOLYGONは2要素以上から構成されるため、シングルの LINESTRINGにはへ感できません。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS LINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON';
ERROR 4032 (22S01): Invalid cast from POLYGON to LINESTRING.

 これは、id=9 のほうの POLYGON が2要素に変換されるため、LINESTRING へと変換できずにエラーとなったものです。
 内側をくり抜く指定のない id=8 のほうだけを指定すると、以下のとおり、変換できます。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS LINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POLYGON' AND id=8;
+------+--------------------------------+---------------------------------+
| id   | ORIGINAL                       | CASTED                          |
+------+--------------------------------+---------------------------------+
|    8 | POLYGON((5 2,7 3,8 6,6 5,5 2)) | LINESTRING(5 2,7 3,8 6,6 5,5 2) |
+------+--------------------------------+---------------------------------+

LINESTRING型からMULTILINESTRING型へ

 これは非常にシンプルです。単一の LINESTRING を、要素をひとつだけ持つ MULTILINESTRIN Gへ変換するのですから、何の心配事もありません。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS MULTILINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING';
+------+-----------------------------+------------------------------------+
| id   | ORIGINAL                    | CASTED                             |
+------+-----------------------------+------------------------------------+
|    5 | LINESTRING(1 2,2 3,3 4)     | MULTILINESTRING((1 2,2 3,3 4))     |
|    6 | LINESTRING(1 3,1 4,2 5,1 3) | MULTILINESTRING((1 3,1 4,2 5,1 3)) |
|    7 | LINESTRING(3 4,4 2,5 4)     | MULTILINESTRING((3 4,4 2,5 4))     |
+------+-----------------------------+------------------------------------+

 もちろん、ST_Collect() を使って LINESTRING を集約したものを、MULTILINESTRIGN にすることもできます。

mysql> SELECT ST_AsText( CAST(ST_Collect(g) AS MULTILINESTRING)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING';
+----------------------------------------------------------------------------------+
| CASTED                                                                           |
+----------------------------------------------------------------------------------+
| MULTILINESTRING((1 2,2 3,3 4),(1 3,1 4,2 5,1 3),(3 4,4 2,5 4),(1 3,2 5,1 4,1 3)) |
+----------------------------------------------------------------------------------+

 というかまぁこの例は、ST_Collect() 自体が集約結果を MULTI* 型にしてくれるので、明示的な CAST() なしでも同じ結果を得られるんですけどね。

mysql> SELECT ST_AsText( ST_Collect(g) ) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING';
+----------------------------------------------------------------------------------+
| CASTED                                                                           |
+----------------------------------------------------------------------------------+
| MULTILINESTRING((1 2,2 3,3 4),(1 3,1 4,2 5,1 3),(3 4,4 2,5 4),(1 3,2 5,1 4,1 3)) |
+----------------------------------------------------------------------------------+

LINESTRING型からMULTIPOINT型へ

 これもシンプルですね。LINESTRINGを構成する各点をバラバラにして、MULTIPOINTに入れてくれるものです。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS MULTIPOINT)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING';
+------+-----------------------------+-------------------------------------+
| id   | ORIGINAL                    | CASTED                              |
+------+-----------------------------+-------------------------------------+
|    5 | LINESTRING(1 2,2 3,3 4)     | MULTIPOINT((1 2),(2 3),(3 4))       |
|    6 | LINESTRING(1 3,1 4,2 5,1 3) | MULTIPOINT((1 3),(1 4),(2 5),(1 3)) |
|    7 | LINESTRING(3 4,4 2,5 4)     | MULTIPOINT((3 4),(4 2),(5 4))       |
|   61 | LINESTRING(1 3,2 5,1 4,1 3) | MULTIPOINT((1 3),(2 5),(1 4),(1 3)) |
+------+-----------------------------+-------------------------------------+

LINESTRING型からPOLYGON型へ

 これが結構くせ者です。
まず、よくあるLINESTRING(始点と終点が異なる点)の場合を見てみましょう。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS POLYGON)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING' AND id=5;
ERROR 4032 (22S01): Invalid cast from LINESTRING to POLYGON.

 この LINESTRING から POLYGON への変換はできない(輪になっていないのだからPOLYGONにはできない)というエラーが出ています。
 では次に id=6 として用意した、始点と終点が同じ点となっている LINESTRING を POLYGON に変換してみましょう。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS POLYGON)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING' AND id=6;
ERROR 4033 (22S04): Invalid cast from LINESTRING to POLYGON. A polygon ring is in the wrong direction.

 先ほどとは異なるエラーが表示されました。これね、知らないとここで詰む話なのですが、POLYGONのリングには「向き」があるんです。私も聞きかじり(読みかじり)の知識なのですけど(たしか shapefile の仕様あたりに書いてあった気が)、POLYGONの線を辿ったときに、線の「右側」のほうが内側にならなければならないのです。なので最初のPOLYGONは右回り、最初に刳り抜く指定は左回り、、、のようになっている必要があります。たぶんこのルールに抵触したのでしょう。たぶん。
 ・・・・おや。
でも、id=6の LINESTRING を見てみると、右回りですね。。まぁとりあえず逆回りになるようにして、試してみましょう。

 逆回りとなる LINESTRING データを1件追加してみます(id=61)。

INSERT INTO t2 VALUES (61, ST_GeomFromText('LINESTRING(1 3, 2 5, 1 4, 1 3)'));
mysql> SELECT id, ST_AsText(g) FROM t2 WHERE id=61;
+------+-----------------------------+
| id   | ST_AsText(g)                |
+------+-----------------------------+
|   61 | LINESTRING(1 3,2 5,1 4,1 3) |
+------+-----------------------------+


 このLINESTRINGについて、POLYGONにCASTを試みます。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS POLYGON)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='LINESTRING' AND id=61;
+------+-----------------------------+----------------------------+
| id   | ORIGINAL                    | CASTED                     |
+------+-----------------------------+----------------------------+
|   61 | LINESTRING(1 3,2 5,1 4,1 3) | POLYGON((1 3,2 5,1 4,1 3)) |
+------+-----------------------------+----------------------------+

 お。POLYGONになった!!
とりあえず直感と異なるけど、今日の所は3つの仮説を立てて、詳細後日追試してみることにします。
仮説(1)「右側が内側」という私の記憶が誤り。実は逆。
仮説(2)SRID=0 の時、x軸y軸は入れ替えた状態(横軸がy)で評価するものである、そういう仕様なのである
仮説(3)MySQLのバグ(そもそも逆の動作をしている説/SRIDによって動作がおかしい説)

POINT型からMULTIPOINT型

 これも、LINESTRING型から MULTILINESTRING型への変換と同様に、単体のものを1要素だけを持つMULTI*型に変換するもの、そしてグルーピングにより単体の集合を MULTIPOINTにまとめるものがあります。後者は ST_Collect 自体がMULTIPOINTに変更してくれるので実はCAST不要です。

mysql> SELECT id, ST_AsText(g) ORIGINAL, 
    ->        ST_AsText( CAST(g AS MULTIPOINT)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POINT';
+------+------------+-------------------+
| id   | ORIGINAL   | CASTED            |
+------+------------+-------------------+
|    1 | POINT(1 1) | MULTIPOINT((1 1)) |
|    2 | POINT(3 3) | MULTIPOINT((3 3)) |
|    3 | POINT(2 2) | MULTIPOINT((2 2)) |
|    4 | POINT(4 4) | MULTIPOINT((4 4)) |
+------+------------+-------------------+
mysql> SELECT ST_AsText( CAST(ST_Collect(g) AS MULTIPOINT)) CASTED
    ->   FROM t2
    ->  WHERE ST_GeometryType(g)='POINT';
+-------------------------------------+
| CASTED                              |
+-------------------------------------+
| MULTIPOINT((1 1),(3 3),(2 2),(4 4)) |
+-------------------------------------+

MULTIPOINT型からPOINT型

 MULTIPOINT型からの変換を確認する元データを格納するために、テーブル t21 を作成します。

CREATE TABLE t21 AS 
SELECT CAST(ST_Collect(g) AS MULTIPOINT) g
  FROM t2
 WHERE ST_GeometryType(g)='POINT';
mysql> SELECT ST_AsText(g) FROM t21;
+-------------------------------------+
| ST_AsText(g)                        |
+-------------------------------------+
| MULTIPOINT((1 1),(3 3),(2 2),(4 4)) |
+-------------------------------------+

 この MULTIPOINT型データからの CAST() を試しましょう。

mysql> SELECT ST_AsText( CAST(g AS POINT) ) P2LS
    ->   FROM t21;
ERROR 4032 (22S01): Invalid cast from MULTIPOINT to POINT.

 複数要素を持つ MULTIPOINT 型は 単一要素であるPOINT型へ変換できなことがわかります。
同じMULTIPOINTでも、要素がひとつしかない場合は POINT型にキャスト可能です。以下の例は、FROMサブクエリ内で 元のデータ(t2テーブル)のPOINTを一旦 1要素のみを持つMULTIPOINT型にした後、POINT型へのCAST()を試したものです。

mysql> SELECT id, ST_AsText(CASTED) ORG, ST_AsText( CAST(CASTED AS POINT)) BTP
    ->  FROM (
    ->     SELECT id, g , 
    ->            CAST(g AS MULTIPOINT) CASTED
    ->       FROM t2
    ->      WHERE ST_GeometryType(g)='POINT'
    ->       ) t;
+------+-------------------+------------+
| id   | ORG                      | BTP        |
+------+-------------------+------------+
|    1 | MULTIPOINT((1 1)) | POINT(1 1) |
|    2 | MULTIPOINT((3 3)) | POINT(3 3) |
|    3 | MULTIPOINT((2 2)) | POINT(2 2) |
|    4 | MULTIPOINT((4 4)) | POINT(4 4) |
+------+-------------------+------------+


 なお、要素数がひとつだけの場合に MULTI* → 単体型 への変換ができるので、予め ST_NumGeometries() 関数を使って要素数を確認し、要素数が1 のデータのみを変換対象にするなどの扱いをすることになろうかと思います。

mysql> SELECT ST_AsText(g), ST_NumGeometries(g) FROM t21;
+-------------------------------------+---------------------+
| ST_AsText(g)                        | ST_NumGeometries(g) |
+-------------------------------------+---------------------+
| MULTIPOINT((1 1),(3 3),(2 2),(4 4)) |                   4 |
+-------------------------------------+---------------------+

MULTIPOINT型からLINESTRING型

 まさにこの変換が欲しかった!という機能。
なんらかのルールに基づき点の集合を MULTIPOINTにまとめた後、それらをつないで線にするといった活用法が考えられます。

mysql> SELECT ST_AsText(g) ORG, ST_AsText( CAST(g AS LINESTRING) ) P2LS
    ->   FROM t21;
+-------------------------------------+-----------------------------+
| ORG                                 | P2LS                        |
+-------------------------------------+-----------------------------+
| MULTIPOINT((1 1),(3 3),(2 2),(4 4)) | LINESTRING(1 1,3 3,2 2,4 4) |
+-------------------------------------+-----------------------------+

 ただし、この、MULTIPOINTを作る際の順序指定がなかなか難しそうで、手元で軽く試した範囲では、順序指定するに至りませんでした。継続調査が必要です(できないはずはないので、私のSQL力の問題だとは思いますが)。例えば上記では、MULTIPOINTを作る時(このテーブルt21を作った時の話です)に ST_X による順序指定ができたら良いのにな、と考えています。あるいは上の実行結果で見るMULTIPOINT型を、順序を並べ替えながら LINESTRINGにCASTできたら、、という考え方もありそうですかね。


その他のキャスト

 MULTILINESTRINGからの変換(MULTIPOLYGON/POLYGON/LINESTRINGへ変換可能)と、MULTIPOLYGONからの変換(POLYGON/MULTILINESTRINGへ変換可能)は今回は割愛します。このうち、

  • MULTILINESTRING→LINESTRING は、例の、1要素のみの場合は簡単そうです
  • MULTIPOLYGON → POLYGONも同様
  • MULTIPOLYGON → MULTILINESTRING も、淡々と要素を分解して入れるだけかな

ということで、あとは 線の集合からPOLYGON系へ変換する場合ですね。これはやや複雑そうな気がします。集合内にある閉じた線が、ひとつの親POLYGONになりたいのか、刳り抜くPOLYGON要素なのかを判断する情報が、MULTILINESTRING内に含まれていないですからね。

まとめ

 MySQL 8.0.24 での CAST() 関数のジオメトリ対応を試してみました。これまでは、「点や線やポリゴンのデータをMySQLに入れて」「判定しながら取り出す」という使い方止まりだったところ、この対応により、「まとめたり」「型を変換したり」ができるようになり、使えるシーンが少し広がったのではないでしょうか。
 今後は、複数のPOLYGONを1個にまとめる(外周を辿ってくれるみたいな)とか、複数のLINESTRINGを一本のLINESTRINGにまとめる(これは CAST() を駆使するとできそうな気がする。あとで試そう)などの関数群も増えていくといいですね。

 MySQLリファレンスマニュアルのSpatial関数のページを見た範囲では、今のところ MySQL 8.0.27 (通常どおりリリースされれば、2021年10月頃リリースされるもの)までの間にジオ関係の新たな関数が追加されるという情報はありませんが、少しずつ機能が増えて、様々な利用シーンに対応できるようになることを楽しみにしています。
 今回の MySQL 8.0.24 は、ジオ的には非常に「大漁」でした!(なお、今回の動作確認は、その後2021年5月に緊急リリースされた MySQL 8.0.25を使用しました)