検索を"設定"で組む — SQLテンプレート+動的WHERE+一時表の設計
検索クエリを外部ファイル化し、条件で動的に組み立て、結果を一時表に置いてページングする。柔軟で速い検索基盤。
WHYなぜ検索を"設定"にするのか
通販基幹では、受注・会員・商品といった主要ドメインごとに一覧検索が無数に存在する。これらをアプリのコードに直接埋め込むと、検索条件が一つ増えるたびにビルドとデプロイが必要になり、レビューの粒度も粗くなる。そこで私たちは検索の中核であるSQLを外部ファイル(テンプレート)として切り出し、"設定"として管理する方針を採る。
- SQLをテンプレート化し、条件に応じて動的に組み立ててから実行する。
- 実行結果は一時表(または結果ストア)に格納し、検索IDで参照してページング取得する。
- SQLがバージョン管理・レビュー対象になるため、追加や修正の見通しが良い。
FLOW組み立てから取得までの流れ
検索リクエストは、テンプレートの読み込み、条件による置換、実行、結果の一時表への格納、そしてページング取得という一連のパイプラインを通る。一度実行した結果を一時表に固定することで、ページ送りのたびに重い本クエリを再実行せずに済む。読み取り最適化DB(CQRSの読み側)と組み合わせれば、集計や結合を含む検索でも安定した応答が得られる。
DYNAMIC動的WHEREの組み立て
テンプレートには {@param} のようなプレースホルダを埋め込む。置換時には値の型に応じて、数値はそのまま、文字列はクォート付き、複数値は IN 展開、未指定の任意条件はブロックごと削除、といった具合に組み立てる。JSON項目の抽出条件なども、DBの関数式に展開して同じ枠組みで扱える。任意条件をON/OFFできるため、WHERE句は利用者の指定に応じて伸縮する。
- 数値 / 文字列 / 日付 / IN展開 / NULL条件など、型別の展開規則を持つ。
- 任意条件は指定がなければ丸ごと除去し、無駄な述語を残さない。
- SQLを"設定"として扱えるため、AIによるクエリ生成・補助とも相性が良い。
CODEテンプレと安全な置換
テンプレートは静的な骨格と、束縛対象のプレースホルダで構成する。動的部分は「構造の生成」と「値の束縛」を厳密に分離する。値そのものを文字列連結でSQLに埋め込んではならない。
SELECT id, status, created
FROM order_read
WHERE 1=1
{@cond_status} -- AND status = :status
{@cond_kind} -- AND kind IN (:kind_list)
{@cond_from} -- AND created >= :from
ORDER BY created DESC
frag = []; binds = {}
# 任意条件はキーがある時だけ追加。値は必ず束縛で渡す
if has(input, "status"):
frag += "AND status = :status"
binds["status"] = as_str(input.status) # 型検証
if has(input, "kind"):
ph = expand_in(input.kind, binds, "kind") # IN展開も束縛
frag += "AND kind IN (" & ph & ")"
# カラム名/並び順はホワイトリスト照合のみ許可
col = whitelist(input.sort, allowed = ["created", "id"])
sql = render(template, frag) # 構造だけ差し込み
run(sql, binds) # 値は束縛で
TRADEOFFトレードオフと後始末
この方式の最大のリスクは、文字列置換がそのままSQLインジェクションの温床になり得ることだ。動的に組み立てるのは「SQLの構造」だけに限り、利用者が与える「値」はプレースホルダ束縛で必ず分離する。テンプレート側で扱わざるを得ないカラム名・並び順・演算子は、ホワイトリスト照合を通したものだけを許可する。入力は型検証を経てから束縛する。
- 値は文字列連結せず束縛で渡す。構造の生成と値の束縛を分ける。
- カラム名・ソート・IN展開の要素数などは、必ずホワイトリストや上限で制約する。
- テンプレの氾濫を防ぐため命名と一覧管理を徹底し、一時表は検索IDに紐づけて確実に掃除する。掃除漏れはストレージ肥大を招く。
検索SQLを外部テンプレート化し、条件で構造だけを動的に組み立て、値はプレースホルダ束縛で分離する。結果は一時表に固定してページング取得する。SQLを"設定"としてレビュー可能にしつつ、インジェクション対策(束縛・型検証・ホワイトリスト)と一時表の後始末を設計の前提に置くことが要点だ。