development

교리-준비된 진술뿐만 아니라 실제 SQL을 인쇄하는 방법?

big-blog 2020. 6. 11. 07:46
반응형

교리-준비된 진술뿐만 아니라 실제 SQL을 인쇄하는 방법?


우리는 PHP ORM 인 Doctrine을 사용하고 있습니다. 다음과 같은 쿼리를 작성 중입니다.

$q = Doctrine_Query::create()->select('id')->from('MyTable');

그런 다음 함수에서 다음과 같이 다양한 where 절과 사물을 적절하게 추가합니다.

$q->where('normalisedname = ? OR name = ?', array($string, $originalString));

나중에 execute()쿼리 개체를 가져 오기 전에 원시 SQL을 인쇄하여 검사하고 다음을 수행하려고합니다.

$q->getSQLQuery();

그러나 전체 쿼리가 아닌 준비된 명령문 만 인쇄합니다. 나는 그것이 MySQL로 무엇을 보내고 있는지보고 싶지만 대신을 포함하여 준비된 진술을 인쇄하고 ?있습니다. '전체'쿼리를 볼 수있는 방법이 있습니까?


Doctrine은 데이터베이스 서버에 "실제 SQL 쿼리"를 보내지 않습니다. 실제로 준비된 명령문을 사용하고 있습니다.

  • 진술서를 준비하여 보내십시오 (이것이 반환하는 것입니다 $query->getSql())
  • 그런 다음 매개 변수를 보내십시오 (로 반환 $query->getParameters())
  • 그리고 준비된 문장을 실행

이는 PHP 측에 "실제"SQL 쿼리가 없다는 것을 의미하므로 Doctrine은이를 표시 할 수 없습니다.


실제 예 :

$qb = $this->createQueryBuilder('a');
$query=$qb->getQuery();
// SHOW SQL: 
echo $query->getSQL(); 
// Show Parameters: 
echo $query->getParameters();

mysql에 모든 쿼리를 기록하면 앱이 실행 한 쿼리를 확인할 수 있습니다.

http://dev.mysql.com/doc/refman/5.1/en/query-log.html

당신이 찾고있는 것뿐만 아니라 더 많은 쿼리가있을 것입니다.

그러나 일반적으로 ->getSql();작동

편집하다:

내가 사용하는 모든 mysql 쿼리를 보려면

sudo vim /etc/mysql/my.cnf 

그 두 줄을 추가하십시오 :

general_log = on
general_log_file = /tmp/mysql.log

mysql을 다시 시작하십시오.


정확히 이것을 수행하는 Doctrine2 Logger를 만들었습니다. Doctrine 2 자체 데이터 형식 대화를 사용하여 매개 변수가있는 SQL 쿼리를 "수화"합니다.

<?php


namespace Drsm\Doctrine\DBAL\Logging;
use Doctrine\DBAL\Logging\SQLLogger,
    Doctrine\DBAL\Types\Type,
    Doctrine\DBAL\Platforms\AbstractPlatform;
/**
 * A SQL logger that logs to the standard output and
 * subtitutes params to get a ready to execute SQL sentence

 * @author  dsamblas@gmail.com
 */
class EchoWriteSQLWithoutParamsLogger implements SQLLogger

{
    const QUERY_TYPE_SELECT="SELECT";
    const QUERY_TYPE_UPDATE="UPDATE";
    const QUERY_TYPE_INSERT="INSERT";
    const QUERY_TYPE_DELETE="DELETE";
    const QUERY_TYPE_CREATE="CREATE";
    const QUERY_TYPE_ALTER="ALTER";

    private $dbPlatform;
    private $loggedQueryTypes;
    public function __construct(AbstractPlatform $dbPlatform, array $loggedQueryTypes=array()){
        $this->dbPlatform=$dbPlatform;
        $this->loggedQueryTypes=$loggedQueryTypes;
    }
    /**
     * {@inheritdoc}
     */
    public function startQuery($sql, array $params = null, array $types = null)

