Suporte PDO para consultas múltiplas (PDO_MYSQL, PDO_MYSQLND)


102

Eu sei que o PDO não oferece suporte a várias consultas sendo executadas em uma instrução. Estive no Google e encontrei alguns posts falando sobre PDO_MYSQL e PDO_MYSQLND.

PDO_MySQL é um aplicativo mais perigoso do que qualquer outro aplicativo MySQL tradicional. O MySQL tradicional permite apenas uma única consulta SQL. No PDO_MySQL não existe essa limitação, mas você corre o risco de ser injetado com várias consultas.

De: Proteção contra injeção de SQL usando PDO e Zend Framework (junho de 2010; por Julian)

Parece que PDO_MYSQL e PDO_MYSQLND fornecem suporte para várias consultas, mas não consigo encontrar mais informações sobre eles. Esses projetos foram interrompidos? Existe alguma maneira agora de executar várias consultas usando PDO.


4
Use transações SQL.
tereško

Por que você gostaria de usar várias consultas? Eles não são negociados, é a mesma coisa que você os executaria um após o outro. IMHO sem prós, apenas contras. No caso de SQLInjection, você permite que o invasor faça o que quiser.
mleko de

Agora é 2020 e o PDO apóia isso - veja minha resposta abaixo.
Andris

Respostas:


141

Como eu sei, PDO_MYSQLNDsubstituído PDO_MYSQLno PHP 5.3. A parte confusa é que o nome ainda está PDO_MYSQL. Portanto, agora ND é o driver padrão para MySQL + PDO.

No geral, para executar várias consultas de uma vez, você precisa:

  • PHP 5.3+
  • mysqlnd
  • Emulado declarações preparadas. Certifique-se de que PDO::ATTR_EMULATE_PREPARESestá definido como 1(padrão). Alternativamente, você pode evitar o uso de instruções preparadas e usá- $pdo->execlas diretamente.

Usando 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');
";

$db->exec($sql);

Usando declarações

$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');
";

$stmt = $db->prepare($sql);
$stmt->execute();

Uma nota:

Ao usar instruções preparadas emuladas, certifique-se de definir a codificação adequada (que reflete a codificação real dos dados) no DSN (disponível desde 5.3.6). Caso contrário , pode haver uma pequena possibilidade de injeção de SQL se alguma codificação estranha for usada .


37
Não há nada de errado com a resposta em si. Ele explica como executar várias consultas. Sua suposição de que a resposta é falha vem da suposição de que a consulta contém entrada do usuário. Existem casos de uso válidos em que o envio por meio de várias consultas de uma vez pode beneficiar o desempenho. Você poderia sugerir o uso de procedimentos como uma resposta alternativa a essa pergunta, mas isso não torna a resposta ruim.
Gajus de

9
O código nesta resposta é ruim e promove algumas práticas muito prejudiciais (uso de emulação para instruções prepares, que tornam o código aberto à vulnerabilidade de injeção de SQL ). Não use isso.
tereško

17
Nada de errado com esta resposta e o modo de emulação em particular. Ele é habilitado por padrão em pdo_mysql, e se houvesse algum problema, já haveria milhares de injeções. Mas ninguém perto de um ainda. Assim vai.
Seu senso comum

3
Na verdade, apenas aquele que conseguiu fornecer não apenas emoções, mas também algum argumento, foi ircmaxell. No entanto, os links que ele trouxe são bastante irrelevantes. O primeiro é inaplicável, pois diz explicitamente "O PDO é sempre imune a esse bug." Enquanto o segundo pode ser resolvido simplesmente definindo a codificação adequada. Portanto, ele merece uma nota, não advertência e menos atraente.
Seu senso comum

6
Falando como alguém que está escrevendo uma ferramenta de migração que usa SQL que apenas nossos desenvolvedores escreveram (ou seja, injeção de SQL não é um problema), isso me ajudou muito, e quaisquer comentários indicando que este código é prejudicial não compreendem totalmente contextos para seu uso.
Lucas

17

Depois de meio dia mexendo nisso, descobri que o PDO tinha um bug onde ...

-

//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;");

Ele executaria o "valid-stmt1;", pararia "non-sense;"e nunca geraria um erro. Não vai correr o "valid-stmt3;", volta verdadeiro e minta que tudo correu bem.

Eu esperaria um erro no, "non-sense;"mas não acontece.

Aqui é onde encontrei esta informação: Consulta PDO inválida não retorna um erro

Aqui está o bug: https://bugs.php.net/bug.php?id=61613


Tentei fazer isso com o mysqli e não encontrei nenhuma resposta sólida sobre como funciona, então pensei em deixar aqui para quem quiser usar.

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

Funciona se você apenas executar $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");sem os dois executivos anteriores? Posso fazer com que ele lance erros no meio, mas não quando executado após execs bem-sucedidos .
Jeff Puckett

Não, não é. Esse é o bug do PDO.
Sai Phaninder Reddy J,

1
Que pena, esses 3 $pdo->exec("")são independentes um do outro. Agora, divido-os para indicar que não precisam estar em uma sequência para que o problema surja. Essas 3 são 3 configurações de execução de várias consultas em uma instrução exec.
Sai Phaninder Reddy J,

