TinyMysql

TinyMysqlというRubyのライブラリ的何かを作ってみました。

tiny_mysql.rb · GitHub

gist.github.com

これはなに?

MySQLCLIを使ったRubyMySQLライブラリです。 普通のMySQLのライブラリと違ってCLIさえインストールされていればどこでも使えます。

なんでつくったの?

とにかく手軽にRubyMySQLを使いたい、ということがたまにあるんですよ。ないですか?

MySQL/Rubymysql2はCライブラリの依存関係がたまにめんどくさくなるのでCLIで適当にできるライブラリがあるとうれしかったので作りました。

qeuryメソッドの出力はHashなので「文字列をパースして…」とか考えなくてすむのがよいと思います。

使い方

ちょうかんたんな使い方は以下の通り。

#!/usr/bin/env ruby

# ▼▼▼ おまじない ▼▼▼
require 'open-uri'
require 'digest/sha1'

tm = open('https://goo.gl/6z8rE1', &:read)
Digest::SHA1.hexdigest(tm) == '4028a6764164a69086bb28e7a940380bfbf7cc71' or raise 'checksum error'
eval tm
# ▲▲▲ おまじない▲▲▲

require 'pp'
mysql = TinyMysql.new(username: 'scott', password: 'tiger', database: 'mysql')
pp mysql.query('show tables')
# => [{"Tables_in_mysql"=>"columns_priv"},
#     {"Tables_in_mysql"=>"db"},
#     {"Tables_in_mysql"=>"event"},
#     {"Tables_in_mysql"=>"func"},
#     {"Tables_in_mysql"=>"general_log"},
#     {"Tables_in_mysql"=>"help_category"},
#     {"Tables_in_mysql"=>"help_keyword"},
#     {"Tables_in_mysql"=>"help_relation"},
#     {"Tables_in_mysql"=>"help_topic"},
#     {"Tables_in_mysql"=>"innodb_index_stats"},
#     {"Tables_in_mysql"=>"innodb_table_stats"},
#     {"Tables_in_mysql"=>"ndb_binlog_index"},
#     {"Tables_in_mysql"=>"plugin"},
#     {"Tables_in_mysql"=>"proc"},
#     {"Tables_in_mysql"=>"procs_priv"},
#     {"Tables_in_mysql"=>"proxies_priv"},
#     {"Tables_in_mysql"=>"servers"},
#     {"Tables_in_mysql"=>"slave_master_info"},
#     {"Tables_in_mysql"=>"slave_relay_log_info"},
#     {"Tables_in_mysql"=>"slave_worker_info"},
#     {"Tables_in_mysql"=>"slow_log"},
#     {"Tables_in_mysql"=>"tables_priv"},
#     {"Tables_in_mysql"=>"time_zone"},
#     {"Tables_in_mysql"=>"time_zone_leap_second"},
#     {"Tables_in_mysql"=>"time_zone_name"},
#     {"Tables_in_mysql"=>"time_zone_transition"},
#     {"Tables_in_mysql"=>"time_zone_transition_type"},
#     {"Tables_in_mysql"=>"user"}]

ファイルをダウンロードしてローカルで読み込んでもよいと思います。

$ curl https://goo.gl/6z8rE1 -L -o tiny_mysql.rb
$ shasum tiny_mysql.rb
4028a6764164a69086bb28e7a940380bfbf7cc71  tiny_mysql.rb
$ RUBYLIB=. ruby -r tiny_mysql -e 'p TinyMysql.new(username: "scott", password: "tiger").query("select 1")'
[{"1"=>"1"}]

Roadworker v0.5.6.beta7とIAMキーをPublicにした件

トレタのブログのおかげでちょっとだけスターが増えたRoadworkerを更新し、v0.5.6.beta7をリリースしました。

ヘルスチェックの新しい属性regionsをちゃんとサポートするようにしました。

github.com

で、ですね。ついでにTravis CIで使っているIAMキーをパブリックにしました。

