Dukungan PDO untuk beberapa kueri (PDO_MYSQL, PDO_MYSQLND)

102

Saya tahu bahwa PDO tidak mendukung banyak kueri yang dieksekusi dalam satu pernyataan. Saya telah Googleing dan menemukan beberapa posting yang berbicara tentang PDO_MYSQL dan PDO_MYSQLND.

PDO_MySQL adalah aplikasi yang lebih berbahaya daripada aplikasi MySQL tradisional lainnya. MySQL tradisional hanya mengizinkan satu kueri SQL. Di PDO_MySQL tidak ada batasan seperti itu, tetapi Anda berisiko disuntik dengan banyak kueri.

Dari: Perlindungan terhadap SQL Injection menggunakan PDO dan Zend Framework (Juni 2010; oleh Julian)

Sepertinya PDO_MYSQL dan PDO_MYSQLND memang menyediakan dukungan untuk beberapa kueri, tetapi saya tidak dapat menemukan informasi lebih lanjut tentang mereka. Apakah proyek ini dihentikan? Apakah sekarang ada cara untuk menjalankan banyak query menggunakan PDO.

Gajus
sumber
4
Gunakan transaksi SQL.
tereško
Mengapa Anda ingin menggunakan banyak kueri? Mereka tidak ditransaksikan, sama seperti Anda akan mengeksekusinya satu demi satu. IMHO tidak ada pro, hanya kontra. Dalam kasus SQLInjection, Anda mengizinkan penyerang untuk melakukan apa pun yang dia inginkan.
mleko
Sekarang tahun 2020, dan PDO mendukung ini - lihat jawaban saya di bawah.
Andris

Jawaban:

141

Seperti yang saya tahu, PDO_MYSQLNDdiganti PDO_MYSQLdengan PHP 5.3. Bagian yang membingungkan adalah nama itu diam PDO_MYSQL. Jadi sekarang ND adalah driver default untuk MySQL + PDO.

Secara keseluruhan, untuk menjalankan beberapa kueri sekaligus, Anda membutuhkan:

  • PHP 5.3+
  • mysqlnd
  • Meniru pernyataan yang disiapkan. Pastikan PDO::ATTR_EMULATE_PREPARESdiatur ke 1(default). Alternatifnya, Anda dapat menghindari penggunaan pernyataan yang disiapkan dan digunakan $pdo->execsecara langsung.

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

Menggunakan pernyataan

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

Sebuah catatan:

Saat menggunakan pernyataan siap yang diemulasi, pastikan Anda telah menyetel pengkodean yang tepat (yang mencerminkan pengkodean data aktual) di DSN (tersedia sejak 5.3.6). Jika tidak, ada kemungkinan kecil untuk injeksi SQL jika beberapa pengkodean aneh digunakan .

Sam Dark
sumber
37
Tidak ada yang salah dengan jawabannya sendiri. Ini menjelaskan cara menjalankan beberapa kueri. Asumsi Anda bahwa jawabannya salah berasal dari asumsi bahwa kueri berisi input pengguna. Ada kasus penggunaan yang valid di mana mengirim melalui beberapa kueri sekaligus dapat menguntungkan kinerja. Anda dapat menyarankan menggunakan prosedur sebagai jawaban alternatif untuk pertanyaan ini, tetapi itu tidak membuat jawaban ini buruk.
Gajus
9
Kode dalam jawaban ini buruk, dan mempromosikan beberapa praktik yang sangat berbahaya (penggunaan emulasi untuk pernyataan persiapan, yang membuat kode terbuka untuk kerentanan injeksi SQL ). Jangan gunakan itu.
tereško
17
Tidak ada yang salah dengan jawaban ini, dan mode emulasi pada khususnya. Ini diaktifkan secara default di pdo_mysql, dan jika ada masalah, sudah akan ada ribuan suntikan. Tapi belum ada yang mendekati satu pun. Begitu seterusnya.
Akal Sehat Anda
3
Faktanya, hanya satu yang berhasil memberikan tidak hanya emosi tetapi beberapa argumen, adalah ircmaxell. Namun, tautan yang dia bawa cukup tidak relevan. Yang pertama tidak dapat diterapkan sama sekali karena secara eksplisit mengatakan "PDO selalu kebal dari bug ini." Sementara yang kedua hanya dapat dipecahkan dengan mengatur pengkodean yang tepat. Jadi, itu patut mendapat catatan, bukan peringatan, dan yang kurang menarik.
Akal Sehat Anda
6
Berbicara sebagai seseorang yang menulis alat migrasi yang menggunakan SQL yang hanya ditulis oleh pengembang kami (yaitu injeksi SQL tidak menjadi masalah), ini telah membantu saya secara masif, dan setiap komentar yang menunjukkan bahwa kode ini berbahaya tidak sepenuhnya memahami semua konteks penggunaannya.
Lukas
17

Setelah setengah hari mengotak-atik ini, ternyata PDO memiliki bug di mana ...

-

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

Itu akan mengeksekusi "valid-stmt1;", berhenti "non-sense;"dan tidak pernah membuat kesalahan. Tidak akan menjalankan "valid-stmt3;", mengembalikan kebenaran dan berbohong bahwa semuanya berjalan dengan baik.

Saya berharap itu salah "non-sense;"tapi ternyata tidak.

Di sinilah saya menemukan info ini: Permintaan PDO yang tidak valid tidak mengembalikan kesalahan

Berikut bugnya: https://bugs.php.net/bug.php?id=61613


