@kyanny's blog

私は天才ではありません。ただ、人より長く一つの事と付き合っていただけです - アインシュタイン

TIL: synonym / antonym

synonym が「同意語、類義語」

antonym が「反意語」

synonym の antonym が antonym という関係。


余談: 「反意語」と「対義語」の違い

反意語(はんいご)の類語・言い換え - 類語辞書 - goo辞書

1四語とも、「善」と「悪」、「大きい」と「小さい」、「兄」と「弟」などのように、同一言語において反対の意味をもつ語をさす。

2「善」と「悪」、「大きい」と「小さい」のように、全く反対の概念を表わす語どうしを「反意語」「反対語」といい、「兄」と「弟」、「右」と「左」のように、組になる概念を表わす語どうしを「対義語」といって区別することもある。

TIL: dry run / live run

dry run の逆をなんと呼んだら良いのだろうと思って調べてみたら、 live run という言い回しを見つけた。

english.stackexchange.com

予行演習としての dry run が特別な場合なのであって、わざわざ --dry-run オプションをつけてコマンドを実行したりするのだから、予行演習ではない場合の呼び名など無くて構わないのでは?という向きもあると思うが、ドキュメントに↓のように書くときに悩んだ。

## dry run
ansible-playbook deploy.yml -i inventory_file --check

## live run
ansible-playbook deploy.yml -i inventory_file

最初は↓のように書いていたが、 ansible-playbook コマンドが # 以下をインラインコメントと解釈してくれずエラーになり、コピペビリティが下がったので、↑のような書き方をしたかった。

ansible-playbook deploy.yml -i inventory_file --check # dry run
ansible-playbook deploy.yml -i inventory_file

SQLite: 複合主キーを使う

データベース操作ライブラリ等を使って CREATE TABLE 文を自動生成してる場合など、まれに手作業で調整する場合。

テーブル作成後に ALTER TABLE で変更できないので、 DROP TABLE してから CREATE TABLE する。

BEGIN;
DROP TABLE "polls_choice";
CREATE TABLE "polls_choice" ("question_id" integer NOT NULL, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, PRIMARY KEY ("question_id", "votes"));
COMMIT;

stackoverflow.com

検索キーワード: multi column primary key, compound primary key, composed primary key

Django: save() に必ず INSERT させるためには force_insert=True を使う

ナチュラルキー + 複合主キーを使っているテーブル(のモデル)に対してループのなかで save() したら一行しか INSERT されなくてハマった。

  • Django は primary key の attribute 値が None ではないオブジェクトに対して save() を呼ぶと、まず UPDATE を実行する。 UPDATE が一行も変更しなかった場合は続けて INSERT を実行する。
  • サロゲートキーを使っている場合、 primary key の attribute 値が重複していたら DB 制約により INSERT できないので、この種の問題は起きない。
  • テーブルに複合主キーが設定されている場合も、 Django Model の定義上は複合主キーを宣言できないので、 Django アプリを動かすためには一つの attribute に対して primary_key=True を宣言することになる。 ref
  • この状態で、親子関係の子にあたるモデルのインスタンスを複数作成して同じ親を参照させて save() すると、一行だけ INSERT されて残りの save() は作成した行を UPDATE する、という挙動が発生する。

検証用に作ったサンプルプロジェクト

github.com

要は、ここの違いが重要。

In [8]: for i in range(3):
   ...:     c = Choice(question=q, choice_text='hi', votes=i)
   ...:     c.save()
   ...:
   ...:
(0.000) UPDATE "polls_choice" SET "choice_text" = 'hi', "votes" = 0 WHERE "polls_choice"."question_id" = 3; args=('hi', 0, 3)
(0.001) INSERT INTO "polls_choice" ("question_id", "choice_text", "votes") SELECT 3, 'hi', 0; args=(3, 'hi', 0)
(0.001) UPDATE "polls_choice" SET "choice_text" = 'hi', "votes" = 1 WHERE "polls_choice"."question_id" = 3; args=('hi', 1, 3)
(0.002) UPDATE "polls_choice" SET "choice_text" = 'hi', "votes" = 2 WHERE "polls_choice"."question_id" = 3; args=('hi', 2, 3)
In [6]: for i in range(3):
   ...:     c = Choice(question=q, choice_text='hi', votes=i)
   ...:     c.save(force_insert=True)
   ...:
(0.003) INSERT INTO "polls_choice" ("question_id", "choice_text", "votes") SELECT 7, 'hi', 0; args=(7, 'hi', 0)
(0.002) INSERT INTO "polls_choice" ("question_id", "choice_text", "votes") SELECT 7, 'hi', 1; args=(7, 'hi', 1)
(0.001) INSERT INTO "polls_choice" ("question_id", "choice_text", "votes") SELECT 7, 'hi', 2; args=(7, 'hi', 2)

当然、 save() ではなく create() を使うことでも解決できる。というか、 create() は↑のショートハンドに過ぎないらしい。

QuerySet API reference | Django documentation | Django

結論: ナチュラルキーなんて使うもんじゃない。この手のフルスタック Web Application Framework とか O/R Mapper はサロゲートキーを暗黙に期待するものが多いだろうから、大人しく慣習に合わせておいた方が無難。(まぁ、 O/R Mapper に合わせてテーブル設計するのは本末転倒ではあるが)

Google カレンダーの「その他の通知」はオフにしないほうが良い

例えば「新しい予定」の通知を「なし」にすると、誰かが自分の Google アカウントを予定に招待しても、通知されず、どこにも表示されないので、招待されたことに気づかない。

しかし、招待した側は普通に予定を作成できるようで、招待者から見ると invitation は送れていて、 RSVP を怠っているだけの状態に見えるようだ。

送った・届いてないのやりとりが発生するならまだしも(無駄だが)、そのやりとりすら起こらない場合、非常に面倒なことになりかねないので、オフにしない方が良い。

not receiving calendar invites - Calendar Community

f:id:a666666:20201111215504p:plain

記憶を辿ってみると、知らないアカウントから突然予定に招待された通知を受け取ったことがあり、新手のスパムっぽかったので、防止のために通知をオフにした、ような気がする。