私が歌川です

@utgwkk が書いている

SQLでSQLを組み立てる

趣味で作っているアプリケーションについて、データマイグレーションを行いたくなった。ちゃんとやるなら、マイグレーション用のスクリプトを書いて、メンテナンスモードにして、スクリプトを適用する……みたいな手順を踏むと思うけど、趣味プロダクトなので高速に済ませたい。SQLでSQLを組み立てればワンタイムスクリプトを書かなくてもよいのではないか、という考えに思い至ったのでメモする。

以下はSQLiteでの事例だけど、他のDBMSでも似たようなことはできると思う。

組み立て方

文字列連結

SQLiteの文字列連結は || 演算子で行う。

sqlite> SELECT 'a' || 'b';
ab

NULLと文字列を連結するとNULLになり、ターミナル上では空行が出力される。 typeof は値の型を返す関数である。空文字列ではなくNULLが出力されていることを確認した。

sqlite> SELECT 'aaa' || NULL;

sqlite> SELECT typeof('aaa' || NULL);
null

文字列のエスケープ

文字列連結でSQLを組み立てると、当然SQLインジェクションのリスクがある。 user テーブルに name カラムがあって、たとえば ' OR 1=1 -- という文字列が格納されていると考える。以下の例ではすでにSQLインジェクションに成功している。

sqlite> SELECT "UPDATE users SET some_flag = 1 WHERE name = '" || "' OR 1=1 --" || "'";
UPDATE users SET some_flag = 1 WHERE name = '' OR 1=1 --'

SQLiteには quote という関数があり、与えられた文字列をクォートして、さらにエスケープも行ってくれる。

