Soporte de PDO para múltiples consultas (PDO MYSQL, PDO MYSQLND)


Sé que PDO no admite que se ejecuten varias consultas en una sola instrucción. He estado buscando en Google y he encontrado algunos mensajes que hablan de PDO_MYSQL y PDO_MYSQLND.

PDO_MySQL es un método más peligroso aplicación que cualquier otra tradicional Aplicaciones MySQL. MySQL tradicional permite una sola consulta SQL. En PDO_MySQL no existe tal limitación, pero corre el riesgo de ser inyectado con múltiples consultas.

Desde: Protección contra SQL Inyección usando DOP y Zend Framework (junio de 2010; por Julian)

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

Author: Matt, 2011-06-14

5 answers

Como sé, PDO_MYSQLND reemplazó a PDO_MYSQL en 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 múltiples consultas a la vez necesita:

  • PHP 5.3 +
  • mysqlnd
  • Emuló declaraciones preparadas. Asegúrese de que PDO::ATTR_EMULATE_PREPARES esté establecido en 1 (predeterminado). Alternativamente, puede evitar el uso de instrucciones preparadas y usar $pdo->exec directamente.

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

try {
    $db->exec($sql);
}
catch (PDOException $e)
{
    echo $e->getMessage();
    die();
}

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

try {
    $stmt = $db->prepare($sql);
    $stmt->execute();
}
catch (PDOException $e)
{
    echo $e->getMessage();
    die();
}

Una nota:

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

 123
Author: Sam Dark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:20

Después de medio día de juguetear con esto, se enteró de 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 en "non-sense;" y nunca lanzaría un error. No se ejecutará el "valid-stmt3;", devolver verdadero y mentira que todo funcionó bien.

Esperaría que se equivoque en el "non-sense;" pero no lo hace.

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


Así que intenté hacer esto con mysqli y realmente no he encontrado ninguna respuesta sólida sobre cómo funciona, así que pensé que solo 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>";
}
 15
Author: Sai Phaninder Reddy J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:25:52

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

 4
Author: bishop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-06 16:26:48

Pruebe esta función : mltiple queries and multiple values insertion.

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
Author: hassan b.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-17 16:00:59

Intentó seguir el código

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

Entonces

 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 añade $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); después de $db = ...

Entonces tengo una página en blanco

Si en cambio SELECT intentó DELETE, entonces 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

Así que mi conclusión es que no hay inyección posible...

 -1
Author: Andris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-30 17:04:57