    {
        if($this->isLoggable($sql)){
            if(!empty($params)){
                foreach ($params as $key=>$param) {
                    $type=Type::getType($types[$key]);
                    $value=$type->convertToDatabaseValue($param,$this->dbPlatform);
                    $sql = join(var_export($value, true), explode('?', $sql, 2));
                }

            }
            echo $sql . " ;".PHP_EOL;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function stopQuery()
    {

    }
    private function isLoggable($sql){
        if (empty($this->loggedQueryTypes)) return true;
        foreach($this->loggedQueryTypes as $validType){
            if (strpos($sql, $validType) === 0) return true;
        }
        return false;
    }
}

사용 예 :; 다음 코드는 $ em Entity Manager로 생성 된 모든 INSERT, UPDATE, DELETE SQL 문장을 표준 출력에 반영합니다.

/**@var  \Doctrine\ORM\EntityManager $em */
$em->getConnection()
                ->getConfiguration()
                ->setSQLLogger(
                    new EchoWriteSQLWithoutParamsLogger(
                        $em->getConnection()->getDatabasePlatform(),
                        array(
                            EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_UPDATE,
                            EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_INSERT,
                            EchoWriteSQLWithoutParamsLogger::QUERY_TYPE_DELETE
                        )
                    )
                );

다른 실제 쿼리는 없으며 준비된 명령문이 작동하는 방식입니다. 값은 응용 프로그램 계층이 아닌 데이터베이스 서버에 바인딩됩니다.

이 질문에 대한 내 답변보기 : PDO가있는 PHP에서 최종 SQL 매개 변수화 쿼리를 확인하는 방법은 무엇입니까?

(편의를 위해 여기에 반복됨)

매개 변수 값이있는 준비된 명령문을 사용하는 것은 단순히 SQL 문자열을 동적으로 작성하는 다른 방법이 아닙니다. 데이터베이스에서 준비된 명령문을 작성한 후 매개 변수 값만 보내십시오.

그래서 아마 데이터베이스로 전송하면됩니다 PREPARE ...다음, SET ...마지막으로EXECUTE ....

SELECT * FROM ...같은 쿼리가 실제로 데이터베이스로 전송되지 않았기 때문에 동등한 결과를 생성하더라도, 와 같은 SQL 문자열을 얻을 수 없습니다.


getSqlQuery() 기술적으로 전체 SQL 명령을 표시하지만 매개 변수를 볼 때 훨씬 유용합니다.

echo $q->getSqlQuery();
foreach ($q->getFlattenedParams() as $index => $param)
  echo "$index => $param";

이 패턴의 재사용 성을 높이기 위해 Doctrine Query Object의 Raw SQL 주석설명 된 멋진 접근 방식이 있습니다.


내 해결책 :

 /**
 * Get SQL from query
 * 
 * @author Yosef Kaminskyi 
 * @param QueryBilderDql $query
 * @return int
 */
public function getFullSQL($query)
{
    $sql = $query->getSql();
    $paramsList = $this->getListParamsByDql($query->getDql());
    $paramsArr =$this->getParamsArray($query->getParameters());
    $fullSql='';
    for($i=0;$i<strlen($sql);$i++){
        if($sql[$i]=='?'){
            $nameParam=array_shift($paramsList);

            if(is_string ($paramsArr[$nameParam])){
                $fullSql.= '"'.addslashes($paramsArr[$nameParam]).'"';
             }
            elseif(is_array($paramsArr[$nameParam])){
                $sqlArr='';
                foreach ($paramsArr[$nameParam] as $var){
                    if(!empty($sqlArr))
                        $sqlArr.=',';

                    if(is_string($var)){
                        $sqlArr.='"'.addslashes($var).'"';
                    }else
                        $sqlArr.=$var;
                }
                $fullSql.=$sqlArr;
            }elseif(is_object($paramsArr[$nameParam])){
                switch(get_class($paramsArr[$nameParam])){
                    case 'DateTime':
                             $fullSql.= "'".$paramsArr[$nameParam]->format('Y-m-d H:i:s')."'";
                          break;
                    default:
                        $fullSql.= $paramsArr[$nameParam]->getId();
                }

            }
            else                     
                $fullSql.= $paramsArr[$nameParam];

        }  else {
            $fullSql.=$sql[$i];
        }
    }
    return $fullSql;
}

 /**
 * Get query params list
 * 
 * @author Yosef Kaminskyi <yosefk@spotoption.com>
 * @param  Doctrine\ORM\Query\Parameter $paramObj
 * @return int
 */
protected function getParamsArray($paramObj)
{
    $parameters=array();
    foreach ($paramObj as $val){
        /* @var $val Doctrine\ORM\Query\Parameter */
        $parameters[$val->getName()]=$val->getValue();
    }

    return $parameters;
}
 public function getListParamsByDql($dql)
{
    $parsedDql = preg_split("/:/", $dql);
    $length = count($parsedDql);
    $parmeters = array();
    for($i=1;$i<$length;$i++){
        if(ctype_alpha($parsedDql[$i][0])){
            $param = (preg_split("/[' ' )]/", $parsedDql[$i]));
            $parmeters[] = $param[0];
        }
    }

    return $parmeters;}

사용 예 :

$query = $this->_entityRepository->createQueryBuilder('item');
$query->leftJoin('item.receptionUser','users');
$query->where('item.customerid = :customer')->setParameter('customer',$customer)
->andWhere('item.paymentmethod = :paymethod')->setParameter('paymethod',"Bonus");
echo $this->getFullSQL($query->getQuery());

다음 방법을 사용하여 SQL 매개 변수에 쉽게 액세스 할 수 있습니다.

   $result = $qb->getQuery()->getSQL();

   $param_values = '';  
   $col_names = '';   

   foreach ($result->getParameters() as $index => $param){              
            $param_values .= $param->getValue().',';
            $col_names .= $param->getName().',';
   } 

   //echo rtrim($param_values,',');
   //echo rtrim($col_names,',');    

따라서 $param_valuesand 를 인쇄 $col_names하면 sql 및 해당 열 이름을 통과하는 매개 변수 값을 얻을 수 있습니다.

참고 : $param배열을 반환하면 IN (:?)일반적으로 내부 매개 변수 가 중첩 배열이므로 반복을 반복해야합니다 .

그 동안 다른 방법을 찾았다면 우리와 공유 할 수있을만큼 친절하십시오. :)

감사합니다!


보다 명확한 해결책 :