sqlite> SELECT "UPDATE users SET some_flag = 1 WHERE name = " || quote("' OR 1=1 --");
UPDATE users SET some_flag = 1 WHERE name = ''' OR 1=1 --'

ちなみにシングルクォートのエスケープは '' である。

sqlite> SELECT 'a''b';
a'b

ここまでの内容で、単純なUPDATE文ぐらいなら書けるようになったと思う。

WHERE IN を書きたい

ところで、こういうSQLを生成したいときはどうしたらよいのか? IN (...) の中には複数の値が入るが、果たしてSQLで書けるのか。

UPDATE users SET some_flag = 1 WHERE id IN (...)

これは group_concat という関数を使うことで実現できる。

まず、対象となるidをかき集めるSELECT文を用意する。

-- 対象となるidをかき集めるSELECT文
SELECT id FROM users WHERE some_condition;

idを group_concat(id) に置換すると、INクエリに突っ込めそうな文字列ができる。

-- 対象となるidをかき集めるSELECT文
sqlite> SELECT group_concat(id) FROM users WHERE some_condition;
2,4,6,8,10,12,14,16,18,20

あとはうまく文字列連結してやればよい。

sqlite> SELECT 'UPDATE users SET some_flag = 1 WHERE id IN (' || group_concat(id) || ')' FROM users WHERE some_condition;
UPDATE users SET some_flag = 1 WHERE id IN (2,4,6,8,10,12,14,16,18,20)

万が一、対象となるidが存在しない場合は group_concat(id) はNULLを返す。従って文字列連結した結果もNULLとなり、空行が出力される。 UPDATE users SET ... WHERE id IN () のようなクエリを実行してしまいエラー、ということにはならないようだった。そもそもSQLiteの場合はエラーにならないが……*1

WHERE IN の中でUPDATEする対象のidをSELECTするのとの違い

SQLを生成する場合は、具体的にどういう行がUPDATEされるのかをクエリやコメントに出力できる。SELECT文だけで何行UPDATEされるかはデータに対する知識がないとすぐには計算できないけど、クエリに直接idが書いてあったら、なんか多すぎておかしい、全くUPDATEされないじゃん、みたいなことに気づけそう。

UPDATE users SET some_flag = 1 WHERE id IN (...) -- ()の中を見たら、UPDATEされたusers.idが分かる

たとえばこういう感じにしたら、対象となるユーザー名も分かってお得。UPDATE文を生成するなら、UPDATEする前の値もメモしておくと後々調査したくなったときに便利かもしれない。

sqlite> SELECT 'UPDATE users SET some_flag = 1 WHERE id = ' || id || '; -- ' || name FROM users;
UPDATE users SET some_flag = 1 WHERE id = 2; -- john_doe
...

まとめ

SQLでSQLを組み立てることでデータマイグレーションを行えそう。生成したSQLをどこかにメモしておけばあとで見返すことができる。

参考

SQLiteのドキュメントが好きで、よく読んでいる。

Pythonのキーワード引数に任意のdictを展開して渡す

Pythonでは、引数に **kwargs のように書くことで、任意のキーワード引数を受ける関数を定義することができる。 キーワード引数はdictとして使うことができる。

def print_kwargs_as_dict(**kwargs):
    print(kwargs)

キーワード引数では、識別子としてvalidな名前の引数しか渡せない。また、任意の文字列や文字列リテラルを使うことはできない。以下の式はいずれも文法エラーになる。

print_kwargs_as_dict(foo/bar='baz')
print_kwargs_as_dict('foo/bar/'='baz')

ところで、Pythonには任意のdictを展開して関数に渡す文法が定義されている。

kwargs = dict(foo='bar')
print_kwargs_as_dict(**kwargs)
# => {'foo':'bar'}

ここで、関数呼び出しの文法を注意深く眺めると、キーワード引数に任意のdictを展開して渡せることが分かる。つまり以下の式はvalidである。

print_kwargs_as_dict(**{'foo/bar':'baz'})
# => {'foo/bar':'baz'}

ペアプロ活動パターン

仕事でよくペアプロでコードを書くのですが、気をつけていることがいくつかあるので共有します。みなさまは何に気をつけていますか?

考えていることを声に出す

  • やりたいことが実現できずに詰まっていると黙りがち
    • どうやったらいいか分からず困っている・違う結果になる、みたいなのを口に出す
    • ペアの人が解決方法を知っているかもしれない
    • クマさんデバッグみたいな効果もありそう
      • ところで「クマさんデバッグ」って一般に通じる言葉なんですか?
  • 書いているコードの自己評価を行う
    • 最高のアルゴリズムになった、とか、これは一時しのぎです、みたいな
  • 認識がずれていないかの確認をする
    • 「こう思っているけどそれで合ってますか?」

こまめにcommitする

  • こまめにセーブするイメージ
  • git diffを確認したら、どこまでできているのかを見れる
  • いざとなったらgit resetで戻れる
  • ひと通りできあがってからちょっとずつcommitするよりは、こまめにcommitしたほうが、どこまでstageするのかとか考えなくて済む

エディタだけでなくブラウザの様子も見せる

  • リファレンスを参照している様子とか、やり方が分からないのでググっている様子とかも見せる
  • 知見共有みたいな側面が強い
    • こうやって調べてるのか〜みたいなのが伝わるとよさそう
    • ナビゲーターをやるときは、こういうページを参照したり検索したりしてみるといいかも、というヒントを思い付いたら伝えている
  • 映り込むとまずいタブは予め閉じておくか、ウィンドウを分けておきましょう

「クマさんデバッグ」について

ペアプロ活動パターン - 私が歌川です

くまのぬいぐるみについて <a href="https://toya.hatenablog.com/entry/2015/07/09/133609" target="_blank" rel="noopener nofollow">https://toya.hatenablog.com/entry/2015/07/09/133609</a>

2021/06/05 09:21
b.hatena.ne.jp

toya.hatenablog.com

「プログラミング作法」こんど読んでみます。

fetch APIにおけるHTTPリクエストの中断・タイムアウト

AbortControllerを使うことで実現できる。

MDNにも書いてあるけど、以下の操作でfetch APIによるHTTPリクエストを中断できる。

  1. fetch() の第2引数のオブジェクトの signal フィールドに AbortController.signal を渡す
  2. AbortController.abort() を呼ぶ

HTTPリクエストが中断されると、 fetch() が返すPromiseはrejectされる。

ユーザー操作でリクエストを中断する

MDNのサンプルコードデモを参照。

タイムアウトさせる

window.setTimeout() のコールバック関数内で AbortController.abort() を呼ぶとできる。 タイムアウトを過ぎなかったときのために window.clearTimeout() しているけど、いらないかもしれない。

const invokeAPI = async (url) => {
  const controller = new AbortController();
  const timer = window.setTimeout(() => {
    controller.abort();
  }, 1000);
  const response = await fetch(url, {
    signal: controller.signal,
  });
  window.clearTimeout(timer);
  return resp;
};

Promise.race() でタイムアウトさせる場合との違い

Promise.race()fetch() とタイムアウトさせるPromiseを並走させてもタイムアウトは実現できそうに見える。

const timeout = (msec) => {
  return new Promise((_, reject) => {
    window.setTimeout(() => reject('timeout'), msec);
  });
};

await Promise.race([fetch(...), timeout(1000)]);

たしかにタイムアウトできているように見えるけど、AbortControllerを使った場合と違ってHTTPリクエスト自体はキャンセルされない。

一方axiosでは

cancel tokenという仕組みでHTTPリクエストをキャンセルできる。また、リクエストに対してタイムアウトを設定できる

cancel tokenはcancelable PromiseというECMAScriptのproposalに基づいて実装されたらしいけど、このproposalは取り下げられた。Promiseをキャンセルしたいという欲求がありそう。

健全なる精神は、健全なる住環境に宿る

引っ越した当初から自宅に洗濯機が置いてあったのだけれど、脱水の効きが悪すぎて、洗濯物がびちょびちょな状態で出てきていた。マンションに共用のコインランドリーがあったので、自宅の洗濯機は物置代わりにしてコインランドリーを使っていたのだけれど、洗濯するために小銭を用意する必要があって面倒だった。

脱水の効きが悪いのは古い洗濯機だからか、買い替える必要があるのか……と思いながらも脱水が効きにくい原因と対策についてインターネットで調べていたら、どうやら洗濯漕クリーナーを使うとマシになる場合があるらしいと知った。こういうときは一番効きそうなやつだろう、と塩素系の洗濯漕クリーナーを買って使った。

めちゃくちゃ白い塊が浮かんでくるし、効いていてほしいな~と思いながら実際に洗濯してみると、脱水の効きが良くなっているのを実感できた。コインランドリーを使った場合と同じくらいには脱水されている。これなら買い替える必要はまだなさそうだと、ヨドバシカメラの洗濯機の商品ページを全部閉じた。

大学に進学した頃からずっと同じ部屋に住んでいて、家賃も安く部屋の質も相応という感じだった。日々の暮らしはできているけどさすがに狭いしもっといい部屋に引っ越したい、ドラム式洗濯機が置ける部屋だといいかも、しかし家賃がめっちゃ上がる……と思っていたけど、引っ越ししたさがちょっと薄れた。洗濯のために小銭をかき集めるとか、自販機でジュースを買うみたいなことを考える必要がなくなったのが大きいと思う。日々の暮らしの思考コストを下げられれば、そのぶんだけ本質的な課題に集中できると思う。しかし良い部屋への願望は尽きないであろう。

突然ですがここでグッバイ宣言を聞いてください。一日の中に、このポーズを取る時間があってもいいと思う。

www.youtube.com