Cloudflare D1には、SQL文において使用できる?
の数が100までという制限がある。
このページにある、Maximum bound parameters per queryってやつがそれ。
workerd
のコードでも、ランタイムの制約に指定してあるのがわかる。
sqlite3_limit(db, SQLITE_LIMIT_VARIABLE_NUMBER, 100); https://github.com/cloudflare/workerd/blob/fae4178326fb9709dea737f51337cda1d1551f25/src/workerd/util/sqlite.c%2B%2B#L731
問題のケース
思ってた以上によく引っかかる。
たとえばカラムが10あるテーブルがあったとすると、そのテーブルに11件をまとめてINSERTするとすぐにこのエラーになる。(もちろん発行されるSQLによるけど)
ORMなんかを使ってると、複数のレコードをINSERTするSQLはこんな風に発行されがち。
INSERT INTO items VALUES (?, ?, ?), (?, ?, ?), ..., (?, ?, ?);
で、この1文で登場する?
が100を超えるとエラーになっちゃう。
?
自体は、INSERT文のVALUES
だけでなく、WHERE
句とかにも出番があるけど、まあ数が問題になるのはだいたいINSERTな気がする。
どうする
D1の制約であるなら、どうしようもない。
クエリを調整して、?
の数を100以下にするしかない。
愚直に、複数のレコードを一括挿入するINSERT文をやめて、複数回のINSERT文にしてしまう。
INSERT INTO items VALUES (?, ?, ?);
INSERT INTO items VALUES (?, ?, ?);
...
INSERT INTO items VALUES (?, ?, ?);
たとえばDrizzle ORMを使ってるとすると、こんな具合に書き直す。
const db = drizzle(env.DB);
// Before
await db.insert(items).values(itemsToInsert);
// After
await db.batch([
...itemsToInsert.map((item) => db.insert(items).values(item));
]);
元の挙動とあわせるために、batch()
で一括実行するようにしておく。batch()
内で使われる?
の合計数は、別に100を超えても問題ない。
他にも、そもそもプリペアドステートメントを使わず、SQL文字列を自分で組み上げる方法もあるかも。
この場合はbatch()
を使わなくて良いので1クエリのままいけるけど、なんだかなあ・・・。
(100カラムある(そしてDEFAULT
指定なし)テーブルがあったとすると、どうしたら良いんやろうね?)