Supporto PDO per più query (PDO_MYSQL, PDO_MYSQLND)


102

So che PDO non supporta più query eseguite in un'unica istruzione. Sono stato su Google e ho trovato alcuni post che parlano di PDO_MYSQL e PDO_MYSQLND.

PDO_MySQL è un'applicazione più pericolosa di qualsiasi altra applicazione MySQL tradizionale. MySQL tradizionale consente solo una singola query SQL. In PDO_MySQL non esiste tale limitazione, ma rischi di essere iniettato con più query.

Da: Protezione contro SQL Injection utilizzando PDO e Zend Framework (giugno 2010; di Julian)

Sembra che PDO_MYSQL e PDO_MYSQLND forniscano supporto per più query, ma non sono in grado di trovare ulteriori informazioni su di loro. Questi progetti sono stati interrotti? Esiste un modo ora per eseguire più query utilizzando PDO.


4
Usa transazioni SQL.
tereško

Perché desideri utilizzare più query? Non vengono effettuate transazioni, è proprio come se le eseguiresti una dopo l'altra. IMHO nessun pro, solo contro. In caso di SQLInjection si consente all'attaccante di fare quello che vuole.
mleko

È il 2020 ora e PDO lo supporta - vedi la mia risposta in basso.
Andris

Risposte:


141

Come so, PDO_MYSQLNDsostituito PDO_MYSQLin PHP 5.3. La parte confusa è che il nome è ancora PDO_MYSQL. Quindi ora ND è il driver predefinito per MySQL + PDO.

Nel complesso, per eseguire più query contemporaneamente è necessario:

  • PHP 5.3+
  • mysqlnd
  • Dichiarazioni preparate emulate. Assicurati che PDO::ATTR_EMULATE_PREPARESsia impostato su 1(predefinito). In alternativa puoi evitare di utilizzare dichiarazioni preparate e utilizzarle $pdo->execdirettamente.

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

Utilizzo di dichiarazioni

$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:

Quando si utilizzano istruzioni preparate emulate, assicurarsi di aver impostato la codifica corretta (che riflette la codifica dei dati effettivi) in DSN (disponibile dalla 5.3.6). Altrimenti potrebbe esserci una leggera possibilità di iniezione SQL se viene utilizzata una codifica dispari .


37
Non c'è niente di sbagliato nella risposta stessa. Spiega come eseguire più query. La tua supposizione che la risposta sia sbagliata deriva dal presupposto che la query contenga l'input dell'utente. Esistono casi d'uso validi in cui l'invio di più query contemporaneamente può migliorare le prestazioni. Potresti suggerire di utilizzare le procedure come risposta alternativa a questa domanda, ma ciò non rende la risposta negativa.
Gajus

9
Il codice in questa risposta è errato e promuove alcune pratiche molto dannose (l'uso dell'emulazione per le istruzioni prepares, che rendono il codice aperto alla vulnerabilità di SQL injection ). Non usarlo.
tereško

17
Niente di sbagliato in questa risposta e in particolare nella modalità di emulazione. È abilitato per impostazione predefinita in pdo_mysql e se ci fossero problemi, ci sarebbero già migliaia di iniezioni. Ma nessuno ne aveva ancora uno. Così è andata.
Il tuo buon senso

3
In effetti, solo uno che è riuscito a fornire non solo emozioni ma qualche argomento, era ircmaxell. Tuttavia, i collegamenti che ha portato sono abbastanza irrilevanti. Il primo è inapplicabile in quanto dice esplicitamente "PDO è sempre immune da questo bug". Mentre il secondo è risolvibile semplicemente impostando la codifica corretta. Quindi, merita una nota, non un avvertimento e uno meno attraente.
Il tuo buon senso

6
Parlando come qualcuno che sta scrivendo uno strumento di migrazione che utilizza SQL che solo i nostri sviluppatori hanno scritto (cioè l'iniezione di SQL non è un problema), questo mi ha aiutato moltissimo e qualsiasi commento che indichi che questo codice è dannoso non comprende appieno tutto il contesti per il suo utilizzo.
Luca

17

Dopo mezza giornata di giocherellare con questo, ho scoperto che PDO aveva un bug in cui ...

-

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

Eseguirà "valid-stmt1;", si fermerà "non-sense;"e non genererà mai un errore. Non verrà eseguito "valid-stmt3;", restituirà vero e mentirà che tutto è andato bene.

Mi aspetterei che si verifichi un errore su "non-sense;"ma non è così.

Qui è dove ho trovato queste informazioni: una query PDO non valida non restituisce un errore

Ecco il bug: https://bugs.php.net/bug.php?id=61613


Quindi, ho provato a farlo con mysqli e non ho trovato nessuna risposta solida su come funziona, quindi ho pensato di lasciarlo qui per coloro che vogliono 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>";
}

