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, LinuxmacOSは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によってご指摘いただけたのでパスワード周りを訂正

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サーバー側でユーザーを作ってない段階でホスト側のmysqlから接続しに行くと以下のエラー。エラーに出力されるIPアドレス172.17.0.1はDockerコンテナのネットワークのもの。

ERROR 1130 (HY000): Host '172.17.0.1' is not allowed to connect to this MySQL server

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 APIMySQL 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クライアントからのSQLMySQL 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クライアントからのSQLMySQL 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クライアントからのSQLMySQL Shellのテーブル一覧取得MySQL Shellのコレクション一覧取得
コマンド
mysql> SHOW TABLES;

                                          • +
Tables_in_x_sample
                                          • +
tbl_sql
tbl_x
                                          • +

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クライアントからのSQLMySQL 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();
Query OK, 1 item affected (0.07 sec)

mysql-js> db.tbl_x.add([{"id": 4, "name": "Luis", "Team": "Development"}, {"id": 5, "name": "Rebeca", "Team": "Marketing"}]).execute();
Query OK, 1 item affected (0.08 sec)

一般ログ
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クライアントからのSQLMySQL 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)

一般ログ

SELECT * FROM tbl_sql

SELECT * FROM `x_sample`.`tbl_sql`

SELECT doc FROM `x_sample`.`tbl_x`

絞り込み検索





mysqlクライアントからのSQLMySQL 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 tbl_sql WHERE JSON_UNQUOTE(JSON_EXTRACT(doc,'$.name')) LIKE 'Mike'

SELECT `doc` FROM `x_sample`.`tbl_sql` WHERE (JSON_UNQUOTE(JSON_EXTRACT(`doc`,'$.name')) LIKE 'Mike')

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();

MySQL的2015年10大ニュース

1) MySQL 20周年
http://www.tocker.ca/2015/05/22/mysql-is-20-years-old-tomorrow.html

2) 日本MySQLユーザ会 (MyNA) ドメインから登録15年
http://orablogs-jp.blogspot.jp/2015/10/15-years-of-myna-japan-mysql-user-group.html

3) OracleによるInnobase買収から10年
http://www.oracle.com/us/corporate/Acquisitions/innobase/index.html

4) OracleによるSun Microsystemsから5年
https://www.oracle.com/sun/index.html

5) MySQL 5.7 GA
https://www.oracle.com/jp/corporate/pressrelease/jp20151022.html

6) MySQL 5.8 機能要望募集中
http://www.tocker.ca/2015/09/14/what-would-you-like-to-see-in-mysql-5-8.html

7) MySQL User Conference Tokyo 2015 (7年ぶりの開催)
https://eventreg.oracle.com/profile/web/index.cfm?PKWebId=0x29733284b7

8) 罠&罠
http://www.slideshare.net/yoku0825/mysql-57-51945745
http://yoku0825.blogspot.jp/2015/12/2015mysql-57.html
http://downloads.mysql.com/presentations/20151030_05_MySQL-Parameter-comparison.pdf
http://qiita.com/advent-calendar/2015/mysql57-yoku0825-traps

9) 次世代のMySQLに向けた各種の布石
http://dev.mysql.com/doc/mysql-router/en/
http://qiita.com/advent-calendar/2015/mysql57-yoku0825-traps
http://mysqlserverteam.com/a-new-data-dictionary-for-mysql/

10) MySQL 5.6 リファレンスマニュアル日本語化
http://www.mysql.gr.jp/mysqlml/mysql/msg/16237