PDO::prepare()に不正なSQLを渡したときの挙動
このページは、PHPの PDO::prepare() に不正なSQLを渡したときの挙動について検証してまとめたページです。
注意
- 下記のバージョンで検証したものです。別のバージョンや組み合わせでは違う動作になるかもしれません。
- PHP 8.1 + MySQL 8.0.31
- PHP 8.1 + MariaDB 10.10.2
- PHP 8.1 + PostgreSQL 15.1
- PHP 8.1 + SQLite 3.34.1
検証結果
環境 | 挙動 (結果) |
---|---|
MySQL (PDO::ATTR_EMULATE_PREPARES => true) | エラーにならない |
〃 (PDO::ATTR_EMULATE_PREPARES => false) | 構文エラー |
MariaDB (PDO::ATTR_EMULATE_PREPARES => true) | エラーにならない |
〃 (PDO::ATTR_EMULATE_PREPARES => false) | 構文エラー |
PostgreSQL | エラーにならない |
SQLite | 構文エラー |
- エラーになっていない環境では、
prepare()
の時点ではまだ各DB側にSQLが渡っていません。 prepare()
でエラーにならない場合でも、execute()
ではエラーになります。(各DB側にSQLが渡るため)- PostgreSQL, SQLiteでは
PDO::ATTR_EMULATE_PREPARES
は利用できません。
MySQL, MariaDB (PDO::ATTR_EMULATE_PREPARES => true)
<?php
$dsn = 'mysql:host=localhost;dbname=demo'; // DSN
$user = 'root'; // ユーザー
$pass = 'pass'; // パスワード
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => true // デフォルトでtrueのため指定しなくてもいい
]);
$stmt = $pdo->prepare('malformed sql'); // 成功
MySQL, MariaDB (PDO::ATTR_EMULATE_PREPARES => false)
<?php
$dsn = 'mysql:host=localhost;dbname=demo'; // DSN
$user = 'root'; // ユーザー
$pass = 'pass'; // パスワード
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false
]);
$stmt = $pdo->prepare('malformed sql'); // エラー
Fatal error: Uncaught PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'malformed sql' at line 1 in /var/www/html/index.php:10 Stack trace: #0 /var/www/html/index.php(10): PDO->prepare('malformed sql') #1 {main} thrown in /var/www/html/index.php on line 10
Fatal error: Uncaught PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'malformed sql' at line 1 in /var/www/html/index.php:10 Stack trace: #0 /var/www/html/index.php(10): PDO->prepare('malformed sql') #1 {main} thrown in /var/www/html/index.php on line 10
ソース
// prepare() → PHP_METHOD(PDO, prepare)[ext/pdo/pdo_dbh.c] → dbh->methods->preparer() → この関数
static bool mysql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options)
// 略
// エミュレートモードの場合は処理がスキップされる
if (H->emulate_prepare) {
goto end;
}
// 略
// 非エミュレートモードの場合はここでステートメントを準備する (不正なSQLだとここでエラーになる)
if (mysql_stmt_prepare(S->stmt, ZSTR_VAL(sql), ZSTR_LEN(sql))) {
PostgreSQL
<?php
$dsn = 'pgsql:host=localhost;dbname=demo'; // DSN
$user = 'postgres'; // ユーザー
$pass = 'postgres'; // パスワード
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->prepare('malformed sql'); // 成功
ソース
// prepare() → PHP_METHOD(PDO, prepare)[ext/pdo/pdo_dbh.c] → dbh->methods->preparer() → この関数
// PDO::prepare()の段階ではPQprepare()等は行われない
static bool pgsql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options)
{
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt));
int scrollable;
int ret;
zend_string *nsql = NULL;
int emulate = 0;
int execute_only = 0;
S->H = H;
stmt->driver_data = S;
stmt->methods = &pgsql_stmt_methods;
scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR,
PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL;
if (scrollable) {
if (S->cursor_name) {
efree(S->cursor_name);
}
spprintf(&S->cursor_name, 0, "pdo_crsr_%08x", ++H->stmt_counter);
emulate = 1;
} else if (driver_options) {
if (pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepares) == 1) {
emulate = 1;
}
if (pdo_attr_lval(driver_options, PDO_PGSQL_ATTR_DISABLE_PREPARES, H->disable_prepares) == 1) {
execute_only = 1;
}
} else {
emulate = H->disable_native_prepares || H->emulate_prepares;
execute_only = H->disable_prepares;
}
if (!emulate && PQprotocolVersion(H->server) <= 2) {
emulate = 1;
}
if (emulate) {
stmt->supports_placeholders = PDO_PLACEHOLDER_NONE;
} else {
stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED;
stmt->named_rewrite_template = "$%d";
}
ret = pdo_parse_params(stmt, sql, &nsql);
if (ret == -1) {
/* couldn't grok it */
strcpy(dbh->error_code, stmt->error_code);
return false;
} else if (ret == 1) {
/* query was re-written */
S->query = nsql;
} else {
S->query = zend_string_copy(sql);
}
if (!emulate && !execute_only) {
/* prepared query: set the query name and defer the
actual prepare until the first execute call */
spprintf(&S->stmt_name, 0, "pdo_stmt_%08x", ++H->stmt_counter);
}
return true;
}
SQLite
<?php
$dsn = 'sqlite:memory'; // DSN
$pdo = new PDO($dsn, '', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->prepare('malformed sql'); // エラー
Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 1 near "malformed": syntax error in /var/www/html/index.php:8 Stack trace: #0 /var/www/html/index.php(8): PDO->prepare('malformed sql') #1 {main} thrown in /var/www/html/index.php on line 8
ソース
// prepare() → PHP_METHOD(PDO, prepare)[ext/pdo/pdo_dbh.c] → dbh->methods->preparer() → この関数
static bool sqlite_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options)
{
// 略
// ステートメントを準備する (不正なSQLだとここでエラーになる)
i = sqlite3_prepare_v2(H->db, ZSTR_VAL(sql), ZSTR_LEN(sql), &S->stmt, &tail);
if (i == SQLITE_OK) {
return true;
}