[php] PDO 준비 명령문에서 원시 SQL 쿼리 문자열 가져 오기

준비된 명령문에서 PDOStatement :: execute ()를 호출 할 때 원시 SQL 문자열을 실행하는 방법이 있습니까? 디버깅 목적으로 매우 유용합니다.



답변

매개 변수 값을 보간하여 최종 SQL 쿼리를 원한다고 가정합니다. 디버깅에 유용하지만 준비된 명령문이 작동하는 방식은 아니라는 것을 알고 있습니다. 매개 변수는 클라이언트 측에서 준비된 명령문과 결합되지 않으므로 PDO는 해당 매개 변수와 결합 된 쿼리 문자열에 액세스 할 수 없습니다.

SQL 문은 준비 ()를 수행 할 때 데이터베이스 서버로 전송되고 매개 변수는 execute ()를 수행 할 때 별도로 전송됩니다. MySQL의 일반 쿼리 로그에는 execute () 후에 보간 된 값과 함께 최종 SQL이 표시됩니다. 아래는 일반적인 쿼리 로그에서 발췌 한 것입니다. PDO가 아닌 mysql CLI에서 쿼리를 실행했지만 원칙은 동일합니다.

081016 16:51:28 2 Query       prepare s1 from 'select * from foo where i = ?'
                2 Prepare     [2] select * from foo where i = ?
081016 16:51:39 2 Query       set @a =1
081016 16:51:47 2 Query       execute s1 using @a
                2 Execute     [2] select * from foo where i = 1

PDO 속성 PDO :: ATTR_EMULATE_PREPARES를 설정하면 원하는 것을 얻을 수도 있습니다. 이 모드에서 PDO는 매개 변수를 SQL 쿼리에 보간하고 실행할 때 전체 쿼리를 보냅니다. 이것은 실제 준비된 쿼리가 아닙니다. execute () 전에 변수를 SQL 문자열에 보간하여 준비된 쿼리의 이점을 피할 수 있습니다.


@afilina의 의견 :

아니요, 텍스트 SQL 쿼리는 실행 중에 매개 변수와 결합 되지 않습니다 . 따라서 PDO가 당신에게 보여줄 것이 없습니다.

내부적으로 PDO :: ATTR_EMULATE_PREPARES를 사용하는 경우 PDO는 준비 및 실행을 수행하기 전에 SQL 쿼리 사본을 작성하고 매개 변수 값을 보간합니다. 그러나 PDO는이 수정 된 SQL 쿼리를 공개하지 않습니다.

PDOStatement 오브젝트에는 $ queryString 특성이 있지만 이는 PDOStatement의 생성자에서만 설정되며 쿼리를 매개 변수로 다시 작성할 때 업데이트되지 않습니다.

PDO가 다시 작성된 쿼리를 노출하도록 요청하는 것이 합리적인 기능 요청입니다. 그러나 PDO :: ATTR_EMULATE_PREPARES를 사용하지 않으면 “완전한”쿼리를 제공하지 않습니다.

이것이 MySQL 서버의 일반 쿼리 로그를 사용하는 것보다 위의 해결 방법을 보여주는 이유입니다.이 경우 매개 변수 자리 표시자가있는 준비된 쿼리조차도 서버에서 다시 작성되고 매개 변수 값이 쿼리 문자열로 다시 채워지기 때문입니다. 그러나 이것은 쿼리 실행 중이 아니라 로깅 중에 만 수행됩니다.


답변

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public static function interpolateQuery($query, $params) {
    $keys = array();

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }
    }

    $query = preg_replace($keys, $params, $query, 1, $count);

    #trigger_error('replaced '.$count.' keys');

    return $query;
}


답변

WHERE IN (?)과 같은 명령문에 대한 배열 출력 처리를 포함하도록 메소드를 수정했습니다.

업데이트 : 방금 NULL 값 검사와 $ params 복제를 추가하여 실제 $ param 값이 수정되지 않았습니다.

대단한 일 webwebguy와 감사합니다!

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from 
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_string($value))
            $values[$key] = "'" . $value . "'";

        if (is_array($value))
            $values[$key] = "'" . implode("','", $value) . "'";

        if (is_null($value))
            $values[$key] = 'NULL';
    }

    $query = preg_replace($keys, $values, $query);

    return $query;
}


답변

아마 조금 늦었지만 지금은 PDOStatement::debugDumpParams

준비된 명령문에 포함 된 정보를 출력에 직접 덤프합니다. 사용중인 SQL 쿼리, 사용 된 매개 변수 수 (Params), 매개 변수 목록, 이름, 유형 (paramtype)을 정수로, 키 이름 또는 위치 및 쿼리 위치 (이 경우 PDO 드라이버에서 지원합니다. 그렇지 않으면 -1)입니다.

공식 PHP 문서 에서 더 많은 것을 찾을 수 있습니다

예:

<?php
/* Execute a prepared statement by binding PHP variables */
$calories = 150;
$colour = 'red';
$sth = $dbh->prepare('SELECT name, colour, calories
    FROM fruit
    WHERE calories < :calories AND colour = :colour');
$sth->bindParam(':calories', $calories, PDO::PARAM_INT);
$sth->bindValue(':colour', $colour, PDO::PARAM_STR, 12);
$sth->execute();

$sth->debugDumpParams();

?>


답변

해결책은 자발적으로 쿼리에 오류를 넣고 오류 메시지를 인쇄하는 것입니다.

//Connection to the database
$co = new PDO('mysql:dbname=myDB;host=localhost','root','');
//We allow to print the errors whenever there is one
$co->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

//We create our prepared statement
$stmt = $co->prepare("ELECT * FROM Person WHERE age=:age"); //I removed the 'S' of 'SELECT'
$stmt->bindValue(':age','18',PDO::PARAM_STR);
try {
    $stmt->execute();
} catch (PDOException $e) {
    echo $e->getMessage();
}

표준 출력 :

SQLSTATE [42000] : 구문 오류 또는 액세스 위반 : 1 행 에서 ‘ELECT * FROM Person WHERE age = 18’ 근처 […]

쿼리의 처음 80 자만 인쇄한다는 점에 유의해야합니다.


답변

Mike가 코드에 조금 더 추가했습니다. 작은 따옴표를 추가하려면 값을 걸어보세요.

/**
 * Replaces any parameter placeholders in a query with the value of that
 * parameter. Useful for debugging. Assumes anonymous parameters from
 * $params are are in the same order as specified in $query
 *
 * @param string $query The sql query with parameter placeholders
 * @param array $params The array of substitution parameters
 * @return string The interpolated query
 */
public function interpolateQuery($query, $params) {
    $keys = array();
    $values = $params;

    # build a regular expression for each parameter
    foreach ($params as $key => $value) {
        if (is_string($key)) {
            $keys[] = '/:'.$key.'/';
        } else {
            $keys[] = '/[?]/';
        }

        if (is_array($value))
            $values[$key] = implode(',', $value);

        if (is_null($value))
            $values[$key] = 'NULL';
    }
    // Walk the array to see if we can add single-quotes to strings
    array_walk($values, create_function('&$v, $k', 'if (!is_numeric($v) && $v!="NULL") $v = "\'".$v."\'";'));

    $query = preg_replace($keys, $values, $query, 1, $count);

    return $query;
}


답변

PDOStatement에는 $ queryString 공용 속성이 있습니다. 원하는 것이어야합니다.

PDOStatement에 문서화되지 않은 debugDumpParams () 메소드가 있음을 알았습니다.