概要
BigQuery の JavaScript UDF は活用できていますか?
外部の package を import するようなコードはバンドルして GCS に置く必要がありますが、これって結構面倒ですよね。
Bun を使ったらシンプルかも?と思って試したので備忘録を残します。最終的なコードはこちら。
Bun の導入
ちまたの記事では webpack が使われている印象ですが、 Bun はそれも含めて代替するような JavaScript / TypeScript 向けの all-in-one なツールキットです。
とりあえずインストールしましょう。
他のインストール方法も確認したい方はこちら。
curl -fsSL https://bun.sh/install | bash # for macOS, Linux, and WSL
UDF を書く前に以下のように空の package.json を作っておきましょう。
普通は bun init
コマンドを使いますが、今回は設定なしでどこまで動くか試してみます。
echo '{}' > package.json
UDF を書く
今回は YAML を JSON に変換する UDF を定義してみましょう。
YAML の解析を自力で実装するのは難しいので js-yaml をインストールします。
bun add js-yaml
このとき bun.lockb というファイルが生成されます。これは npm でいう package-lock.json です。ただし bun.lockb はバイナリファイルなので、 Git で管理するならこの辺りを一読するべきです。
さて YAML を JSON に変換する処理はこんな感じでしょうか。
末尾の console.log()
は不要な気もしますが、これがないと全体が dead code と判断され後段で tree shaking されてしまうんですよね。賢すぎる。
import * as yaml from "js-yaml";
const yml2json = {
convert: (y) => {
return JSON.stringify(yaml.load(y));
},
};
console.log(yml2json);
GCS に配置
先ほど書いたコードをバンドルしてから GCS に配置します。
次のコマンドを実行すると ./out/yml2json.js が生成されます。これには js-yaml のコードもバンドルされています。超簡単!
bun build ./yml2json.js --outdir ./out
後は GCS に置くだけです。 BUCKET_NAME は各々書き換えてもらうとして、こんな感じ。
gsutil cp out/* gs://${BUCKET_NAME}
そうすると SQL から以下のように実行できます。
create temp function yml2json(yml string)
returns string
language js
options (library = ['gs://BUCKET_NAME/yml2json.js'])
as 'return yml2json.convert(yml)'
;
select yml2json('value: string value'); -- '{"value":"string value"}'
補足
bun build
について補足します。これは BigQuery に特化した JavaScript ファイルを作成するコマンドではありません。デフォルトではブラウザで実行するためのファイルを生成します(詳しくはこの辺りを読んでください)。
BigQuery はブラウザ独自の API も Node / Bun 独自の API も実行できないですが、とりあえずブラウザ向けにバンドルしておけば動くことが多そうです。
例えば以下のコードは明らかに Node 独自の API を使っていますが、先ほどと同様に(デフォルトのブラウザ向けに)バンドルして GCS に配置すると正常に動きます。
import { default as Buffer } from "node:buffer";
const base64 = {
fromString: (s) => {
const buf = Buffer.from(s, "utf8");
return buf.toString("base64");
},
};
console.log(base64);
declare input string default 'Hello, 世界';
create temp function string2base64(s string)
returns string
language js
options (library = ['gs://BUCKET_NAME/base64.js'])
as 'return base64.fromString(s)'
;
-- いずれも "SGVsbG8sIOS4lueVjA=="
select
string2base64(input),
to_base64(cast(input as bytes)), -- 一般的な関数で答え合わせ
;
どうもバンドルの過程で polyfill されて、独自の API が都合よく消えるようですね。
Bun automatically polyfills certain Node.js APIs when imported in the browser such as node:crypto, similarly to Webpack 4's behavior.
https://bun.sh/blog/bun-bundler#targets
最後に
まだ様々なケースを試したわけではないですが、わずかな設定でバンドルできるのはすごいですね。よければお試しあれ。