Funziona se esegui solo $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");senza i due dirigenti precedenti? Posso convincerlo a lanciare errori nel mezzo, ma non quando viene eseguito dopo esecuzioni di successo .
Jeff Puckett

No, non lo fa. Questo è il bug con PDO.
Sai Phaninder Reddy J

1
Colpa mia, quei 3 $pdo->exec("")sono indipendenti l'uno dall'altro. Ora li ho divisi per indicare che non devono essere in sequenza affinché il problema si presenti. Quelle 3 sono 3 configurazioni di esecuzione di più query in un'istruzione exec.
Sai Phaninder Reddy J

Interessante. Hai avuto la possibilità di vedere la mia domanda pubblicata? Mi chiedo se questo sia stato parzialmente corretto perché posso ricevere l'errore se è l'unico execsulla pagina, ma se eseguo più execciascuno con più istruzioni SQL, riproduco lo stesso bug qui. Ma se è l'unico execsulla pagina, non posso riprodurlo.
Jeff Puckett

Quella execsulla tua pagina conteneva più affermazioni?
Sai Phaninder Reddy J

3

Un approccio rapido e sporco:

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

Si divide in punti finali ragionevoli dell'istruzione SQL. Non c'è controllo degli errori, nessuna protezione dall'iniezione. Comprendi il tuo utilizzo prima di usarlo. Personalmente, lo uso per il seeding dei file di migrazione non elaborati per i test di integrazione.


1
Questo fallisce se il tuo file SQL contiene dei comandi incorporati mysql ... Probabilmente farà saltare anche il tuo limite di memoria PHP, se il file SQL è grande ... Diviso in ;pause se il tuo SQL contiene definizioni di procedure o trigger ... ragioni per cui non va bene.
Bill Karwin

1

Come migliaia di persone, sto cercando questa domanda:
può eseguire più query contemporaneamente, e se ci fosse un errore, nessuno verrebbe eseguito Sono andato a questa pagina ovunque
Ma sebbene gli amici qui abbiano dato buone risposte, queste risposte non andavano bene per il mio problema
Così ho scritto una funzione che funziona bene e non ha quasi nessun problema con sql Injection.
Potrebbe essere utile per coloro che sono alla ricerca di domande simili, quindi le metto qui per utilizzarle

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

per l'uso (esempio):

 $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 la mia connessione:

    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:
questa soluzione consente di eseguire più istruzioni contemporaneamente.
Se si verifica un'istruzione errata, non viene eseguita alcuna altra istruzione


0

Ho provato seguendo il codice

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

Poi

 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 ottenuto

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 aggiunto $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);dopo$db = ...

Quindi ha una pagina vuota

Se invece si è SELECTtentato DELETE, in entrambi i casi si ottiene un errore del tipo

 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

Quindi la mia conclusione che nessuna iniezione possibile ...


3
Avresti dovuto fare una nuova domanda in cui fare riferimento a questo
Il tuo buon senso

Non tanto domanda come risultato di quello che ho provato. E la mia conclusione. La domanda iniziale è vecchia, forse non attuale al momento.
Andris

Non sono sicuro di quanto questo sia rilevante per qualsiasi cosa nella domanda.
cHao

in questione sono parole La but you risk to be injected with multiple queries.mia risposta riguarda l'iniezione
Andris

0

Prova questa funzione: query mltiple e inserimento di più valori.

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

PDO lo supporta (a partire dal 2020). Basta fare una chiamata query () su un oggetto PDO come al solito, separando le query per; e quindi nextRowset () per passare al risultato SELECT successivo, se si dispone di più file. I set di risultati saranno nello stesso ordine delle query. Ovviamente pensa alle implicazioni sulla sicurezza, quindi non accettare query fornite dall'utente, utilizzare parametri, ecc. Lo uso con query generate dal codice, ad esempio.

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

Non avrei mai capito questo tipo di ragionamento: "Ecco un codice che è un grosso buco nella sicurezza trascurando tutte le buone pratiche consigliate, quindi è necessario pensare alle implicazioni sulla sicurezza". Chi dovrebbe pensarci? Quando dovrebbero pensare, prima di utilizzare questo codice o dopo essere stati violati? Perché non ci pensi prima, prima di scrivere questa funzione o di offrirla ad altre persone?
Il tuo buon senso

Caro @YourCommonSense, l'esecuzione di più query in una volta aiuta con le prestazioni, meno traffico di rete + il server può ottimizzare le query correlate. Il mio esempio (semplificato) intendeva solo introdurre il metodo richiesto per usarlo. È un buco di sicurezza solo se non usi quelle buone pratiche a cui ti riferisci. A proposito, sono sospettoso delle persone che dicono "Non avrei mai capito ..." quando potrebbero facilmente ... :-)
Andris
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.