 /**
 * Get string query 
 * 
 * @param Doctrine_Query $query
 * @return string
 */
public function getDqlWithParams(Doctrine_Query $query){
    $vals = $query->getFlattenedParams();
    $sql = $query->getDql();
    $sql = str_replace('?', '%s', $sql);
    return vsprintf($sql, $vals);
}

당신이 사용할 수있는 :

$query->getSQL();

MySQL을 사용하는 경우 Workbench를 사용하여 실행중인 SQL 문을 볼 수 있습니다. 다음을 사용하여 mysql에서 실행중인 쿼리를 볼 수도 있습니다.

 SHOW FULL PROCESSLIST \G

어쩌면 누군가에게 유용 할 수 있습니다.

// Printing the SQL with real values
$vals = $query->getFlattenedParams();
foreach(explode('?', $query->getSqlQuery()) as $i => $part) {
    $sql = (isset($sql) ? $sql : null) . $part;
    if (isset($vals[$i])) $sql .= $vals[$i];
}

echo $sql;

Solution:1
====================================================================================

function showQuery($query)
{
    return sprintf(str_replace('?', '%s', $query->getSql()), $query->getParams());
}

// call function  
echo showQuery($doctrineQuery);

Solution:2
====================================================================================

function showQuery($query)
{
    // define vars              
    $output    = NULL;
    $out_query = $query->getSql();
    $out_param = $query->getParams();

    // replace params
   for($i=0; $i<strlen($out_query); $i++) {
       $output .= ( strpos($out_query[$i], '?') !== FALSE ) ? "'" .str_replace('?', array_shift($out_param), $out_query[$i]). "'" : $out_query[$i];
   }

   // output
   return sprintf("%s", $output);
}

// call function  
echo showQuery($doctrineQueryObject);

삽입 된 매개 변수로 쿼리를 기록 할 수있는 간단한 로거를 작성했습니다. 설치:

composer require cmyker/doctrine-sql-logger:dev-master

용법:

$connection = $this->getEntityManager()->getConnection(); 
$logger = new \Cmyker\DoctrineSqlLogger\Logger($connection);
$connection->getConfiguration()->setSQLLogger($logger);
//some query here
echo $logger->lastQuery;

$sql = $query->getSQL();

$parameters = [];
    foreach ($query->getParameters() as $parameter) {
        $parameters[] = $parameter->getValue();
    }

$result = $connection->executeQuery($sql, $parameters)
        ->fetchAll();

매개 변수가 '2019-01-01'과 같은 날짜 문자열이고 IN을 사용하여 전달 된 배열이있는 경우 작동하도록 수정 된 @dsamblas 함수

$qb->expr()->in('ps.code', ':activeCodes'),

. So do everything what dsamblas wrote, but replace startQuery with this one or see the differences and add my code. (in case he modified something in his function and my version does not have modifications).

public function startQuery($sql, array $params = null, array $types = null)

{
    if($this->isLoggable($sql)){
        if(!empty($params)){
            foreach ($params as $key=>$param) {

                try {
                    $type=Type::getType($types[$key]);
                    $value=$type->convertToDatabaseValue($param,$this->dbPlatform);
                } catch (Exception $e) {
                    if (is_array($param)) {
                        // connect arrays like ("A", "R", "C") for SQL IN
                        $value = '"' . implode('","', $param) . '"';
                    } else {
                        $value = $param; // case when there are date strings
                    }
                }

                $sql = join(var_export($value, true), explode('?', $sql, 2));
            }

        }
        echo $sql . " ;".PHP_EOL;
    }
}

Did not test much.


TL;DR

$qb = ... // your query builder
$query = $qb->getQuery();
// temporarily enable logging for your query (will also work in prod env)
$conf = $query->getEntityManager()->getConnection()->getConfiguration();
$backupLogger = $conf->getSQLLogger();
$logger = new \Doctrine\DBAL\Logging\DebugStack();
$conf->setSQLLogger($logger);
// execute query
$res = $query->getResult();
$conf->setSQLLogger($backupLogger); //restore logger for other queries
$params = [
  'query' => array_pop($logger->queries) //extract query log details
  //your other twig params here...
]
return $params; //send this to your twig template...

in your twig files, user Doctrine's twig helpers filters:

// show raw query:
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)
// highlighted
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)|doctrine_pretty_query(highlight_only = true) }}
// highlighted and formatted (i.e. with tabs and newlines)
{{ (query.sql ~ ';')|doctrine_replace_query_parameters(query.params)|doctrine_pretty_query }}

