MariaDBの顛末を書き留めておく
MariaDBの開発コミュニティMariaDB Foundationではなく、サポートや有償ツールを販売しているMariaDB Corporationが12ヶ月の間に一気にグダグダすぎることになっていたのでまとめておく。
MariaDB Foundationは複数の企業がスポンサーをしているものの、もちろんMariaDB Corporationが筆頭のスポンサーであり、コアメンバーにMariaDB Corporationの社員もいる。
一連の事案がニュースとしてのバリューがないのかMariaDB自体を追ってる人がいないのか、上場するとの発表以外は日本のIT系メディアではほとんどスルーされている状態である。
顛末
- 2022年2月1日 NYSE上場のSPAC(特別買収目的会社)と合併する形でNYSEに上場すると発表
※SPAC: https://smbiz.asahi.com/article/14622436 - 2022年5月-6月 JPモルガンとゴールドマンがこの案件の事務幹事会社を降りる (この時点でいろいろヤバい)
※ https://www.thedeal.com/spacs/jpm-goldman-cut-ties-with-angel-pond-mariadb-deal/ - 2022年12月19日 SPACとの統合で看板を掛け替えMariaDBが上場していることに
初値は$11.55だが同日終値は$6.75 その後数日で初値の1/3以下に下落 - 2023年1月18日 出資者の99%が資金撤退の方向済み 6月までに資金ショートのリスクが出る
※ https://twitter.com/ElinaLappalaine/status/1615655206460481537 - 2023年2月1日 従業員の8%をリストラすると発表
※ https://www.marketscreener.com/quote/stock/MARIADB-PLC124600271/news/MARIADB-PLC-Regulation-FD-Disclosure-form-8-K-42872884/
これが12ヶ月足らずの出来事である。この後で元々の投資家の一角だった中国のアリババが7%弱の株を取得していたり別の投資家グループによる動きも多少はあるが株価は$3台の最安値を付けていたりする。
なおSPACを使った上場は、ベンチャー企業の資金調達を支援して成長を加速させる可能性を秘めているものの、本来の上場の基準にはるかに満たないような企業まで上場させることになってしまうこともあるため裏口上場と呼ばれたりしているケースも。上記と同じような事案がすでに多発していて2022年3月末にSPAC案件を担当する証券会社や投資銀行への規制の強化がされ、2社のアドバイザー引き受けからの撤退につながっている。
2023年2月15日追記
2月13日付で12月31日締めの第一四半期決算が公開されたが売り上げの1.5倍ぐらい販管費にかかる状態は変わらず。決算書の読み方を間違えてると思いたいが債務超過状態に見える。現預金や売掛金がそれなりにあるものの、この状態の後で出資者が撤退となると資金ショートのリスクも現実味を帯びてくる。
2023年4月9日追記
上記の1月18日部分を修正。元記事を翻訳しつつ読んでたけど誤りがあった模様。出資者の資金引き上げはまさかの上場的なものの前日。。。
2023年5月30日追記
4月上旬にしばらく空席だったCFOが採用され、いくつかプレスリリースを打って挽回を試みたものの、5月の中旬にはついに株価が1ドルを切ってしまった。
2023年5月31日追記
今度はCEO, CRO, CTO交代の記事。CRO (chief revenue officer)がもともといて交代なのかは知らんけど。
finance.yahoo.com
ちなみに6月5日付けで就任するCTOはPostgreSQLのコントリビューターだったりOracle DBの記事書いていたりでMySQLやMariaDB色が見られないのが興味深いところ。
Jupyter NotebookとMySQL X DevAPI
Pythonのコードを試すのに便利なJupyter Notebookを使って、Connector/PythonからMySQL X DevAPIを試す環境を作ってみました。Jupyter Notebookの利用には Anaconda に含まれるPythonとJupyter を使うことが強く推奨(strongly recommend)されています。
https://jupyter.org/install.html
ちなみにAnacondaは「データサイエンス向けに作成された Pythonパッケージ」と Python.jp で紹介されています。WindowsでもmacOSでもGUIのインストーラでさくさくインストールできます。
AnacondaへのConnector/Pythonのインストール
Anaconda.orgにパッケージをインストールする場合は conda install コマンドを使うとされています。
https://conda.io/docs/user-guide/tasks/manage-pkgs.html#installing-packages-from-anaconda-org
ところがAnaconda.orgには、標準のanacondaチャンネルではConnector/Python 2.0.4が、その他のチャンネルでも8.0系にバージョン番号ポリシーが変更される前の2.2.3が最新という状態で、Connector/Python 8.0をインストールすることができません。
https://anaconda.org/anaconda/mysql-connector-python
そこでnon-condaパッケージのインストールという手順を使います。
https://conda.io/docs/user-guide/tasks/manage-pkgs.html#installing-non-conda-packages
pipパッケージ管理ツールでmysql-connector-pythonパッケージをバージョン番号を指定して、Connector/Python 8.0.6をインストールします。
https://pypi.python.org/pypi/mysql-connector-python
# Anacondaの仮想環境の確認 sakila:~ sakila$ conda info --envs # conda environments: # root * /Users/sakila/anaconda3 # 仮想環境rootをアクティベート # Windowsではこのコマンドでエラーなくアクティベートできた # macOSではエラーになるのでエラーメッセージのsource付きコマンドを実行 sakila:~ sakila$ activate root Error: activate must be sourced. Run 'source activate envname' instead of 'activate envname'. sakila:~ sakila$ source activate envname CondaEnvironmentNotFoundError: Could not find environment: envname . You can list all discoverable environments with `conda info --envs`. sakila:~ sakila$ source activate root # pipコマンドでConnector/Python 8.0.6をインストール (root) sakila:~ sakila$ pip install mysql-connector-python==8.0.6 Collecting mysql-connector-python==8.0.6 Downloading mysql_connector_python-8.0.6-cp36-cp36m-macosx_10_12_x86_64.whl (3.2MB) 100% |======================================| 3.2MB 413kB/s Installing collected packages: mysql-connector-python Successfully installed mysql-connector-python-8.0.6
これでJupyter NotebookからConnector/Python 8.0.6を利用できるようになりました。
MySQL 8.0.4のインストールとX Pluginの登録
MySQL 8.0.4に関してはWindowsはZIP, LinuxやmacOSはTARを展開するのが手っ取り早いです。ちなみにWindows用のMySQL Installerは8.0.4が用意されていませんでした。--initializeまたは--initialize-insecureなどで初期化し、起動しておきます。
X DevAPIをサポートするためにX Pluginを登録します。
https://dev.mysql.com/doc/refman/8.0/en/document-store-setting-up.html
MySQL Shellがインストールしてあれば下記のコマンドでX Pluginが登録できます。
mysqlsh -u user -h localhost --classic --dba enableXProtocol
MySQL Shellがなければmysqlクライアントからサーバーに接続し、下記のコマンドを実行します。
mysql> INSTALL PLUGIN mysqlx SONAME 'mysqlx.so';
なおWindows上ではmysqlx.soだとファイルが見つからないためエラーとなるので、mysqlx.dllに変更します。
X DevAPIの動作確認のためJSONデータを含んだサンプルデータベースworld_xデータベースを下記からダウンロードし、展開してロードしておきます。
https://dev.mysql.com/doc/index-other.html
mysql -uroot < /foo/bar/world_x-db/world_x.sql
Jupyter Notebookからの動作確認
Anaconda Navigatorを起動しJupyter Notebookを選択します。起動直後のJupyter NotebookはWebブラウザ上にフォルダの一覧が表示される状態となっています。必要に応じてノート作成のフォルダを作成するか選択し、その後、画面右上のNewのプルダウンからPython 3を選択し、Notebookを新規作成します。
NotebookではIn [1]:の横のテキストボックスにPythonのコードを記載し、Shift + Enterで実行することができます。
まずは動作確認を兼ねて、Connector/Pythonのリファレンスマニュアルのサンプルコードを組み合わせて、稼働中のMySQLサーバーのバージョンをVERSION()関数で確認してみます。
from mysql.connector import (connection) cnx = connection.MySQLConnection(user='root', host='127.0.0.1') cur = cnx.cursor(buffered=True) cur.execute("SELECT VERSION()") print(cur.fetchone()) cur.close() cnx.close()
続いてSQLを変更して実際のデータとして先ほどインポートしたworld_xスキーマのcountryinfoテーブルのデータを取得してみます。このテーブルはCollectionとなるテーブル構造(JSON型の列とJSONドキュメント内の_id要素を抽出したGenerated Columnの_id列のみ、_id列は主キー)となっています。
mysql> desc world_x.countryinfo; +-------+-------------+------+-----+---------+------------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+------------------+ | doc | json | YES | | NULL | | | _id | varchar(32) | NO | PRI | NULL | STORED GENERATED | +-------+-------------+------+-----+---------+------------------+ 2 rows in set (0.02 sec)
from mysql.connector import (connection) cnx = connection.MySQLConnection(user='root', host='127.0.0.1') cur = cnx.cursor(buffered=True) cur.execute("SELECT * FROM world_x.countryinfo") print(cur.fetchone()) cur.close() cnx.close()
Jupyter NotebookからのX Dev APIでのアクセス
先ほどと同様に、リファレンスマニュアルのサンプルコードを少し変更して、SQL文でもアクセスしたCollectionであるcountryinfoテーブルにX Dev APIでアクセスしてみます。ここでは10ドキュメント(=10行)取得して配列docに格納、その先頭レコードのうちName要素を取得して表示しています。インポート対象パッケージはmysqlxになっています。
import mysqlx # Connect to server on localhost session = mysqlx.get_session({ 'host': 'localhost', 'port': 33060, 'user': 'root', 'password': '' }) schema = session.get_schema('world_x') # Use the collection 'my_collection' collection = schema.get_collection('countryinfo') # Specify which document to find with Collection.find() result = collection.find().limit(10).execute() # Print document docs = result.fetch_all() print('Name: {0}'.format(docs[0]['Name'])) session.close()
ちなみにMySQLサーバーのユーザーにパスワードがない場合、ポート3306を使用するMySQL標準プロトコルの例では接続情報にパスワードに関する表記はなくても問題ありませんが、X Dev APIの場合は空文字を指定しないと接続時にエラーとなります。
もしかして気づかれていないかも知れないMySQLのパスワード関連の機能
validate_passwordプラグイン
MySQL 5.6で登場し、MySQL 5.7.8からはYumまたはSLESレポジトリからのインストール、もしくはrpmパッケージでインストールした場合に有効になっているのがvalidate_passwordプラグインです。validate_passwordプラグインはパスワード強度の検証を行うプラグインです。「初心者殺しの罠」だの「クソ機能→速攻削除」だの言われちゃうかわいそうなかわいそうな機能だったりします。tarで入れればデフォルトでは無効なのでrpmじゃなくてtarで入れよう。
機能と設定の概要はMySQL 5.6の日本語リファレンスマニュアルを参考にしてみて下さい。
validate_passwordプラグインでは以下の項目を検証します。
https://dev.mysql.com/doc/refman/8.0/en/validate-password-options-variables.html
validate_password_check_user_name | パスワードとユーザ名を比較 |
validate_password_dictionary_file | 禁止されたパスワードのリストを格納した辞書ファイル名 |
validate_password_length | 最低文字数 |
validate_password_mixed_case_count | 大文字と小文字を含めることを強制 |
validate_password_number_count | 数字の数 |
validate_password_special_char_count | 特殊文字の数 |
validate_password_policy | ポリシーの強度 |
validate_password_check_user_nameはMySQL 5.7.15 (= GA後) にて追加されたオプションです。このパラメタを ON に設定すると、パスワードの設定や変更の際にユーザー名、もしくは逆になっていないかの検証が行われます。ここでのユーザー名は USER()およびCURRENT_USER()のユーザー名部分です(ホスト名は検証に利用されない)。またバイト列でのユーザー名との比較となります。
パスワード再利用ポリシー
以前使ったパスワードを再利用させない面倒くさいポリシーを設定できる機能がMySQL 8.0.3 RCで追加されました。また「速攻削除」とか言われちゃうのかな。。。とりあえずデフォルトではオフになっているので安心して下さい。
https://dev.mysql.com/doc/refman/8.0/en/password-management.html#password-reuse-policy
設定項目は下記の2つです。
password_history | 何世代のパスワードを利用不可とするか |
password_reuse_interval | 同じパスワードはどれだけ経たないと再利用できないか |
このパラメータはサーバー全体の設定値となります。MySQL 8.0の新機能 SET PERSIST 文 でも設定できます。
CREATE USER文やALTER USER文でユーザーごとに設定することもできます。
mysql> CREATE USER 'yoku0825'@'localhost' PASSWORD HISTORY 825; mysql> ALTER USER 'soudai1025'@'localhost' PASSWORD REUSE INTERVAL 1025 DAY;
いい感じの設定ができますね。
変更したパスワードは新たに追加されたpassword_historyテーブルに格納されていきます。
mysql> DESC password_history; +--------------------+--------------+------+-----+----------------------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------------+--------------+------+-----+----------------------+-------+ | Host | char(60) | NO | PRI | | | | User | char(32) | NO | PRI | | | | Password_timestamp | timestamp(6) | NO | PRI | CURRENT_TIMESTAMP(6) | | | Password | text | YES | | NULL | | +--------------------+--------------+------+-----+----------------------+-------+ 4 rows in set (0.00 sec)
例えばパスワードを変更し、確認してみると下記のような状態となります。(パスワードの文字列はダミーです。たぶん)
mysql> ALTER USER 'yoku0825'@'localhost' IDENTIFIED BY 'ConoHa'; mysql> ALTER USER 'yoku0825'@'localhost' IDENTIFIED BY 'Anzu'; mysql> SELECT * FROM password_history; +-----------+----------+----------------------------+-------------------------------------------+ | Host | User | Password_timestamp | Password | +-----------+----------+----------------------------+-------------------------------------------+ | localhost | yoku0825 | 2017-12-06 00:02:22.120225 | *01234567890123456789A1FB252ADCCED23DHOGE | | localhost | yoku0825 | 2017-12-06 00:01:45.514278 | *9876543210987654321006CDBDBBE5538249FUGA | +-----------+----------+----------------------------+-------------------------------------------+
ちなみにMySQL 5.7.9 GAで話題となり、MySQL 5.7.11でデフォルト値が360日から0 (= 無制限) に変更された default_password_lifetime はMySQL 8.0.3 RCの時点でのデフォルト値は0となっています。
https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_default_password_lifetime
GAでどうなるかは誰も知りません。
MySQL Fabric? 知らない子ですねぇ とりあえず可用性関連の製品が単一障害点を持ってたり、開発エンジニアが単一障害点とか悪い冗談ですよね。
MySQLの公式Dockerイメージを使おうとしたときのメモ
(追記) MySQL界が誇る優秀な外部APIによってご指摘いただけたのでパスワード周りを訂正
`-e MYSQL_ALLOW_EMPTY_PASSWORD=1` でrootのパスワード空っぽにできるますよ;) @RKajiyama
— yoku0825 (@yoku0825) August 21, 2017
MySQLの公式Dockerイメージを使おうとしたときのメモ - rkajiyamaの日記https://t.co/hqEWOTdxID
MySQLサーバーのコミュニティ版バイナリが含まれる公式Dockerイメージのマニュアルはこちら。
https://hub.docker.com/r/mysql/mysql-server/
マニュアルに書いてある基本の起動するコマンド
docker run --name my-container-name -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql/mysql-server:tag
オプション | 設定項目 |
---|---|
--name | my-container-nameは任意の名称 |
-d | 対象イメージ タグでMySQLのバージョン等を指定 |
-e | 環境変数 マニュアル中段のEnvironment Variables |
-p | ポートフォワーディング設定 ホスト側:コンテナ側 |
-v | ディレクトリのマッピング ホスト側:コンテナ側 |
バージョン等を指定するためのタグの一覧
5.5から8.0のマイナーバージョンを指定可能(全てではない)
https://hub.docker.com/r/mysql/mysql-server/tags/
起動コマンド例
docker run --name my80 -d mysql/mysql-server:8.0 -e MYSQL_ROOT_PASSWORD=mysql -p 6603:3306 -v /Users/mysql/mysql-docker/data:/var/lib/mysql
ちょっとしたハマりどころ
MySQLサーバーのパスワード設定は必須必須ではない、けどオプション必須
初期化オプション --initialize-insecure に該当する設定はなさそうあるけどオプションの指定が必要。パスワード関連の環境変数が設定がされていないとコンテナが起動しない。MYSQL_RANDOM_ROOT_PASSWORDとMYSQL_ONETIME_PASSWORDを設定すると --initialize と同様の動きになる模様。MYSQL_ALLOW_EMPTY_PASSWORDを使うと --initialize-insecure と同様に。
MYSQL_ROOT_PASSWORD | 明示的にパスワードを設定 |
MYSQL_RANDOM_ROOT_PASSWORD | ランダムパスワードを設定 |
MYSQL_ONETIME_PASSWORD | 初回ログイン時にパスワードの変更を強制 |
MYSQL_ALLOW_EMPTY_PASSWORD | --initialize-insecureと同様にパスワード無しを許容 |
ランダムパスワードは下記コマンドで表示されるログにて確認可能
docker logs my-container-name
一瞬では起動しない(いつ起動が完了したか分からない)
コンテナの起動コマンドを打つと一瞬で応答が返ってきて出力もきわめてシンプル。MySQLサーバーがすでに起動しているのかと思ったらまだだったりする。
docker psのSTATUS表示がhealth: startingなら起動中、healthyなら起動済み。
(docker runコマンドのオプションでログをコンソールに出すようにすればよさげな話だけど)
MySQL 5.7.17で追加されたあのプラグインについて
この記事は MySQL Casual Advent Calendar 2016 の13日目の記事です!
MySQL 5.7.17がリリースされたわけですが、みなさん待望のあの機能がついに追加されました。なので早速試してみましょう。
プラグインとしての機能追加なので、まずはプラグインをインストールします。
http://dev.mysql.com/doc/refman/5.7/en/connection-control-plugin-installation.html
mysql> INSTALL PLUGIN connection_control SONAME 'connection_control.so'; Query OK, 0 rows affected (0.02 sec) mysql> INSTALL PLUGIN connection_control_failed_login_attempts SONAME 'connection_control.so'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS -> WHERE PLUGIN_NAME LIKE 'connection%'; +------------------------------------------+---------------+ | PLUGIN_NAME | PLUGIN_STATUS | +------------------------------------------+---------------+ | CONNECTION_CONTROL | ACTIVE | | CONNECTION_CONTROL_FAILED_LOGIN_ATTEMPTS | ACTIVE | +------------------------------------------+---------------+ 2 rows in set (0.00 sec)
プラグインのインストール時にシステム変数ならびにステータス変数が追加されます。
mysql> SHOW GLOBAL VARIABLES LIKE 'connection_control%'; +-------------------------------------------------+------------+ | Variable_name | Value | +-------------------------------------------------+------------+ | connection_control_failed_connections_threshold | 3 | | connection_control_max_connection_delay | 2147483647 | | connection_control_min_connection_delay | 1000 | +-------------------------------------------------+------------+ 3 rows in set (0.01 sec) mysql> SHOW GLOBAL STATUS LIKE 'connection_control%'; +------------------------------------+-------+ | Variable_name | Value | +------------------------------------+-------+ | Connection_control_delay_generated | 0 | +------------------------------------+-------+ 1 row in set (0.01 sec)
システム変数については以下の通りです。
connection_control_failed_connections_threshold | 接続に失敗した場合に遅延を適用するまでの回数の閾値 |
connection_control_min_connection_delay | 当初の遅延時間(ミリ秒) |
connection_control_max_connection_delay | 最大の遅延時間(ミリ秒) |
閾値を超えて接続に失敗するたびにconnection_control_min_connection_delay分の遅延が追加されます。
ではデフォルト値のまま試してみましょう。
$ while true; do time ./mysql -uscott -ptiger; done mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 1045 (28000): Access denied for user 'scott'@'localhost' (using password: YES) real 0m0.014s user 0m0.007s sys 0m0.004s mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 1045 (28000): Access denied for user 'scott'@'localhost' (using password: YES) real 0m0.014s user 0m0.007s sys 0m0.003s mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 1045 (28000): Access denied for user 'scott'@'localhost' (using password: YES) real 0m0.010s user 0m0.006s sys 0m0.003s mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 1045 (28000): Access denied for user 'scott'@'localhost' (using password: YES) real 0m1.017s user 0m0.006s sys 0m0.003s mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 1045 (28000): Access denied for user 'scott'@'localhost' (using password: YES) real 0m2.015s user 0m0.007s sys 0m0.004s mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 1045 (28000): Access denied for user 'scott'@'localhost' (using password: YES) real 0m3.017s user 0m0.007s sys 0m0.004s mysql: [Warning] Using a password on the command line interface can be insecure. ERROR 1045 (28000): Access denied for user 'scott'@'localhost' (using password: YES) real 0m4.018s user 0m0.006s sys 0m0.005s mysql: [Warning] Using a password on the command line interface can be insecure.
4回目の失敗から遅延が1秒ずつ追加されているのが分かります。
このConnection-Controlプラグインによって総当たり攻撃(ブルートフォースアタック)などの対策とすることができそうです。
MySQL 5.7.17ではマルチマスター型レプリケーションのグループレプリケーション プラグインがGAになるという大きな機能追加がありました。グループレプリケーションと類似するGalera Clusterとの機能の比較や性能比較も公開されています。グループレプリケーションはきっと誰かが書いてくれると思うので、気づいていない人も多そうなConnection-Controlプラグインについて書いてみました。
MySQL 5.7のGeoHash
MySQL 5.7で追加された機能一つにBoost.Geometryとの統合によるGISの強化があります。さらにGeoHashという機能が追加されています。GeoHashは地点の情報をハッシュ値として格納する機能で、ハッシュ長によって精度を変えることができます。緯度経度の値から長さの異なるGeoHashを生成し、そこから再度緯度経度の値を算出してみます。今回の例では直線距離で約3km離れた2地点の緯度経度を利用しています。
なお緯度経度の情報および十進表記は下記のサイトの情報を使いました
http://user.numazu-ct.ac.jp/~tsato/webmap/sphere/coordinates/advanced.html
関数 | 内容 |
ST_GeoHash() | 緯度経度の値またはPOINT型の値からGeoHash値を生成 |
ST_LatFromGeoHash() | GeoHash値から緯度を算出 |
ST_LongFromGeoHash() | GeoHash値から経度を算出 |
ST_PointFromGeoHash() | GeoHash値から緯度経度を算出しPOINT型の値を返す |
# 横浜スタジアム ホームベース付近 北緯35°26′35″ 東経139°38′25″ # 十進表記 北緯35.442972 東経139.640223 mysql> SELECT -> ST_GeoHash(139.640223, 35.442972, 4) AS GeoHash4, -> ST_GeoHash(139.640223, 35.442972, 32) AS GeoHash32; +----------+------------------------+ | GeoHash4 | GeoHash32 | +----------+------------------------+ | xn73 | xn739s4ym0c53e0pys2c9g | +----------+------------------------+ 1 row in set (0.01 sec) mysql> SELECT -> ST_LatFromGeoHash(ST_GeoHash(139.640223, 35.442972, 4)) AS GeoHash4, -> ST_LatFromGeoHash(ST_GeoHash(139.640223, 35.442972, 32)) AS GeoHash32; +----------+-----------+ | GeoHash4 | GeoHash32 | +----------+-----------+ | 35.4 | 35.442972 | +----------+-----------+ 1 row in set (0.00 sec) mysql> SELECT -> ST_LongFromGeoHash(ST_GeoHash(139.640223, 35.442972, 4)) AS GeoHash4, -> ST_LongFromGeoHash(ST_GeoHash(139.640223, 35.442972, 32)) AS GeoHash32; +----------+------------+ | GeoHash4 | GeoHash32 | +----------+------------+ | 139.7 | 139.640223 | +----------+------------+ 1 row in set (0.01 sec)
# 横浜駅きた西口 北緯35°28′2″ 東経139°37′23″ # 十進表記 北緯35.467360 東経139.623148 mysql> SELECT -> ST_GeoHash(139.623148, 35.467360, 4) AS GeoHash4, -> ST_GeoHash(139.623148, 35.467360, 32) AS GeoHash32; +----------+----------------------------------+ | GeoHash4 | GeoHash32 | +----------+----------------------------------+ | xn73 | xn73c0wkr5j52nh2eub7y5bpbpbpbpbp | +----------+----------------------------------+ 1 row in set (0.00 sec) mysql> SELECT -> ST_LatFromGeoHash(ST_GeoHash(139.623148, 35.467360, 4)) AS GeoHash4, -> ST_LatFromGeoHash(ST_GeoHash(139.623148, 35.467360, 32)) AS GeoHash32; +----------+-------------------+ | GeoHash4 | GeoHash32 | +----------+-------------------+ | 35.4 | 35.46736000000001 | +----------+-------------------+ 1 row in set (0.01 sec) mysql> SELECT -> ST_LongFromGeoHash(ST_GeoHash(139.623148, 35.467360, 4)) AS GeoHash4, -> ST_LongFromGeoHash(ST_GeoHash(139.623148, 35.467360, 32)) AS GeoHash32; +----------+--------------------+ | GeoHash4 | GeoHash32 | +----------+--------------------+ | 139.7 | 139.62314800000001 | +----------+--------------------+ 1 row in set (0.00 sec)
ハッシュ長を4桁にした場合は情報が丸められ、同じ緯度経度の情報になっています。プライバシー保護などの観点から、正確な位置を利用するのではなく大まかな場所やエリアで表示させるなどに利用できそうです。
mysqlクライアントプログラムによるSQLコマンドとMySQL ShellでのJavaScriptベースのコマンドの比較
2016年10月1日 MySQL 5.7.15 + MySQL Shell 1.0.5にて検証のうえ、コマンドおよび一般ログの出力内容更新済み
MySQL 5.7.12のリリースと同時に登場したMySQL Document Storeとそれに関連するX Dev APIやMySQL Shellについて、既存のSQLとの関係をMySQL 5.7.125.7.15にて一般ログの出力を見ながら簡単に調べてみました。
初期設定等はここ↓ここに詳しく書いてあります。
MYSQLXとMYSQL SHELL初期設定と基本動作確認
基本操作はこちらにも書かれています。
MySQL Shell / X DevAPI / X Protocol - @tmtms のメモ
なお一般ログの出力からは日時、クライアントのIDおよびQueryの文字は削除してあります。
2016-05-18T02:50:58.045435Z 13 Query SELECT DATABASE() ↓ SELECT DATABASE()
カレントデータベースの変更
mysqlクライアントからのSQL | MySQL Shellのコマンド | |
---|---|---|
コマンド | mysql> USE x_sample; Database changed |
mysql-js> db = session.getSchema('x_sample'); |
一般ログ | SELECT DATABASE() Init DB x_sample |
show databases like 'x_sample' SHOW TABLES FROM `x_sample` SELECT C.table_name AS name, IF(ANY_VALUE(T.table_type)='VIEW', 'VIEW', IF(COUNT(*) = COUNT(CASE WHEN (column_name = 'doc' AND data_type = 'json') THEN 1 ELSE NULL END) + COUNT(CASE WHEN (column_name = '_id' AND generation_expression = 'json_unquote(json_extract(`doc`,''$._id''))') THEN 1 ELSE NULL END) + COUNT(CASE WHEN (column_name != '_id' AND generation_expression RLIKE '^(json_unquote.(.)?json_extract.(.`doc`,''.$.(...[^[:space:][...]]+)+''.).{1,2}$') THEN 1 ELSE NULL END), 'COLLECTION', 'TABLE')) AS type FROM information_schema.columns AS C LEFT JOIN information_schema.tables AS T USING (table_name)WHERE C.table_schema = 'x_sample' GROUP BY C.table_name ORDER BY C.table_name |
テーブルおよびコレクションの作成
mysqlクライアントからのSQL | MySQL Shellのコマンド | |
---|---|---|
コマンド | mysql> CREATE TABLE `x_sample`.`tbl_sql` (id SERIAL, doc JSON) CHARSET utf8mb4 ENGINE=InnoDB; Query OK, 0 rows affected (0.16 sec) |
mysql-js> db.createCollection('tbl_x'); |
一般ログ | CREATE TABLE `x_sample`.`tbl_sql` (id SERIAL, doc JSON) CHARSET utf8mb4 ENGINE=InnoDB |
CREATE TABLE `x_sample`.`tbl_x` (doc JSON,_id VARCHAR(32) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(doc, '$._id'))) STORED PRIMARY KEY) CHARSET utf8mb4 ENGINE=InnoDB |
テーブル一覧の表示
mysqlクライアントからのSQL | MySQL Shellのテーブル一覧取得 | MySQL Shellのコレクション一覧取得 | ||||
---|---|---|---|---|---|---|
コマンド | mysql> SHOW TABLES;
2 rows in set (0.00 sec) | mysql-js> db.getTables(); [ ] | mysql-js> db.getCollections(); [ ] | |||
一般ログ | Query SHOW TABLES |
SHOW TABLES FROM `x_sample` SELECT C.table_name AS name, IF(ANY_VALUE(T.table_type)='VIEW', 'VIEW', IF(COUNT(*) = COUNT(CASE WHEN (column_name = 'doc' AND data_type = 'json') THEN 1 ELSE NULL END) + COUNT(CASE WHEN (column_name = '_id' AND generation_expression = 'json_unquote(json_extract(`doc`,''$._id''))') THEN 1 ELSE NULL END) + COUNT(CASE WHEN (column_name != '_id' AND generation_expression RLIKE '^(json_unquote.(.)?json_extract.(.`doc`,''.$.(...[^[:space:][...]]+)+''.).{1,2}$') THEN 1 ELSE NULL END), 'COLLECTION', 'TABLE')) AS type FROM information_schema.columns AS C LEFT JOIN information_schema.tables AS T USING (table_name)WHERE C.table_schema = 'x_sample' GROUP BY C.table_name ORDER BY C.table_name |
SHOW TABLES FROM `x_sample` SELECT C.table_name AS name, IF(ANY_VALUE(T.table_type)='VIEW', 'VIEW', IF(COUNT(*) = COUNT(CASE WHEN (column_name = 'doc' AND data_type = 'json') THEN 1 ELSE NULL END) + COUNT(CASE WHEN (column_name = '_id' AND generation_expression = 'json_unquote(json_extract(`doc`,''$._id''))') THEN 1 ELSE NULL END) + COUNT(CASE WHEN (column_name != '_id' AND generation_expression RLIKE '^(json_unquote.(.)?json_extract.(.`doc`,''.$.(...[^[:space:][...]]+)+''.).{1,2}$') THEN 1 ELSE NULL END), 'COLLECTION', 'TABLE')) AS type FROM information_schema.columns AS C LEFT JOIN information_schema.tables AS T USING (table_name)WHERE C.table_schema = 'x_sample' GROUP BY C.table_name ORDER BY C.table_name |
データの追加
mysqlクライアントからのSQL | MySQL ShellのCRUDでの通常のテーブルへのデータの追加 | MySQL ShellのCRUDでのコレクションへのデータの追加 | |
---|---|---|---|
コマンド | mysql> INSERT INTO tbl_sql(doc) VALUES('{"id": 1, "name": "Mike", "Team": "Products"}'); Query OK, 1 row affected (0.06 sec) |
mysql-js> db.tbl_sql.insert(['doc']).values('{"id": 2, "name": "Joe", "Team": "Sales", "Title": "VP"}').values('{"id": 3, "name": "Tomas", "Team": "Development"}').execute(); Query OK, 1 item affected (0.07 sec) |
mysql-js> db.tbl_x.add({"id": 1, "name": "Mike", "Team": "Products"}).execute(); Query OK, 1 item affected (0.10 sec) mysql-js> db.tbl_x.add([{"id": 2, "name": "Joe", "Team": "Sales", "Title": "VP"}, {"id": 3, "name": "Tomas", "Team": "Development"}]).execute(); mysql-js> db.tbl_x.add([{"id": 4, "name": "Luis", "Team": "Development"}, {"id": 5, "name": "Rebeca", "Team": "Marketing"}]).execute(); |
一般ログ | INSERT INTO tbl_sql(doc) VALUES('{"id": 1, "name": "Mike", "Team": "Products"}') |
INSERT INTO `x_sample`.`tbl_sql` (`doc`) VALUES ('{\"id\": 2, \"name\": \"Joe\", \"Team\": \"Sales\", \"Title\": \"VP\"}'),('{\"id\": 3, \"name\": \"Tomas\", \"Team\": \"Development\"}') | INSERT INTO `x_sample`.`tbl_x` (doc) VALUES ('{\"Team\":\"Products\",\"_id\":\"2ae1f178a81ce611620d5a37036c1421\",\"id\":1,\"name\":\"Mike\"}') INSERT INTO `x_sample`.`tbl_x` (doc) VALUES ('{\"Team\":\"Sales\",\"Title\":\"VP\",\"_id\":\"fc451c81a81ce611620d5a37036c1421\",\"id\":2,\"name\":\"Joe\"}'),('{\"Team\":\"Development\",\"_id\":\"28471c81a81ce611620d5a37036c1421\",\"id\":3,\"name\":\"Tomas\"}') INSERT INTO `x_sample`.`tbl_x` (doc) VALUES ('{\"Team\":\"Development\",\"_id\":\"7c7bed8ba81ce611620d5a37036c1421\",\"id\":4,\"name\":\"Luis\"}'),('{\"Team\":\"Marketing\",\"_id\":\"0c7ded8ba81ce611620d5a37036c1421\",\"id\":5,\"name\":\"Rebeca\"}') |
複数ドキュメントの追加には、add()の引数としてドキュメントの配列を渡すか、ドキュメントごとにadd()にドキュメントを渡す
[add()の引数にドキュメントの配列を指定] db.tbl_x.add([{"id": 2, "name": "Joe", "Team": "Sales", "Title": "VP"}, {"id": 3, "name": "Tomas", "Team": "Development"}]).execute(); または [ドキュメントごとにadd()を指定] db.tbl_x.add({"id": 2, "name": "Joe", "Team": "Sales", "Title": "VP"}).add({"id": 3, "name": "Tomas", "Team": "Development"}).execute();
全件取得
mysqlクライアントからのSQL | MySQL ShellのCRUDでの通常のテーブルへの検索 | MySQL ShellのCRUDでのコレクションの検索 | |
---|---|---|---|
コマンド |
mysql> SELECT * FROM tbl_sql; +----+----------------------------------------------------------+ | id | doc | +----+----------------------------------------------------------+ | 1 | {"id": 1, "Team": "Products", "name": "Mike"} | | 2 | {"id": 2, "Team": "Sales", "name": "Joe", "Title": "VP"} | | 3 | {"id": 3, "Team": "Development", "name": "Tomas"} | +----+----------------------------------------------------------+ 3 rows in set (0.00 sec) |
mysql-js> db.tbl_sql.select(); +----+----------------------------------------------------------+ | id | doc | +----+----------------------------------------------------------+ | 1 | {"id": 1, "Team": "Products", "name": "Mike"} | | 2 | {"id": 2, "Team": "Sales", "name": "Joe", "Title": "VP"} | | 3 | {"id": 3, "Team": "Development", "name": "Tomas"} | +----+----------------------------------------------------------+ 3 rows in set (0.01 sec) |
mysql-js> db.tbl_x.find(); [ { "Team": "Marketing", "_id": "0c7ded8ba81ce611620d5a37036c1421", "id": 5, "name": "Rebeca" }, <中略> { "Team": "Sales", "Title": "VP", "_id": "fc451c81a81ce611620d5a37036c1421", "id": 2, "name": "Joe" } ] 5 documents in set (0.00 sec) |
一般ログ |
|
|
|
絞り込み検索
mysqlクライアントからのSQL | MySQL ShellのCRUDでの通常のテーブルへの検索 | MySQL ShellのCRUDでのコレクションの検索 | |
---|---|---|---|
コマンド |
mysql> SELECT doc FROM tbl_sql WHERE JSON_UNQUOTE(JSON_EXTRACT(doc,'$.name')) LIKE 'Mike'; +-----------------------------------------------+ | doc | +-----------------------------------------------+ | {"id": 1, "Team": "Products", "name": "Mike"} | +-----------------------------------------------+ 1 row in set (0.00 sec) |
mysql-js> db.tbl_sql.select(['doc']).where("JSON_UNQUOTE(JSON_EXTRACT(doc,'$.name')) like 'Mike'"); +-----------------------------------------------+ | doc | +-----------------------------------------------+ | {"id": 1, "Team": "Products", "name": "Mike"} | +-----------------------------------------------+ 1 row in set (0.01 sec) |
mysql-js> db.tbl_x.find("name like 'Mike'"); [ { "Team": "Products", "_id": "1436e7f2e61be611620d5a37036c1421", "id": 1, "name": "Mike" } ] 1 document in set (0.00 sec) |
一般ログ |
|
|
SELECT doc FROM `x_sample`.`tbl_x` WHERE (JSON_UNQUOTE(JSON_EXTRACT(doc,'$.name')) LIKE 'Mike') |
MySQL ShellのCRUDでの通常のテーブルへの検索時の注意点
- select()の第一引数は取得する列名の配列、列名はシングルクオートでかこむ
- where()内のLIKEなどの演算子は小文字にする
MySQL Shellでクオーテーションが入れ子になる場合、シングルクオートまたはダブルクオートのいずれを外にしても問題なさそうです。
mysql-js> db.tbl_x.find("name like 'Mike'"); または mysql-js> db.tbl_x.find('name like "Mike"');
検索する値をパラメタ化してバインドしてもSQL的には同じ結果でした。
mysql-js> db.tbl_x.find("name like 'Mike'"); または mysql-js> db.tbl_x.find('name like :param').bind('param','Mike').execute();