JavaScript Tips | 文字列ユーティリティ:業務用 - SQL エスケープ

JavaScript JavaScript
スポンサーリンク

何をしたいユーティリティか:「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'

つまり、

  1. 文字列中の ''' に置き換える
  2. 全体を ' で囲む

という二段構えです。


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";
}
JavaScript

SQL では「値がない」を表すのは NULL です。
ここでは、nullundefined が来たら 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 文字列整形”になっていきます。

タイトルとURLをコピーしました