今まではセキュリティ上の理由で暗号化された環境変数を使っていたんですが、これだとプルリクエストのCIがこけるんですよね。RoadworkerのテストはS3・CloudFront・ELBが絡んでいるので、ローカルでのテスト環境を構築するのがかなりめんどくさいんですよ。なんで、プルリクエストでCIが回らないのはかなり痛いんですが、セキュリティ的になぁ…と思っていたら、コンテナベースのTravis CIのIPアドレスFixしてた

なんで権限しぼれば大丈夫かなーということで、ソースIPと権限を絞った上で環境変数の暗号化をやめました。さすがに$50/月のトラフィックフローまわりのAPIたたかれると即破産なので許可してませんが、それ以外のRoute53の操作はとりあえず許可するようにしています。

プルリクエストで悪さをできなくもないんですが、基本的にCI回るたびにまっさらにしてるし、プルリクエストは全部補足しているので問題ないかな、と。むしろ破産する勢いでプルリクが来るようならそれはそれでありがたいです。

ということで、プルリクできちんとCIが回るようになって、修正に問題ないかはある程度わかるようになりましたので、皆様のプルリクをお待ちしています m(_ _)m

Hashのエントリの順序を操作するhash_order_helperを作った

Hashのエントリの順序を破壊的に更新したいということが希によくあるので、エントリの順序をArrayのように操作できるメソッドを追加するhash_order_helperを作った。

github.com

以下のような感じで、Hashのエントリの順序を操作できる。

require 'hash_order_helper'

hash = {b: 200, a: 100, c: 150}

hash.sort_pair
#=> {:a=>100, :b=>200, :c=>150}

hash.unshift(d: 300)
#=> {:d=>300, :b=>200, :a=>100, :c=>150}

追加されるメソッドは以下の通り。

  • sort_pair -> Hash
  • sort_pair! -> Hash
  • sort_pair_by {|key, value| ... } -> Hash
  • sort_pair_by! {|key, value| ... } -> Hash
  • at(nth) -> Array
  • insert(nth, hash) -> Hash
  • last -> Array
  • last(n) -> Array
  • pop -> Array
  • pop(n) -> Array
  • push(key, hash) -> Hash
  • unshift(key, hash) -> Hash

シンプルなERBのヘルパー ERBh を作った

シンプルなERBのヘルパー ERBh を作った…というか、先日作ったrspec-match_fuzzyから分離・独立させた。

github.com

以下のように、1メソッドで変数の定義とEBBの評価が行える。

require 'erbh'
include ERBh

erbh('<%= @foo %>, <%= @bar %>', foo: 100, bar: 'zoo')
#=> "100, zoo"

trim modeも使える。

erbh(<<-EOS, {foo: 1..3}, trim_mode: '-')
<%- @foo.each do |i| -%>
<%= i %>
<%- end -%>
EOS
#=> "1\n2\n3\n"

モダンな書式のHash#inspectを作った

なんだかんだで必要になるわりにそれっぽいgemが見つからないので、モダンな書式のHash#inspect — hash_modern_inspectを作った。

github.com

使い方は以下の通り。

hash =  {
  foo: ['FOO', {baz: 100}],
  bar: {zoo: 200},
  "hoge"=>"piyo"
}

hash.modern_inspect
#=> '{foo: ["FOO", {baz: 100}], bar: {zoo: 200}, "hoge"=>"piyo"}'

modern_inspect_without_braceを使うと前後のブレースを削除した文字列を出力する。

hash.modern_inspect_without_brace
#=> 'foo: ["FOO", {baz: 100}], bar: {zoo: 200}, "hoge"=>"piyo"'

空白空行を無視するrspec-match_fuzzyを作った

なんだかんだで必要になるので、空白空行を無視するrspecのmatcher、rspec-match_fuzzyを作った。

github.com

以下の例は空白空行を無視してマッチする。

it '...' do
  str1 = <<-EOS
    London Bridge
    Is Broken down,
    Dance over my Lady Lee.
    London Bridge
    Is Broken down
    With a gay Lady.
  EOS

  str2 = <<-EOS
    London  Bridge
    Is  Broken  down,

    Dance over my Lady Lee.


    London\tBridge
    Is\tBroken\tdown
    With a gay Lady.
  EOS

  expect(str1).to match_fuzzy str2
