こんにちは、トレタ Advent Calendar 2017の8日目記事です。

トレタでは膨大なデータを分析する際に Google BigQuery を使ってますが、今年はデータサイエンティストチームも立ち上がってより大規模な分析が可能になりました。

さてさて、お硬い話は置いといて。クリスマス前なので、ここでは何か面白おかしい SQL 芸でもしましょう。BigQuery は JavaScript ベースのユーザー定義の関数(UDF)をサポートしています、つまり必要であれば好き勝手に UDF で BigQuery の関数を拡張することができます。この際 UDF を使って別の言語を実装して BigQuery のなかで呼び出しましょう!!

イメージ

イメージとしては SQL でこんな呼び出しをできたら面白いと思います。

select mal('(fn* [a b c] (reduce + 0 [a b c]))', [1.1, 2.2, 3.3])

内部はコードを read 関数で AST に変換して、 eval 関数で環境と共に評価します。

Screen Shot 2017-12-07 at 17.17.11.png

Make a Lisp (mal)

今回は Make a Lisp (mal) という Lisp 系の処理系をもとに実装してみようというプロジェクトです。mal の特徴といえば説明が詳しく、多くの言語(現時点で71言語)での実装例を参考にすることが可能です。

内部実装はほとんど mal の JavaScript 実装例を持ってきましたが、Node.js や Browser の環境と異なるため、モジュール周りは変更して、console オヴジェクトがないため prn 系の関数も除外してます。

Screen Shot 2017-12-07 at 17.35.07.png
Source: https://github.com/kanaka/mal/blob/master/process/guide.md#stepA

完成したコードはこちらに公開しております。
https://github.com/hden/mal

UDF 周り

BigQuery の UDF は基本こんな構造になってますが

create temporary function multiplyInputs(x int64, y int64)
returns int64
language js as """
  return x * y
""";

OPTIONS セクションを付けることで GCS 上の外部コード、またはライブラリを読み込むことが可能です。例えば自分の gs://hden/mal/ バケット内にコードを置くと

#standardsql
create temporary function mal (str string, args array<float64>)
  returns float64
  language js as 'return mal.apply(str, args)'
  options (
    library=['gs://hden/mal/bundle.js']
  )
;

こういう風に読み込むことが可能です。

データ型

BigQuery の int 型は基本 int64 ですが、 JavaScript に int64 はないので文字列として渡されます、parseInt() などの関数である程度丸め込むことが可能ですが、注意が必要です。

データ型のマッピングはこちらです。
https://cloud.google.com/bigquery/sql-reference/user-defined-functions?hl=ja#sql-type-encodings-in-javascript

最後に

楽しんでいただけたでしょうか?

#standardsql
create temporary function mal (str string, args array<float64>)
  returns float64
  language js as 'return mal.apply(str, args)'
  options (
    library=['gs://hden/mal/bundle.js']
  )
;
select mal('(fn* [a b c] (reduce + 0 [a b c]))', [1.1, 2.2, 3.3])

では皆さん、良いクリスマスを。