PDO支持多个查询(PDO_MYSQL,PDO_MYSQLND)
我知道PDO不支持在一个语句中执行多个查询。 我一直在Googleing,发现有几篇文章谈论PDO_MYSQL和PDO_MYSQLND。
PDO_MySQL比其他传统的MySQL应用程序更危险。 传统的MySQL只允许一个SQL查询。 在PDO_MySQL中没有这样的限制,但是你有被多次查询注入的风险。
来自: 使用PDO和Zend Framework防止SQL注入 (2010年6月,由Julian提供)
看起来像PDO_MYSQL和PDO_MYSQLND确实提供了对多个查询的支持,但我无法find更多关于它们的信息。 这些项目是否中断? 现在有什么办法可以使用PDO运行多个查询。
据我所知, PDO_MYSQLND
取代了PHP 5.3中的PDO_MYSQL
。 令人困惑的是,这个名字仍然是PDO_MYSQL
。 所以现在ND是MySQL + PDO的默认驱动程序。
总体而言,要一次执行多个查询,您需要:
- PHP 5.3+
- mysqlnd
- 仿真准备好的语句。 确保
PDO::ATTR_EMULATE_PREPARES
设置为1
(默认)。 或者,您可以避免使用$pdo->exec
准备语句,并直接使用$pdo->exec
。
使用exec
$db = new PDO("mysql:host=localhost;dbname=test", 'root', ''); // works regardless of statements emulation $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0); $sql = " DELETE FROM car; INSERT INTO car(name, type) VALUES ('car1', 'coupe'); INSERT INTO car(name, type) VALUES ('car2', 'coupe'); "; try { $db->exec($sql); } catch (PDOException $e) { echo $e->getMessage(); die(); }
使用语句
$db = new PDO("mysql:host=localhost;dbname=test", 'root', ''); // works not with the following set to 0. You can comment this line as 1 is default $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1); $sql = " DELETE FROM car; INSERT INTO car(name, type) VALUES ('car1', 'coupe'); INSERT INTO car(name, type) VALUES ('car2', 'coupe'); "; try { $stmt = $db->prepare($sql); $stmt->execute(); } catch (PDOException $e) { echo $e->getMessage(); die(); }
一张纸条:
在使用模拟的准备好的语句时,确保你已经在DSN中设置了正确的编码(反映实际的数据编码)(从5.3.6开始可用)。 否则,如果使用一些奇数编码,则可能会有一点SQL注入的可能性 。
经过半天的摆弄之后,发现PDO有一个bug …
–
//This would run as expected: $pdo->exec("valid-stmt1; valid-stmt2;");
–
//This would error out, as expected: $pdo->exec("non-sense; valid-stmt1;");
–
//Here is the bug: $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");
它会执行"valid-stmt1;"
,停止"non-sense;"
永远不会抛出一个错误。 不会运行"valid-stmt3;"
,回到真实的谎言,一切运行良好。
我希望它会在"non-sense;"
上错误的出现 但事实并非如此。
这里是我发现这个信息的地方: 无效的PDO查询不会返回错误
这是错误: https : //bugs.php.net/bug.php?id = 61613
所以,我试着用mysqli来做这件事,并没有真正find任何可靠的答案,所以我想我只是把它留在这里为那些谁想要使用它..
try{ // db connection $mysqli = new mysqli("host", "user" , "password", "database"); if($mysqli->connect_errno){ throw new Exception("Connection Failed: [".$mysqli->connect_errno. "] : ".$mysqli->connect_error ); exit(); } // read file. // This file has multiple sql statements. $file_sql = file_get_contents("filename.sql"); if($file_sql == "null" || empty($file_sql) || strlen($file_sql) <= 0){ throw new Exception("File is empty. I wont run it.."); } //run the sql file contents through the mysqli's multi_query function. // here is where it gets complicated... // if the first query has errors, here is where you get it. $sqlFileResult = $mysqli->multi_query($file_sql); // this returns false only if there are errros on first sql statement, it doesn't care about the rest of the sql statements. $sqlCount = 1; if( $sqlFileResult == false ){ throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], [".$mysqli->errno."]: '".$mysqli->error."' }"); } // so handle the errors on the subsequent statements like this. // while I have more results. This will start from the second sql statement. The first statement errors are thrown above on the $mysqli->multi_query("SQL"); line while($mysqli->more_results()){ $sqlCount++; // load the next result set into mysqli's active buffer. if this fails the $mysqli->error, $mysqli->errno will have appropriate error info. if($mysqli->next_result() == false){ throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], Error No: [".$mysqli->errno."]: '".$mysqli->error."' }"); } } } catch(Exception $e){ echo $e->getMessage(). " <pre>".$e->getTraceAsString()."</pre>"; }
一个快速和肮脏的方法:
function exec_sql_from_file($path, PDO $pdo) { if (! preg_match_all("/('(\\\\.|.)*?'|[^;])+/s", file_get_contents($path), $m)) return; foreach ($m[0] as $sql) { if (strlen(trim($sql))) $pdo->exec($sql); } }
在合理的SQL语句端点拆分。 没有错误检查,没有注射保护。 在使用之前了解您的使用。 就我个人而言,我使用它来进行原始迁移文件的集成testing。
试了下面的代码
$db = new PDO("mysql:host={$dbhost};dbname={$dbname};charset=utf8", $dbuser, $dbpass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
然后
try { $db->query('SET NAMES gbk'); $stmt = $db->prepare('SELECT * FROM 2_1_paidused WHERE NumberRenamed = ? LIMIT 1'); $stmt->execute(array("\xbf\x27 OR 1=1 /*")); } catch (PDOException $e){ echo "DataBase Errorz: " .$e->getMessage() .'<br>'; } catch (Exception $e) { echo "General Errorz: ".$e->getMessage() .'<br>'; }
得到了
DataBase Errorz: 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 '/*' LIMIT 1' at line 1
如果添加了$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
在$db = ...
然后得到空白页面
如果反而SELECT
试图DELETE
,那么在这两种情况下得到错误
DataBase Errorz: 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 '* FROM 2_1_paidused WHERE NumberRenamed = '¿\' OR 1=1 /*' LIMIT 1' at line 1
所以我的结论是,没有注射可能…
试试这个函数:多重查询和多值插入。
function employmentStatus($Status) { $pdo = PDO2::getInstance(); $sql_parts = array(); for($i=0; $i<count($Status); $i++){ $sql_parts[] = "(:userID, :val$i)"; } $requete = $pdo->dbh->prepare("DELETE FROM employment_status WHERE userid = :userID; INSERT INTO employment_status (userid, status) VALUES ".implode(",", $sql_parts)); $requete->bindParam(":userID", $_SESSION['userID'],PDO::PARAM_INT); for($i=0; $i<count($Status); $i++){ $requete->bindParam(":val$i", $Status[$i],PDO::PARAM_STR); } if ($requete->execute()) { return true; } return $requete->errorInfo(); }