15

I stumbled upon this question from two years ago.

Is there a way to get the raw SQL string executed when calling PDOStatement::execute() on a prepared statement? For debugging purposes this would be extremely useful.

The winning answer states that

[...] You can also get what you want if you set the PDO attribute PDO::ATTR_EMULATE_PREPARES. In this mode, PDO interpolate parameters into the SQL query and sends the whole query when you execute().

But it doesn't mention how to get the resulting query string. I know it's a bad idea performance wise but that doesn't bother me in debug mode. Does anybody know how to do this?

PS If there is some way I could have reopened / drawn attention to the original two year old topic instead of opening a new one, please let me know.

Community
  • 1
  • 1
Michael Clerx
  • 2,928
  • 2
  • 33
  • 47
  • The accepted answer *does* tell you where to discover the query: in your database server's log. – Rob Kennedy Sep 23 '10 at 00:14
  • @Rob Yes I read that too. It says: "MySQL's general query log does show the final SQL with values interpolated after you execute()". But then it goes on to say "You can also get what you want if...", which suggests there's a second way. That's what I want to find out. How to do it without access to the full mysql server log. – Michael Clerx Sep 23 '10 at 08:03
  • 1
    I think it meant that rather than looking at the logs for the prepared statement and *then* the server-synthesized statement afterward, you can also have PHP generate the full SQL query on the client side, and then the *only* thing in the server log will be the actual SQL query that it executed. That is, the answer was describing two different ways to get the query in the server log. – Rob Kennedy Sep 23 '10 at 15:10
  • +1 For that comment! But I still hope you're wrong :) – Michael Clerx Sep 23 '10 at 17:27
  • Turning on server logs is the best way to do this: if I had not waited hours to do it it would have saved me hours: [instructions for logging PDO prepared statements](http://stackoverflow.com/a/15964840/932820) – Chris Adams Apr 12 '13 at 06:52
  • @Anton Paniflov's answer below is what I was looking for - some of us want queries written to a combined application log, rather than to a dedicated server log, since correlating timestamped entries from an application log with a database log is very cumbersome... – mindplay.dk Oct 27 '15 at 12:58

5 Answers5

14

I believe this is mentioned in the original question that was reference in this one. However there is actually supposed to be a method for retrieving this data.

PDOStatement::debugDumpParams

However it isn't currently working as documented. There is a bug report and patch submitted for it here http://bugs.php.net/bug.php?id=52384 in case anyone is interested in voting on it. Until it's fixed it seems like you are left to use query logging or setting a custom statement class using the PDO::ATTR_STATEMENT_CLASS attribute.

Chris Gutierrez
  • 4,750
  • 19
  • 18
2

Afaik, PDO doesn't really expose it to you. On development servers, you could enable the general query log for MySQL (if that's what you use), with possibly more control with sql_log_off, which does require the SUPER privilege.

Wrikken
  • 69,272
  • 8
  • 97
  • 136
2

If you can't get it from PDO itself, consider using a wrapper class just for PDOStatement::execute() which will log the SQL query and values, and then call execute() on the statement. You will have to refactor your code to use the new class.

As a sidenote, I see that PDOStatement has a class variable $queryString that holds the query being used. The values will have to be retrieved from whatever's passed into execute() or bindParam().

First some utility functions for logging:

//removes newlines and extra spaces from print_r output
function str_squeeze($str) {

    if (is_array($str)) {
        $str = print_r($str, true);
    }

    $str = preg_replace('/[(\r)?\n|\t]/', ' ', $str);
    $str = trim(ereg_replace(' +', ' ', $str));
    return $str;
}

function logger($str) {
    //log it somewhere
}

Option 1: wrapper class around PDOStatement

class My_PDO_Utils {

    public static function execute(PDOStatement &$stm, $values = array()) {
        logger("QUERY: " . $stm->queryString . ", values = " . str_squeeze($values)) ;
        return $stm->execute($values) ;

    }

}

Then your code will have to be:

$stm = $db->prepare("SELECT * FROM table2 WHERE id = ?") ;

$res = My_PDO_Utils::execute($stm, array(79)) ;

instead of

$res = $stm->execute(array(79)) ;

Thinking some more about it, you could take it one further:

Option 2: Extend PDO and PDOStatement

If you want to be adventurous you can extend PDOStatement to do the logging for you, and PDO to return your extended PDOStatement class. This will require the least possible refactoring, ie just change new PDO() to new MY_PDO(), but could get tricky in its implementation as you would need to explicitely define any PDOStatement functionality you need in MY_PDOStatement so it gets called properly.

class My_PDO extends PDO {

    public function prepare($sql, $options = array()) {

        //do normal call
        $stm = parent::prepare($sql, $options) ;

        //encapsulate it in your pdostatement wrapper
        $myStm = new My_PDOStatement() ;
        $myStm->stm = $stm ;

        return $myStm ;

    }

}

