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

102

Sé que PDO no admite la ejecución de varias consultas en una declaración. He estado buscando en Google y encontré algunas publicaciones que hablan 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 una sola 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 la 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 interrumpieron estos proyectos? ¿Hay alguna forma ahora de ejecutar múltiples consultas usando PDO?

Gajus
fuente
4
Utilice transacciones SQL.
tereško
¿Por qué le gustaría utilizar varias consultas? No se realizan transacciones, es lo mismo que los ejecutaría uno tras otro. En mi humilde opinión, no hay pros, solo contras. En el caso de SQLInjection, le permite al atacante hacer lo que quiera.
mleko
Es 2020 ahora, y PDO lo admite: vea mi respuesta a continuación.
Andris

Respuestas:

141

Como sé, PDO_MYSQLNDreemplazado PDO_MYSQLen PHP 5.3. La parte confusa es que el nombre sigue siendo PDO_MYSQL. Así que 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 de que PDO::ATTR_EMULATE_PREPARESesté configurado en 1(predeterminado). Alternativamente, puede evitar usar declaraciones preparadas y usarlas $pdo->execdirectamente.

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 declaraciones

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

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 usa alguna codificación extraña .

Sam Dark
fuente
37
No hay nada de malo en la respuesta en sí. Explica cómo ejecutar múltiples consultas. Su suposición de que la respuesta es defectuosa proviene de la suposición de que la consulta contiene información de usuario. Existen casos de uso válidos en los que enviar varias consultas a la vez puede beneficiar el rendimiento. Podría sugerir el uso de procedimientos como una respuesta alternativa a esta pregunta, pero eso no hace que esta respuesta sea mala.
Gajus
9
El código en esta respuesta es malo y promueve algunas prácticas muy dañinas (uso de emulación para declaraciones preparadas, que hacen que el código esté abierto a la vulnerabilidad de inyección SQL ). No lo uses.
tereško
17
No hay nada de malo en esta respuesta, y en el modo de emulación en particular. Está habilitado por defecto en pdo_mysql, y si hubiera algún problema, ya habría miles de inyecciones. Pero nadie se acerca a uno todavía. Así que va.
Tu sentido común
3
De hecho, solo uno que logró proporcionar no solo emociones sino algún argumento, fue Ircmaxell. Sin embargo, los vínculos que trajo son bastante irrelevantes. El primero es inaplicable en absoluto, ya que dice explícitamente "PDO siempre es inmune a este error". Mientras que el segundo es simplemente solucionable configurando la codificación adecuada. Por lo tanto, merece una nota, no una advertencia, y menos atractiva.
Your Common Sense
6
Hablando como alguien que está escribiendo una herramienta de migración que usa SQL que solo nuestros desarrolladores han escrito (es decir, la inyección de SQL no es un problema), esto me ha ayudado enormemente, y cualquier comentario que indique que este código es dañino no comprende completamente todos los contextos para su uso.
Luke
17

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 "valid-stmt1;", se detendría "non-sense;"y nunca arrojaría un error. No correrá el "valid-stmt3;", volverá verdad y mentirá que todo salió 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 he encontrado una respuesta sólida sobre cómo funciona, así que pensé que lo dejaría 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
fuente
¿Funciona si solo ejecuta $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");sin los dos ejecutivos anteriores? Puedo hacer que arroje errores en el medio, pero no cuando se ejecuta después de ejecutivos exitosos .
Jeff Puckett
No, no es así. Ese es el error con PDO.
Sai Phaninder Reddy J
1
Mi mal, esos 3 $pdo->exec("")son independientes entre sí. Ahora los divido para indicar que no es necesario que estén en una secuencia para que surja el problema. Esas 3 son 3 configuraciones para ejecutar varias consultas en una declaración ejecutiva.
Sai Phaninder Reddy J
Interesante. ¿Tuviste la oportunidad de ver mi pregunta publicada? Me pregunto si esto se ha parcheado parcialmente porque puedo generar el error si es el único execen la página, pero si ejecuto varias, execcada una con varias declaraciones SQL, reproduzco el mismo error aquí. Pero si es el único execen la página, entonces no puedo reproducirlo.
Jeff Puckett
¿Ese execde tu página tenía varias declaraciones?
Sai Phaninder Reddy J
3

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 instrucción SQL. No hay comprobación de errores ni protección contra inyección. Comprenda su uso antes de usarlo. Personalmente, lo uso para sembrar archivos de migración sin procesar para pruebas de integración.

obispo
fuente
1
Esto falla si su archivo SQL contiene cualquier comando incorporado de mysql ... Probablemente también superará su límite de memoria PHP, si el archivo SQL es grande ... Dividiendo en ;interrupciones si su SQL contiene definiciones de procedimiento o disparador ... Un montón de razones por las que no es bueno.
Bill Karwin
1

Al igual que miles de personas, estoy buscando esta pregunta:
puedo ejecutar varias consultas simultáneamente, y si hubiera un error, ninguna se ejecutaría. Fui a esta página en todas partes.
Pero aunque los amigos aquí dieron buenas respuestas, estas respuestas no fueron buenas para mi problema
Así que escribí una función que funciona bien y casi no tiene ningún problema 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 sentencias juntas,
si se produce una sentencia incorrecta, no ejecuta ninguna otra sentencia

mirzaei.sajad
fuente
0

Intenté el siguiente código

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

Luego

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

Y consiguió

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

Si se agrega $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);después$db = ...

Luego tengo una página en blanco

Si en cambio lo SELECTintentó DELETE, en ambos casos obtuvo un error 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

Entonces mi conclusión de que no es posible una inyección ...

Andris
fuente
3
Debería haber hecho una nueva pregunta al hacer referencia a esta
su sentido común
No hay tantas dudas como resultado de lo que intenté. Y mi conclusión. La pregunta inicial es antigua, posiblemente no actual en este momento.
Andris
No estoy seguro de cómo esto es relevante para algo en la pregunta.
cHao
en cuestión son palabras but you risk to be injected with multiple queries.Mi respuesta es sobre la inyección
Andris
0

Pruebe esta función: múltiples consultas e inserción de múltiples valores.

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();
}
hassan b.
fuente
0

PDO admite esto (a partir de 2020). Simplemente haga una llamada query () 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.

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());
Andris
fuente
Nunca entendería este tipo de razonamiento: "Aquí hay un código que es un gran agujero en la seguridad que descuida todas las buenas prácticas recomendadas, por lo que debe pensar en las implicaciones de seguridad". ¿Quién debería pensarlo? ¿Cuándo deberían pensar, antes de usar este código o después de ser pirateados? ¿Por qué no lo piensa primero, antes de escribir esta función u ofrecerla a otras personas?
Tu sentido común
Estimado @YourCommonSense, ejecutar varias consultas a la vez ayuda con el rendimiento, menos tráfico de red + el servidor puede optimizar las consultas relacionadas. Mi ejemplo (simplificado) solo pretendía presentar el método requerido para usarlo. Es un agujero de seguridad solo si no utiliza esas buenas prácticas a las que se refiere. Por cierto, sospecho de las personas que dicen "Yo nunca entendería ..." cuando fácilmente podrían ... :-)
Andris