Поддержка PDO для нескольких запросов (PDO_MYSQL, PDO_MYSQLND)

Я знаю, что PDO не поддерживает несколько запросов, выполняемых в одном выражении. Я был Google и нашел несколько сообщений о PDO_MYSQL и PDO_MYSQLND.

PDO_MySQL – более опасное приложение, чем любые другие традиционные приложения MySQL. Традиционный MySQL допускает только один SQL-запрос. В PDO_MySQL такого ограничения нет, но вы рискуете получить несколько запросов.

От: защита от SQL-инъекций с использованием PDO и Zend Framework (июнь 2010 г., Julian)

Похоже, что PDO_MYSQL и PDO_MYSQLND обеспечивают поддержку нескольких запросов, но я не могу найти больше информации о них. Были ли эти проекты прекращены? Есть ли способ запустить несколько запросов с помощью PDO.

Как я знаю, PDO_MYSQLND заменил PDO_MYSQL в PHP 5.3. Сбивая с толку часть, это имя по-прежнему PDO_MYSQL . Итак, теперь ND является драйвером по умолчанию для MySQL + PDO.

В целом, для одновременного выполнения нескольких запросов вам необходимо:

  • PHP 5.3+
  • mysqlnd
  • Эмулированные подготовленные заявления. Убедитесь, что для параметра PDO::ATTR_EMULATE_PREPARES установлено значение 1 (по умолчанию). В качестве альтернативы вы можете избежать использования подготовленных операторов и напрямую использовать $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 была ошибка, где …

 //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 и на самом деле не нашел твердого ответа на то, как это работает, поэтому я решил оставить его здесь для тех, кто хочет его использовать.

 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>"; } в 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. Проверка ошибок отсутствует, защита от инъекций отсутствует. Поймите свое использование перед использованием. Лично я использую его для загрузки необработанных файлов миграции для тестирования интеграции.

Попробуйте эту функцию: mltiple query и несколько значений вставки.

 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(); } 

Пробовал следующий код

  $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 

Поэтому мой вывод о том, что инъекции невозможно …