何をしたいユーティリティか:「SQL エスケープ」
ここでの「SQL エスケープ」は、文字列を SQL の文字列リテラルとして安全な形に整える処理です。
ただし、最初にめちゃくちゃ大事なことを言います。
本番の業務システムで「SQL インジェクション対策」としてやるべきなのは、
「文字列連結で SQL を組み立てる」のではなく「プレースホルダ付きのパラメータバインド」を使うことです。
SELECT * FROM users WHERE name = ?
の ? に値を渡す、というやり方ですね。
ここで作る「SQL エスケープ」は、
- ログ用に「SQL っぽい文字列」を出したいとき
- どうしてもレガシーな「生 SQL 文字列」を組まざるを得ないとき
などに使う「最低限の安全策」として捉えてください。
SQL 文字列リテラルの基本ルール
シングルクォートで囲む
SQL では、文字列リテラルは基本的に シングルクォート ' で囲みます。
'山田太郎'
'abc'
'2024-01-01'
JavaScript でこれを作るなら、例えば:
const name = "山田太郎";
const sql = "SELECT * FROM users WHERE name = '" + name + "'";
JavaScript…と書きたくなりますが、このままだと危険です。
中に ' があると壊れる
もし name が "O'Hara" だったらどうなるか。
SELECT * FROM users WHERE name = 'O'Hara'
ここで文字列が途中で終わってしまい、
残りの Hara' が「変な SQL」として解釈されます。
これが、SQL エスケープが必要になる一番分かりやすい理由です。
SQL でのエスケープルール(シングルクォート)
SQL の世界では、文字列の中のシングルクォート ' は、''(2 つ連続)に置き換えることで表現します。
元の文字列:O'Hara
SQL リテラル:'O''Hara'
つまり、
- 文字列中の
'を''に置き換える - 全体を
'で囲む
という二段構えです。
JavaScript での SQL エスケープ関数
文字列用の sqlEscapeString
まずは、「文字列を SQL の文字列リテラルにする」関数を作ります。
function sqlEscapeString(value) {
if (value == null) {
return "NULL";
}
const str = String(value);
const escaped = str.replace(/'/g, "''");
return "'" + escaped + "'";
}
JavaScript重要なポイントをかみ砕いて説明する
null / undefined は「NULL」にする
if (value == null) {
return "NULL";
}
JavaScriptSQL では「値がない」を表すのは NULL です。
ここでは、null や undefined が来たら SQL の NULL を返すようにしています。
INSERT INTO users (name) VALUES (NULL)
のような形になります。
まずは文字列に変換する
const str = String(value);
JavaScript数値や日付でも、文字列として扱いたい場合はここで文字列化します。
(数値を数値のまま使いたい場合は、後で別の関数にします)
' を '' に置き換える
const escaped = str.replace(/'/g, "''");
JavaScriptここが SQL エスケープの核心です。
"O'Hara" → "O''Hara""It's fine" → "It''s fine"
というように、シングルクォートを 2 つに増やします。
全体を ' で囲む
return "'" + escaped + "'";
JavaScriptこれで、SQL の文字列リテラルとして安全な形になります。
実際の動きを例で確認する
sqlEscapeString("山田太郎"); // "'山田太郎'"
sqlEscapeString("O'Hara"); // "'O''Hara'"
sqlEscapeString("It's OK"); // "'It''s OK'"
sqlEscapeString(null); // "NULL"
JavaScriptこれを SQL に埋め込むと、例えば:
const name = "O'Hara";
const sql =
"SELECT * FROM users WHERE name = " + sqlEscapeString(name);
// SELECT * FROM users WHERE name = 'O''Hara'
JavaScriptという形になります。
数値や日付をどう扱うか
数値は基本「そのまま」
数値は、SQL ではクォートなしで書くのが普通です。
age = 30
amount = 1234.5
なので、数値用には別の関数を用意しておくと分かりやすいです。
function sqlEscapeNumber(value) {
if (value == null || value === "") {
return "NULL";
}
const num = Number(value);
if (!Number.isFinite(num)) {
throw new Error("数値として不正な値です: " + value);
}
return String(num);
}
JavaScript使い方はこんな感じです。
sqlEscapeNumber(30); // "30"
sqlEscapeNumber("1234.5"); // "1234.5"
sqlEscapeNumber(null); // "NULL"
JavaScript日付は「文字列として扱う」ことが多い
日付は DB ごとに型や書き方が違うので、
ここでは「YYYY-MM-DD などの文字列にして、sqlEscapeString に渡す」方針が無難です。
function sqlEscapeDate(date) {
if (!date) {
return "NULL";
}
const d = date instanceof Date ? date : new Date(date);
if (Number.isNaN(d.getTime())) {
throw new Error("日付として不正な値です: " + date);
}
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
const iso = `${y}-${m}-${day}`;
return sqlEscapeString(iso);
}
JavaScriptこれで、
sqlEscapeDate(new Date("2024-01-05"));
// "'2024-01-05'"
JavaScriptのような形になります。
実際に SQL 文字列を組み立ててみる
INSERT 文の例
const name = "O'Hara";
const age = 30;
const sql =
"INSERT INTO users (name, age) VALUES (" +
sqlEscapeString(name) + ", " +
sqlEscapeNumber(age) +
")";
JavaScript生成される SQL はこうなります。
INSERT INTO users (name, age) VALUES ('O''Hara', 30)
UPDATE 文の例
const id = 123;
const name = "山田太郎";
const birthday = "1990-04-01";
const sql =
"UPDATE users SET " +
"name = " + sqlEscapeString(name) + ", " +
"birthday = " + sqlEscapeDate(birthday) +
" WHERE id = " + sqlEscapeNumber(id);
JavaScriptここが一番大事:「これは“最終手段”だと理解する」
もう一度、強めに言っておきます。
本番の業務システムで、ユーザー入力を含む SQL を文字列連結で組み立てるのは、基本的に NG です。
やるべきなのは、次のような「プレースホルダ+パラメータ」の形です。
// 例:Node.js の DB ライブラリなど
const sql = "SELECT * FROM users WHERE name = ? AND age >= ?";
const params = [name, age];
db.query(sql, params);
JavaScriptこの形なら、ライブラリ側が適切にエスケープしてくれるので、
自分で sqlEscapeString を呼ぶ必要はありません。
ここで作った sqlEscapeString / sqlEscapeNumber / sqlEscapeDate は、
- ログに「実行された SQL のイメージ」を残したいとき
- どうしてもレガシーな「生 SQL 文字列」を吐くしかないとき
などの「最後の防波堤」として使うもの、と理解しておいてください。
少し手を動かして感覚をつかむ
コンソールで、次のようなコードを実際に打ってみてください。
sqlEscapeString("山田太郎");
sqlEscapeString("O'Hara");
sqlEscapeString("It's OK");
sqlEscapeNumber(1234.5);
sqlEscapeNumber("999");
sqlEscapeDate("2024-01-05");
const name = "O'Hara";
const age = 30;
const sql =
"SELECT * FROM users WHERE name = " +
sqlEscapeString(name) +
" AND age >= " +
sqlEscapeNumber(age);
sql;
JavaScript生成された SQL を眺めて、
'が''になっていること- 数値がクォートなしで出ていること
NULLの扱いがどうなっているか
を確認してみてください。
そのうえで、自分のプロジェクトにはまず
- 「DB アクセスは必ずパラメータバインドを使う」
- 「どうしても文字列連結が必要なところだけ、
sqlEscapeXXXを使う」
という二段構えのルールを作ってみてください。
それができたら、あなたの「SQL エスケープ」は、
場当たり的な置換ではなく、
意図と安全性を意識した“業務レベルの SQL 文字列整形”になっていきます。
