Skip to content

Commit

Permalink
Merge pull request box#50 from kormoc/17-QDUI-Rewriter
Browse files Browse the repository at this point in the history
Use QDUI Query Rewriter which supports a much wider set of queries to ex...
  • Loading branch information
gtowey committed Oct 3, 2012
2 parents 4b1d9f6 + 1613777 commit c7b0957
Show file tree
Hide file tree
Showing 3 changed files with 376 additions and 11 deletions.
24 changes: 13 additions & 11 deletions lib/QueryExplain.php
Expand Up @@ -43,6 +43,8 @@
*/

require "QueryTableParser.php";
require_once "QueryRewrite.php";

class QueryExplain {

private $get_connection_func;
Expand Down Expand Up @@ -143,20 +145,18 @@ public function explain() {
if (!isset($this->mysqli)) {
return null;
}

if (!preg_match("/^\s*\(?\s*(EXPLAIN)?\s*SELECT/i", $this->query)) {
return null;
}


try {
$result = $this->explain_query($this->query);

if ($this->mysqli->errno) {
return $this->mysqli->error . " (" . $this->mysqli->errno . ")";
}

if (!$result) {
return "unknown error getting explain plan\n";
}

return $this->result_as_table($result);
} catch (Exception $e) {
return $e->getMessage();
Expand All @@ -177,7 +177,7 @@ private function connect() {
throw new Exception("Missing field {$r}");
}
}

try {
$this->mysqli = new mysqli();
$this->mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, self::$CONNECT_TIMEOUT);
Expand All @@ -202,11 +202,13 @@ private function connect() {
* @return MySQLi_Result the result handle
*/
private function explain_query() {
if (preg_match("/^\s*EXPLAIN/i", $this->query))
{
return $this->mysqli->query($this->query);
}
return $this->mysqli->query("EXPLAIN " . $this->query);
$Query = new QueryRewrite($this->query);
$explain = $Query->asExplain();

if (is_null($explain))
return null;

return $this->mysqli->query($explain);
}

/**
Expand Down
117 changes: 117 additions & 0 deletions lib/QueryRewrite.php
@@ -0,0 +1,117 @@
<?php

class QueryRewrite{
public $sql = null;
public $type = 0;

const UNKNOWN = 0;
const SELECT = 1;
const DELETE = 2;
const INSERT = 3;
const UPDATE = 4;
const ALTER = 5;
const DROP = 6;
const CREATE = 7;
const DELETEMULTI = 8;
const UNION = 9;
const INSERTSELECT = 10;

// Valid Table Regex
const TABLEREF = '`?[A-Za-z0-9_]+`?(\.`?[A-Za-z0-9_]+`?)?';
// Comment Regexs
const COMMENTS_C = '/\s*\/\*.*?\*\/\s*/';
const COMMENTS_HASH = '/#.*$/';
const COMMENTS_SQL = '/--\s+.*$/';

public function __construct($sql = null) {
$this->setQuery($sql);
}

public function setQuery($sql = null) {
$this->type = self::UNKNOWN;
$this->sql = $sql;
if (is_null($this->sql))
return;
// Remove comments
$this->sql = preg_replace(self::COMMENTS_C, '', $this->sql);
$this->sql = preg_replace(self::COMMENTS_HASH, '', $this->sql);
$this->sql = preg_replace(self::COMMENTS_SQL, '', $this->sql);
// Remove whitespace
$this->sql = trim($this->sql);
$this->sql = str_replace("\n", " ", $this->sql);
$this->figureOutType();
}

private function figureOutType(){
if (preg_match('/^SELECT\s/i', $this->sql))
$this->type = self::SELECT;
elseif (preg_match('/^DELETE\s+FROM\s/i', $this->sql))
$this->type = self::DELETE;
elseif (preg_match('/^DELETE\s+'.self::TABLEREF.'\s+FROM\s/i', $this->sql))
$this->type = self::DELETEMULTI;
elseif (preg_match('/^INSERT\s+INTO\s'.self::TABLEREF.'[\s\(]*SELECT\s+/i', $this->sql))
$this->type = self::INSERTSELECT;
elseif (preg_match('/^INSERT\s+INTO\s/i', $this->sql))
$this->type = self::INSERT;
elseif (preg_match('/^(.*)\s+UNION\s+(.*)$/i', $this->sql))
$this->type = self::UNION;
elseif (preg_match('/^UPDATE\s/i', $this->sql))
$this->type = self::UPDATE;
elseif (preg_match('/^ALTER\s/i', $this->sql))
$this->type = self::ALTER;
elseif (preg_match('/^CREATE\s/i', $this->sql))
$this->type = self::CREATE;
elseif (preg_match('/^DROP\s/i', $this->sql))
$this->type = self::DROP;
else
$this->type = self::UNKNOWN;
}

public function getType() {
return $this->type;
}

public function toSelect() {
$select = '';
switch ($this->type) {
case self::SELECT:
case self::UNION:
$select = $this->sql;
break;
case self::DELETE:
$select = preg_replace('/^DELETE\s+FROM\s/i', 'SELECT 0 FROM ', $this->sql);
break;
case self::DELETEMULTI:
$select = preg_replace('/^DELETE\s+'.self::TABLEREF.'\s+FROM\s/i', 'SELECT 0 FROM ', $this->sql);
break;
case self::UPDATE:
preg_match('/^UPDATE\s+(.*)\s+SET\s+(.*)\s+WHERE\s+(.*)$/i', $this->sql, $subpatterns);
$select = "SELECT {$subpatterns[2]} FROM {$subpatterns[1]} WHERE {$subpatterns[3]}";
break;
case self::INSERTSELECT:
if (preg_match('/\(\s*(SELECT\s+.*)\)/', $this->sql, $subpatterns))
$select = trim("{$subpatterns[1]}");
else
preg_match('/^INSERT\s+INTO\s+\w+\s+(SELECT.*)/i', $this->sql, $subpatterns);
$select = trim("{$subpatterns[1]}");
break;
default:
return null;
}
return trim($select);
}

public function asExplain() {
$sql = $this->toSelect();
if (is_null($sql))
return null;
return "EXPLAIN $sql";
}

public function asExtendedExplain() {
$sql = $this->toSelect();
if (is_null($sql))
return null;
return "EXPLAIN EXTENDED $sql";
}
}

0 comments on commit c7b0957

Please sign in to comment.