Soporte PDO para múltiples consultas (PDO_MYSQL, PDO_MYSQLND)

Resuelto Gajus asked hace 54 años • 7 respuestas

Sé que PDO no admite la ejecución de múltiples consultas en una sola declaración. Estuve buscando en Google y encontré algunas publicaciones que hablaban sobre PDO_MYSQL y PDO_MYSQLND.

PDO_MySQL es una aplicación más peligrosa que cualquier otra aplicación MySQL tradicional. MySQL tradicional permite sólo una única consulta SQL. En PDO_MySQL no existe tal limitación, pero corre el riesgo de que le inyecten múltiples consultas.

De: Protección contra inyección SQL usando PDO y Zend Framework (junio de 2010; por Julian)

Parece que PDO_MYSQL y PDO_MYSQLND brindan soporte para múltiples consultas, pero no puedo encontrar más información sobre ellas. ¿Se suspendieron estos proyectos? ¿Hay alguna forma ahora de ejecutar múltiples consultas usando PDO?

Gajus avatar Jan 01 '70 08:01 Gajus
Aceptado

Como sé, PDO_MYSQLNDreemplazado PDO_MYSQLen PHP 5.3. Lo confuso es que el nombre todavía es PDO_MYSQL. Ahora ND es el controlador predeterminado para MySQL+PDO.

En general, para ejecutar varias consultas a la vez necesita:

  • PHP 5.3+
  • mysqlnd
  • Declaraciones preparadas emuladas. Asegúrese PDO::ATTR_EMULATE_PREPARESde que esté configurado en 1(predeterminado para mysql)

Usando ejecutivo

$db = new PDO("mysql:host=localhost;dbname=test;charset=utf8mb4", 'root', '');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

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

Tenga en cuenta que este método tiene un uso limitado y solo es adecuado para SQL que contiene valores constantes. Cuando los datos se suministran para SQL desde variables PHP, se deben utilizar declaraciones preparadas:

Usando declaraciones

$db = new PDO("mysql:host=localhost;dbname=test;charset=utf8mb4", 'root', '');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// wouldn't work if set to 0. You can comment out this line as 1 is a default
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES (:car1, :type1); 
INSERT INTO car(name, type) VALUES (:car2, :type2);
";

$stmt = $db->prepare($sql);
$stmt->execute(
    ["car1" => "brand1", "type1" => "coupe", "car2" => "brand2", "type2" => "coupe"]
);
// check for errors and collect query results
do {
    echo $pdo->lastInsertId(); // for example
} while ($stmt->nextRowset());

Tenga en cuenta que en este caso tiene un bucle sobre los resultados de la consulta después de ejecutar su declaración, para verificar posibles errores o recopilar los resultados de la consulta, como se muestra arriba. En caso de que no necesite recopilar ningún resultado, el ciclo se puede reducir a solo

while ($stmt->nextRowset());

que seguirá buscando errores (siempre que PDO::ATTR_ERRMODE esté configurado en PDO::ERRMODE_EXCEPTION como se muestra arriba).


Una nota:

Cuando utilice declaraciones preparadas emuladas, asegúrese de haber configurado la codificación adecuada (que refleje la codificación de datos real) en DSN (disponible desde 5.3.6). De lo contrario, puede haber una pequeña posibilidad de inyección SQL si se utiliza alguna codificación extraña .

Sam Dark avatar Jun 23 '2011 22:06 Sam Dark

Después de medio día de jugar con esto, descubrí que PDO tenía un error donde...

--

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

Ejecutaría el comando "valid-stmt1;", se detendría "non-sense;"y nunca arrojaría un error. No ejecutará "valid-stmt3;", devolverá verdadero y mentirá diciendo que todo funcionó bien.

Esperaría que se produjera un error, "non-sense;"pero no es así.

Aquí es donde encontré esta información: La consulta PDO no válida no devuelve un error

Aquí está el error: https://bugs.php.net/bug.php?id=61613


Entonces, intenté hacer esto con mysqli y realmente no encontré ninguna respuesta sólida sobre cómo funciona, así que pensé en dejarlo aquí para aquellos que quieran usarlo.

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>";
}
Sai Phaninder Reddy J avatar Mar 04 '2015 23:03 Sai Phaninder Reddy J

PDO admite esto (a partir de 2020). Simplemente haga una llamada query() o prepare() en un objeto PDO como de costumbre, separando las consultas por ; y luego nextRowset() para pasar al siguiente resultado SELECT, si tiene varios. Los conjuntos de resultados estarán en el mismo orden que las consultas. Obviamente, piense en las implicaciones de seguridad, así que no acepte consultas proporcionadas por el usuario, use parámetros, etc. Lo uso con consultas generadas por código, por ejemplo.

$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);
$query = "select * from t1 where param=?;select * from t2 where param=?"
$statement = $connection->prepare($query);
$statement->execute([$param1, $param2]);
do {
    $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());

Tenga en cuenta que para utilizar este método, las declaraciones preparadas emuladas deben estar activadas.

Andris avatar Jul 08 '2020 21:07 Andris

Un enfoque rápido y sucio:

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

Se divide en puntos finales razonables de la declaración SQL. No hay verificación de errores ni protección de inyección. Comprenda su uso antes de usarlo. Personalmente, lo uso para generar archivos de migración sin procesar para pruebas de integración.

bishop avatar May 06 '2015 16:05 bishop

Como miles de personas, estoy buscando esta pregunta:
¿Puedo ejecutar varias consultas simultáneamente y si había un error, no se ejecutaría ninguna? Fui a esta página en todas partes.
Pero aunque los amigos aquí dieron buenas respuestas, estas respuestas no fueron buenas para mi problema
Entonces escribí una función que funciona bien y casi no tiene problemas con la inyección SQL.
Puede ser útil para aquellos que buscan preguntas similares, así que las pongo aquí 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 (ejemplo):

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

y mi conexión:

    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 solución le ayuda a ejecutar varias declaraciones juntas.
Si se produce una declaración incorrecta, no ejecuta ninguna otra declaración.

Sajad Mirzaei avatar Mar 14 '2019 18:03 Sajad Mirzaei