Explanation:

The other answers mentioning that Prepared statement are actually "real queries" are right, but they don't answer the obvious asker's expectation... Every developer wants to display a "runnable query" for debugging (or to display it to the user).

So, I looked into Symfony profiler's source to see how they do it. The Doctrine part is Doctrine's responsibility so they made a doctrine-bundle to integrate with Symfony. Having a look at the doctrine-bundle/Resources/views/Collector/db.html.twig file, you will find out how they do it (this might change across versions). Interestingly, they created twig filters that we can reuse (see above).

For everything to work we need to enable Logging for our query. There are multiple ways to do this and here I use DebugStack which allows to log queries without actually printing them. This also ensure that this will work in production mode if this is what you need...

If you need further formatting, you will see that they include some CSS in a style tag, so simply "steal" it ^^:

.highlight pre { margin: 0; white-space: pre-wrap; }
.highlight .keyword   { color: #8959A8; font-weight: bold; }
.highlight .word      { color: #222222; }
.highlight .variable  { color: #916319; }
.highlight .symbol    { color: #222222; }
.highlight .comment   { color: #999999; }
.highlight .backtick  { color: #718C00; }
.highlight .string    { color: #718C00; }
.highlight .number    { color: #F5871F; font-weight: bold; }
.highlight .error     { color: #C82829; }

Hope, this will help ;-)


To print out an SQL query in Doctrine, use:

$query->getResult()->getSql();

참고URL : https://stackoverflow.com/questions/2095394/doctrine-how-to-print-out-the-real-sql-not-just-the-prepared-statement

반응형