class My_PDOStatement extends PDOStatement {

    /**
     *
     * @var PDOStatement
     */
    public $stm ;

    public function execute($values) {

        logger("QUERY: " . $this->stm->queryString . ", values = " . str_squeeze($values)) ;
        return $this->stm->execute($values) ;

    }

    public function fetchAll($fetch_style = PDO::FETCH_BOTH, $column_index = 0, $ctor_args = array()) {
        return $this->stm->fetchAll($fetch_style, $column_index, $ctor_args) ;
    }


}

But now your code can be:

$db = new My_PDO($dsn, $user, $pass) ;

$stm = $db->prepare("SELECT * FROM table2 WHERE id = ?") ;

$res = $stm->execute(array(79)) ;    
$row = $stm->fetchAll() ;
Fanis Hatzidakis
  • 5,282
  • 1
  • 33
  • 36
  • Thanks for all the effort! I'm doing something very similar at the moment by extending PDO and PDOStatement (there's a command you can use to tell PDO to use your statement class). As far as I can see, there's two good arguments against this approach: 1. You'd have to update your classes every time the PDO API changed slightly. 2. By recreating the prepared statements (which is really pretty complicated if you consider the various syntaxes, handling references etc.) you'd be duplicating the functionality of PDO when using PDO::ATTR_EMULATE_PREPARES – Michael Clerx Sep 23 '10 at 12:53
  • For my own needs I'm wrapping around PEAR::DB and MDB2 with static functions at the moment to do proper logging, among other things, but am evaluating PDO so this was a good enough excuse to play with that :) (1) is my main concern, needing to maintain the extended classes for all required functionality across upgrades. For (2), it shouldn't need to recreate any statement as it's only wrapping around PDOStatement, unless I'm misunderstanding this point. – Fanis Hatzidakis Sep 23 '10 at 13:03
  • Well you can write queries with '?' syntax and ':var' syntax, and you can add values with bindValue() or bindParam() - one of which uses references. Finally, you can add parameters at the last moment by adding them to the execute function. It'd be quite a bit of work to capture all of this in your query logging function, and apparently PDO can do this already (if you use the emulated prepares functionality) so you'd be rewriting something difficult that's already being done for you at a lower level... – Michael Clerx Sep 23 '10 at 13:13
  • Ah I see what you mean. Right, yes. My dummy code assumes only `execute($values)` would be used, as I'm doing in my own db wrapper. Gotcha. – Fanis Hatzidakis Sep 23 '10 at 13:42
  • `ereg*` functions are deprecated. You should use `preg*` functions: http://stackoverflow.com/questions/6270004/how-can-i-convert-ereg-expressions-to-preg-in-php – Agi Hammerthief Sep 18 '14 at 14:22
1

The best approach in my opinion is using the mysql log to show the last queries ran as getting them directly in php is a drag.

From How to show the last queries executed on MySQL? first answer:

Additionally, for those blessed with MySQL >= 5.1.12:

SET GLOBAL log_output = 'TABLE';
SET GLOBAL general_log = 'ON';

Take a look at the table mysql.general_log If you prefer to output to a file:

SET GLOBAL log_output = "FILE"; which is set by default.
SET GLOBAL general_log_file = "/path/to/your/logfile.log"
SET GLOBAL general_log = 'ON';

I prefer this method because:

you're not editing the my.cnf file and potentially permanently turning on logging you're not fishing around the filesystem looking for the query log - or even worse, distracted by the need for the perfect destination. /var/log /var/data/log /opt /home/mysql_savior/var restarting the server leaves you where you started (log is off) For more information, see MySQL 5.1 Reference Manual - Server System Variables - general_log

Community
  • 1
  • 1
zardilior
  • 2,810
  • 25
  • 30
0

The following static method takes a PDO query template (an SQL query with ? and/or :name placeholders) and interpolates the parameters:

static public function getDebugFullQuery($query, $params = array()){
    if(is_array($params) && count($params)){

        $search = [];
        $replace = [];

        foreach($params as $k => $p){
            $pos = strpos($query, ":{$k}");
            if($pos !== false){
                $query = substr($query, 0, $pos) . "%!-!{$k}!-!%" . substr($query, $pos + strlen($k) + 1);
            }
            else {
                $pos = strpos($query, "?");
                if($pos !== false){
                    $query = substr($query, 0, $pos) . "%!-!{$k}!-!%" . substr($query, $pos + 1);
                }
                else {
                    break;
                }
            }

            $search[] = "%!-!{$k}!-!%";
            $replace[] = "'" . str_replace(array("\r", "\n", "'"), array("\\\\r", "\\\\n", "\\'"), $p) . "'";
        }

        if(count($search)){
            $query = str_replace($search, $replace, $query);
        }
    }

    return $query;
}

As indicated by the method name, you should use this for debugging purposes only.

mindplay.dk
  • 7,085
  • 3
  • 44
  • 54