Jadi, saya mencoba melakukan ini dengan mysqli dan belum benar-benar menemukan jawaban yang pasti tentang cara kerjanya jadi saya pikir saya tinggalkan saja di sini untuk mereka yang ingin menggunakannya ..

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
sumber
Apakah ini berfungsi jika Anda hanya berjalan $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");tanpa dua eksekutif sebelumnya? Saya bisa membuatnya membuang kesalahan di tengah, tetapi tidak ketika dijalankan setelah eksekutif sukses .
Jeff Puckett
Tidak, tidak. Itulah bug dengan PDO.
Sai Phaninder Reddy J
1
Saya buruk, 3 itu $pdo->exec("")tidak tergantung satu sama lain. Sekarang saya membaginya untuk menunjukkan bahwa mereka tidak harus berurutan agar masalah muncul. Ketiga adalah 3 konfigurasi menjalankan beberapa query dalam satu pernyataan exec.
Sai Phaninder Reddy J
Menarik. Apakah Anda mendapat kesempatan untuk melihat pertanyaan saya yang diposting? Saya ingin tahu apakah ini telah ditambal sebagian karena saya bisa mendapatkan kesalahan jika itu satu-satunya execdi halaman, tetapi jika saya menjalankan beberapa execmasing-masing dengan beberapa pernyataan SQL di dalamnya, maka saya mereproduksi bug yang sama di sini. Tetapi jika itu satu-satunya execdi halaman, maka saya tidak dapat mereproduksinya.
Jeff Puckett
Apakah yang execdi halaman Anda memiliki banyak pernyataan?
Sai Phaninder Reddy J
3

Pendekatan cepat dan kotor:

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

Perpecahan pada titik akhir pernyataan SQL yang wajar. Tidak ada pemeriksaan kesalahan, tidak ada perlindungan injeksi. Pahami penggunaan Anda sebelum menggunakannya. Secara pribadi, saya menggunakannya untuk menyemai file migrasi mentah untuk pengujian integrasi.

uskup
sumber
1
Ini gagal jika file SQL Anda berisi perintah bawaan mysql ... Ini mungkin akan meledakkan batas memori PHP Anda juga, jika file SQL besar ... Membagi saat ;istirahat jika SQL Anda berisi prosedur atau definisi pemicu ... Banyak alasan mengapa itu tidak baik.
Bill Karwin
1

Seperti ribuan orang, saya mencari pertanyaan ini:
Dapat menjalankan beberapa kueri secara bersamaan, dan jika ada satu kesalahan, tidak ada yang akan berjalan Saya membuka halaman ini di mana-mana
Tetapi meskipun teman-teman di sini memberikan jawaban yang baik, jawaban ini tidak baik untuk masalah saya
Jadi saya menulis sebuah fungsi yang bekerja dengan baik dan memiliki hampir tidak ada masalah dengan Injection sql.
Mungkin berguna bagi mereka yang mencari pertanyaan serupa jadi saya taruh di sini untuk digunakan

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

untuk digunakan (contoh):

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

dan koneksi saya:

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

Catatan:
Solusi ini membantu Anda menjalankan beberapa pernyataan bersama-sama,
Jika terjadi pernyataan yang salah, itu tidak mengeksekusi pernyataan lain

mirzaei.sajad
sumber
0

Mencoba kode berikut

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

Kemudian

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

Dan mendapatkan

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

Jika ditambahkan $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);setelah$db = ...

Lalu mendapat halaman kosong

Jika malah SELECTdicoba DELETE, maka dalam kedua kasus tersebut mendapat error seperti

 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

Jadi kesimpulan saya bahwa suntikan tidak mungkin ...

Andris
sumber
3
Anda seharusnya membuat pertanyaan baru di mana merujuk ke pertanyaan ini
Akal
Tidak begitu banyak pertanyaan sebagai hasil dari apa yang saya coba. Dan kesimpulan saya. Pertanyaan awal sudah lama, mungkin tidak aktual saat ini.
Andris
Tidak yakin bagaimana ini relevan dengan apa pun dalam pertanyaan.
cHao
dalam pertanyaan adalah kata-kata but you risk to be injected with multiple queries.Jawaban saya tentang injeksi
Andris
0

Coba fungsi ini: beberapa kueri dan beberapa nilai penyisipan.

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.
sumber
0

PDO mendukung ini (per 2020). Lakukan saja panggilan query () pada objek PDO seperti biasa, pisahkan kueri dengan; dan kemudian nextRowset () untuk melangkah ke hasil SELECT berikutnya, jika Anda memiliki beberapa. Hasil akan memiliki urutan yang sama dengan kueri. Jelas pikirkan tentang implikasi keamanan - jadi jangan terima kueri yang disediakan pengguna, gunakan parameter, dll. Saya menggunakannya dengan kueri yang dihasilkan oleh kode misalnya.

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());
Andris
sumber
Saya tidak akan pernah memahami alasan seperti ini, "Ini adalah kode yang merupakan lubang besar dalam keamanan yang mengabaikan semua praktik baik yang disarankan sehingga Anda perlu memikirkan implikasi keamanan." Siapa yang harus memikirkannya? Kapan mereka harus berpikir - sebelum menggunakan kode ini atau setelah mereka diretas? Mengapa Anda tidak memikirkannya dulu, sebelum menulis fungsi ini atau menawarkannya kepada orang lain?
Akal Sehat Anda
@YourCommonSense yang terhormat, menjalankan beberapa kueri sekaligus membantu kinerja, lebih sedikit lalu lintas jaringan + server dapat mengoptimalkan kueri terkait. Contoh saya (yang disederhanakan) hanya dimaksudkan untuk memperkenalkan metode yang diperlukan untuk menggunakannya. Ini adalah lubang keamanan hanya jika Anda tidak menggunakan praktik baik yang Anda maksud. BTW, saya curiga dengan orang-orang yang mengatakan "Saya tidak akan pernah mengerti ..." padahal mereka dengan mudah bisa ... :-)
Andris