Interessante. Você teve a chance de ver minha pergunta postada? Eu me pergunto se isso foi corrigido parcialmente porque posso obter o erro lançado se for o único execna página, mas se eu executar vários execcada um com várias instruções SQL neles, reproduzo o mesmo bug aqui. Mas se for o único execna página, não posso reproduzi-lo.
Jeff Puckett,

Aquele execem sua página tinha várias declarações?
Sai Phaninder Reddy J

3

Uma abordagem rápida e suja:

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

Divide em pontos finais de instrução SQL razoáveis. Não há verificação de erros, nem proteção contra injeção. Entenda seu uso antes de usá-lo. Pessoalmente, eu o uso para semear arquivos de migração brutos para testes de integração.


1
Isso falhará se o seu arquivo SQL contiver qualquer comando interno do mysql ... Provavelmente também irá estourar o limite de memória do PHP, se o arquivo SQL for grande ... Dividir em ;quebras se seu SQL contiver procedimentos ou definições de gatilho ... Muitos razões pelas quais não é bom.
Bill Karwin,

1

Como milhares de pessoas, estou procurando esta pergunta:
Posso executar várias consultas simultaneamente, e se houvesse um erro, nenhuma seria executada Eu fui a esta página em todos os lugares.
Mas embora os amigos aqui tenham dado boas respostas, essas respostas não eram boas para meu problema
Então eu escrevi uma função que funciona bem e quase não tem problemas com injeção de sql.
Pode ser útil para aqueles que procuram perguntas semelhantes, então eu as coloco aqui para usar

function arrayOfQuerys($arrayQuery)
{
    $mx = true;
    $conn->beginTransaction();
    try {
        foreach ($arrayQuery AS $item) {
            $stmt = $conn->prepare($item["query"]);
            $stmt->execute($item["params"]);
            $result = $stmt->rowCount();
            if($result == 0)
                $mx = false;
         }
         if($mx == true)
             $conn->commit();
         else
             $conn->rollBack();
    } catch (Exception $e) {
        $conn->rollBack();
        echo "Failed: " . $e->getMessage();
    }
    return $mx;
}

para uso (exemplo):

 $arrayQuery = Array(
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("aa1", 1)
    ),
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("bb1", 2)
    )
);
arrayOfQuerys($arrayQuery);

e minha conexão:

    try {
        $options = array(
            //For updates where newvalue = oldvalue PDOStatement::rowCount()   returns zero. You can use this:
            PDO::MYSQL_ATTR_FOUND_ROWS => true
        );
        $conn = new PDO("mysql:host=$servername;dbname=$database", $username, $password, $options);
        $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        echo "Error connecting to SQL Server: " . $e->getMessage();
    }

Nota:
Esta solução ajuda você a executar várias instruções juntas.
Se ocorrer uma instrução incorreta, ela não executará nenhuma outra instrução


0

Tentei seguir o código

 $db = new PDO("mysql:host={$dbhost};dbname={$dbname};charset=utf8", $dbuser, $dbpass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Então

 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>';
 }

E pegou

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

Se adicionado $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);depois$db = ...

Então tenho a página em branco

Se em vez disso SELECTtentasse DELETE, em ambos os casos ocorria um erro como

 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

Portanto, minha conclusão de que nenhuma injeção é possível ...


3
Você deveria ter feito uma nova pergunta ao se referir a esta
Seu senso comum

Nem tanto por causa do que tentei. E minha conclusão. A pergunta inicial é antiga, possivelmente não real no momento.
Andris

Não tenho certeza de como isso é relevante para alguma coisa na questão.
cHao

em questão são palavras but you risk to be injected with multiple queries.Minha resposta é sobre injeção
Andris

0

Experimente esta função: consultas múltiplas e inserção de valores múltiplos.

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

0

O PDO apoia isso (a partir de 2020). Basta fazer uma chamada query () em um objeto PDO como de costume, separando as consultas por; e, em seguida, nextRowset () para passar para o próximo resultado de SELECT, se houver vários. Os conjuntos de resultados estarão na mesma ordem das consultas. Obviamente, pense nas implicações de segurança - portanto, não aceite consultas fornecidas pelo usuário, use parâmetros, etc. Eu uso isso com consultas geradas por código, por exemplo.

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());

Eu nunca entenderia esse tipo de raciocínio: "Aqui está um código que é um grande buraco na segurança, negligenciando todas as boas práticas recomendadas, então você precisa pensar sobre as implicações de segurança." Quem deve pensar nisso? Quando eles devem pensar - antes de usar esse código ou depois de serem hackeados? Por que você não pensa primeiro, antes de escrever esta função ou oferecê-la a outras pessoas?
Seu senso comum

Caro @YourCommonSense, executar várias consultas de uma só vez ajuda no desempenho, menos tráfego de rede + servidor pode otimizar as consultas relacionadas. Meu exemplo (simplificado) pretendia apenas apresentar o método necessário para usá-lo. É um buraco de segurança apenas se você não usar as boas práticas a que se refere. Aliás, desconfio de pessoas que dizem "Eu nunca entenderia ..." quando poderiam facilmente ... :-)
Andris
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.