end

差異がある場合はdiffが出る。

f:id:winebarrel:20160409220635p:plain

また、ERBのヘルパーメソッドも追加した。

include RSpec::MatchFuzzy

it '...' do
  result = erb("<%= @foo %>, <%= @bar %>", foo: 100, bar: 'zoo')
  expect(result).to eq '100, zoo'
end

Ridgepole v0.6.4

業務でも使ってみて問題なさそうな感じなので v0.6.4 をリリースしました。

github.com

主な変更点は以下の通りです。

  • Add --use-external-script option
  • Add --mysql-use-alter option
  • Add --alter-extra option
  • Add --dump-with-default-fk-name option
  • Support t.index
  • Remove migration_comments support
  • Fix fk apply order

--use-external-script / --mysql-use-alter

以前の記事にも書きましたが、--use-external-scriptは外部スクリプトで変更を実施するためのオプションです。

https://github.com/winebarrel/ridgepole#execute-sql-using-external-script

$ cat test.sh
#!/bin/sh
SQL="$1"
CONFIG_JSON="$2"
echo "$SQL" | mysql -u root my_db

$ ridgepole -c config.yml --apply --external-script ./test.sh

変更を適用するときに、SQLを外部のスクリプトに渡すことができるようになります。 --mysql-use-alterはそれに関連して、CREATE/DROP INDEXの代わりにALTER TABLEを使うようになるオプションです。 この方が外部スクリプトでのパースが楽なので…

業務では、CIで「実行時間長すぎ」と言われたクエリについては、自動的に「pt-oscでクエリを実行する」スクリプトを出力して、それを手動実行みたいなことをやっています。がんばればpt-oscで勝手に適用、とかまでできると思いますが、そこまでやったことはないです。

--alter-extra

--alter-extraMySQLのALRER文にLOCK=NONEとかつけるためのオプションです。

https://github.com/winebarrel/ridgepole#add-extra-statement-to-alter

$ ridgepole -a -c database.yml --alter-extra="LOCK=NONE" --debug
Apply `Schemafile`
...
-- add_column("dept_manager", "to_date2", :date, {:null=>false, :after=>"from_date"})
   (42.2ms)  ALTER TABLE `dept_manager` ADD `to_date2` date NOT NULL AFTER `from_date`,LOCK=NONE
   -> 0.0428s
-- remove_column("dept_manager", "to_date")
   (46.9ms)  ALTER TABLE `dept_manager` DROP `to_date`,LOCK=NONE
   -> 0.0471s

--dump-with-default-fk-name / fk apply order

外部キーに関して二点ほど。

Railsでadd_foreign_keyを実行したとき、名前をつけないと自動的にfk_rails_e74ce85cbcみたいな名前がつけられるんですよね。 ただ、この名前だと、スキーマをダンプしたときにname: が出力されない…

ridgepole的には外部キーに名前ついていないと困るので、--dump-with-default-fk-nameオプションで強制的にダンプできるようにしました。

あと、外部キーに関してはテーブルの作成順を考慮しないとエラーになる問題があったのですが、どういう定義順であっても、まずテーブルの作成を行ってから、外部キーを張るように修正しました。

Support t.index / Remove alias_method_chain

プルリクをいただきまして、

t.indexがサポートされて、alias_method_chainが削除されました。 Rails5対応への足がかりができて、ありがたいことです。

また、それに付随して、migration_commentsのサポートを切りました。 migration_commentsについては対応状況が微妙だったのと、prependとalias_method_chainが同居する状況は避けたかったので、外した感じです。 今後も追加はないかなぁ…

次バージョンついて

とりあえず、以下を進めていきたいなぁ…と考えています。

  • Rails5サポート
  • MySQL 5.7サポート
    • 特にJSONカラムと生成カラム
  • 可能ならViewのサポート
    • MySQLでうまい手が見つからないのですが…