開発環境で自動でSQLにExplainをかけるコンポーネント バージョンアップ(1.1) PostgreSQL対応

English page is here.


何度もバージョンアップしてすみません。MASA-Pさんのコメントや、あつさんのトラックバックからPostgreSQLでは一部のExplain結果しか出力されない問題がわかりました。ありがとうございます。
PostgreSQLで検証してませんでした、手を抜いてすみません。。。


というわけで、PostgreSQL対応しました。あつさんのコードを参考にしました。
下記のような画面になります。


最新版はここからダウンロードしてください。


PostgreSQL対応の差分だけ解説します
CakePHP のおいしい食べ方で英語で紹介してもらえて、http://planetcakephp.org/にも反映されたので、ソースコードは全部載せておきます

<?php

/**
 * ExplainSqlComponent - Auto execute SQL Explain and set results in the debug mode.
 *
 * Copyright (c) 2009 Yasushi Ichikawa
 *
 * Use this compnent in afterFilter or afterRender.
 *  var $components = array('ExplainSql');
 *  $this->ExplainSql->showExplainSQL( $slowQueryThreshold = 0 );
 *
 * @author Yasushi Ichikawa
 * @version 1.11
 *
 * @lastmodified  Date: 2009-03-10 22:00:00 (JST)
 */
class ExplainSqlComponent extends Object{

	/**
	 *
	 * @var controller object
	 */
	var $_controller;


	/**
	 * set conroller object
	 */
	function startup(& $controller) {
		$this->_controller = $controller;

	}


	/**
	 * Get all SQL query and execute SQL Explain of them without DESCRIBE query.
	 *
	 * if set the $slowQueryThreshold,
	 * execute SQL Explain only slow query which are spent over $slowQueryThreshold seconds.
	 *
	 * @param integer $slowQueryThreshold
	 * @access public
	 */
	function showExplainSQL( $slowQueryThreshold = 0 ){
		$explain_results = array();
		$count = 1;

		if(Configure::read() < 2 ){
			return ;
		}

		if (!class_exists('ConnectionManager')) {
			return ;
		}

		$dbConfigs = ConnectionManager::sourceList();

		foreach ( $dbConfigs as $configName ) {
			$db =& ConnectionManager::getDataSource( $configName );

			if( empty($db->_queriesLog[0]) ){
				continue;
			}

			$driver = $db->config['driver'];

			foreach( $db->_queriesLog as $key => $value ){

				if( preg_match( '/^SELECT /i', $value['query'] ) && 
                                    $value['took'] >= $slowQueryThreshold ){

					$reesults = null;
					$results = $db->query( "Explain ". $value['query'] );


					if( $driver === 'postgres' ){
						//merge QIERY PLAN value
						$query_plan = "<ul>";
						foreach( $results as $postgre_value ){
							$query_plan .= "<li>";
							$query_plan .= $postgre_value[0]['QUERY PLAN'] ;
							$query_plan .= "</li>";
						}
						$query_plan .= "</ul>";
						$results[0][0]['QUERY PLAN'] = $query_plan;

						//change column order
						$results[0][0] = array_merge( array("id" => $count), $results[0][0] );
					}

					$results[0][0]['query'] =  $value['query'];
					$results[0][0]['id'] = $count;
					$explain_results[] = $results[0][0];

					$count++;
				}
			}

		}

		if( !empty( $explain_results[0] ) ){
				$this->_outputHtml( $explain_results );
		}
		return;

	}


	/**
	 * set SQL Explain results on the controller->output.
	 *
	 * @param array $explain_results
	 */
	function _outputHtml( $explain_results ){
		$html_out = '<table>';
		$html_out .= '<tr>';

		//set table column name
		foreach( $explain_results[0] as $titlekey => $titleval ){
			$html_out .= '<th>';
			$html_out .= $titlekey;
			$html_out .= '</th>';
		}
		$html_out .= '</tr>';


		//set results
		foreach($explain_results as $recordnum => $val_arr){
			$html_out .= '<tr>';
			foreach( $val_arr as $key => $value ){
				$html_out .= '<td  style = "text-align: left">';
				$html_out .= $value."&nbsp";
				$html_out .= '</td>';
			}
			$html_out .= '</tr>';
		}

		$html_out .= '</table>';


		$this->_controller->output .= $html_out;
	}

}

?>

$db->configの中に、DB接続情報が入っているので、そこから接続しているDBサーバの値を取得して、PostgreSQLの場合の処理を追加しました。
PostgreSQLのExplain結果の配列構造とMySQLの結果とが異なるので、無理やり合わせました。あつさんの記事を参考に、QUERY PLANのカラムの値をひとつにマージしてリスト表示させてます。
QUERY PLAN1, QUERY PLAN2, QUERY PLAN3のようにカラムを分けようと思いましたが、クエリによってQUERY PLANの数が異なるので、1カラムにまとめました。
それと、idという番号を振ってるだけのカラムの位置が、PostgreSQLの場合は連想配列の後半に来てたので、array_mergeで先頭に来るようにしました。


不具合や、要望があればどしどしお寄せください。よろしくお願いします。


追記:bakeryに投稿しました。そのうち承認されたら公開されると思います(たぶん。。。)