このテックブログを「quine」で検索していただくとわかるんですが、エムスリーのエンジニアはわりとQuine好きです。Quine(クワイン)とは、文字列を打ち出すだけのプログラムであってその打ち出した文字列というのが自身のコードそのものになっているようなもののことです。
Perl, Python, Rubyなど、手続き型言語で書かれることの多いQuineなのですが、日頃業務でSQLを扱うことの多い私は思うわけです。SQLだって文字列を出力できる言語だぞ、Quine書けるんじゃと。エムスリーエンジニアリンググループUnit1(製薬プロモーション)/Unit9(治験臨床研究支援)エンジニアの三浦[記事一覧 ]です。日頃のお仕事からは離れてSQLでQuineを書いて遊んでみました。
基本方針
Quineの基本形は、どんな言語で書くのであっても「A "A"」の形になることと思います。「A」が文字列を打ち出すプログラムコード本体ですね。そして、Aを文字列データとして持ちます。打ち出すプログラムAは、データとして持っているAをもとにして「A "A"」という文字列を作り上げるようなものにすればいいわけです。
SQLもこの方針で行きましょう。Aはおそらく select なんちゃらかんちゃら になります。ターゲットにするSQL処理系としてはPostgreSQL, BigQuery, Athenaのどれでも動くようにということにします。
やっていく
とはいえいきなり完成形は目指さず、試行錯誤しながらいってみましょう。
select 'select' => select
はい、いきなり基本形の種ができました。「A "A"」ですね。いやしかしこのままだと先がありません。文字列データをまずは変数か定数に受けないと、「A」を「A "A"」に変換するみたいなことができません。
SQLで変数に受けるってどうするか。はい、サブクエリです。
select q.s from (select 'select q.s from (select ' s) q => select q.s from (select
できましたね。文字列カラムsを持つサブクエリqを作れば、q.sがその文字列「A」です。では「A」を「A "A"」の形にするためq.sを2個連結させてみましょう。
select q.s||q.s from (select 'select q.s||q.s from (select ' s) q => select q.s||q.s from (select select q.s||q.s from (select
連結はこうしてあっさりできるわけですが、さて、考え込んでしまいます。まずシングルクォーテーション(')が入りませんね。いや、入れればいいという話ではあるんですが、シングルクォーテーションを加えるという式を書いたなら、その式をシングルクォーテーションで区切られた文字列の中にも書かないといけないんです。これは面倒さが爆発する予感⋯⋯!
コード内にシングルクォーテーションを書かない
コード「A」の中にシングルクォーテーションが一文字も登場しなければわりと話は楽になります。エスケープすることとか考えなくて良くなります。ではどうするかというと、これはどの言語でもだいたい同じだと思うのですが文字コード指定で書けばよい。SQLなら chr(39) です。
select q.s||chr(39)||q.s||chr(39) from (select 'select q.s||chr(39)||q.s||chr(39) from (select ' s) q => select q.s||chr(39)||q.s||chr(39) from (select 'select q.s||chr(39)||q.s||chr(39) from (select '
だいぶ形になってきました。あとは最後に s) q をつけてあげられれば完成なのですが⋯⋯
最後に s) q をつけるのって、もしかしてこの s) q も文字列データとして持つ必要が出てきますか? うっ、急にこれまた面倒なことに。持つ文字列データは一個だけにしておきたいところです、簡単さのために。
プログラムの先頭側と末尾側をひとつの文字列データに含める
文字列データ「A」ですが、これを扱うプログラムはどうしてもこの文字列データの左側にだけ書くのではすみません。右側にも何かしら書く必要があります。
すると、最初に書いた基本形「A "A"」はちょっと不完全だったと言わざるを得ません。むしろこうです。「A "A _ B" B」プログラム A B は、挟み込んだ文字列データ「A _ B」を元に「A "A _ B" B」という文字列を生み出せばいいのです。
そうですreplace関数です。やってみましょう。アンダースコア(_)は chr(95) ですから、「A _ B」のアンダースコア部分にそれ自身をはめ込む式は
replace(q.s, chr(95), chr(39)||q.s||chr(39))
となります。というわけで、プログラムの基本骨格はこうなります。
select replace(q.s, chr(95), chr(39)||q.s||chr(39)) from (select _ s) q
この基本骨格ってのがつまり「A _ B」ですから、これを文字列データとしてプログラム本体と合わせれば、こうなります。
select replace(q.s, chr(95), chr(39)||q.s||chr(39)) from (select 'select replace(q.s, chr(95), chr(39)||q.s||chr(39)) from (select _ s) q' s) q => select replace(q.s, chr(95), chr(39)||q.s||chr(39)) from (select 'select replace(q.s, chr(95), chr(39)||q.s||chr(39)) from (select _ s) q' s) q
コードとまったく同じ出力が出ました! SQL Quineの完成です!
最後にもう一度ご鑑賞ください
select replace(q.s, chr(95), chr(39)||q.s||chr(39)) from (select 'select replace(q.s, chr(95), chr(39)||q.s||chr(39)) from (select _ s) q' s) q
美しうございますね。
We are hiring
圧倒的生産性を目指しつつも遊び心は忘れないのがエムスリーのエンジニアです。ご興味あったらこちらのページからどうぞ。応募を前提にしないカジュアル面談もやっています。