Issues (1686)

sources/ElkArte/QueryAnalysis.php (3 issues)

1
<?php
2
3
/**
4
 * A class to analyse and extract information from queries
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * @version 2.0 dev
11
 *
12
 */
13
14
namespace ElkArte;
15
16
/**
17
 * Class QueryAnalysis
18
 *
19
 * Analyzes the text and execution time of a query
20
 */
21
class QueryAnalysis
22
{
23
	/** @var string The SELECT statement of the query (if any) */
24
	protected $_select;
25
26
	/**
27
	 * Analyze the text of a query and the execution time of a query
28
	 *
29
	 * @param array $query_data array of information regarding the query
30
	 * @return string[] - 'text', 'is_select', 'position_time'
31
	 */
32
	public function extractInfo($query_data)
33
	{
34
		global $txt;
35
36
		// Fix the indentation....
37
		$query_data['q'] = $this->_normalize_query_indent($query_data['q']);
38
39
		// Make the filenames look a bit better.
40
		if (isset($query_data['f']))
41
		{
42
			$query_data['f'] = preg_replace('~^' . preg_quote(BOARDDIR, '~') . '~', '...', $query_data['f']);
43
		}
44
45
		$query_info = array(
46
			'text' => nl2br(str_replace("\t", '&nbsp;&nbsp;&nbsp;', htmlspecialchars($query_data['q'], ENT_COMPAT, 'UTF-8'))),
47
			'is_select' => $this->_is_select_query($query_data['q']),
48
			'position_time' => '',
49
		);
50
51
		if (!empty($query_data['f']) && !empty($query_data['l']))
52
		{
53
			$query_info['position_time'] = sprintf($txt['debug_query_in_line'], $query_data['f'], $query_data['l']);
54
		}
55
56
		if (isset($query_data['s'], $query_data['t'], $txt['debug_query_which_took_at']))
57
		{
58
			$query_info['position_time'] .= sprintf($txt['debug_query_which_took_at'], round($query_data['t'], 8), round($query_data['s'], 8));
59
		}
60
		else
61
		{
62
			$query_info['position_time'] .= sprintf($txt['debug_query_which_took'], round($query_data['t'], 8));
63
		}
64
65
		return $query_info;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query_info returns an array which contains values of type false which are incompatible with the documented value type string.
Loading history...
66
	}
67
68
	/**
69
	 * Fix query indentation
70
	 *
71
	 * @param string $query_data - The query string
72
	 *
73
	 * @return string
74
	 */
75
	protected function _normalize_query_indent($query_data)
76
	{
77
		$query_data = ltrim(str_replace("\r", '', $query_data), "\n");
78
		$query = explode("\n", $query_data);
79
		$min_indent = 0;
80
81
		foreach ($query as $line)
82
		{
83
			preg_match('/^(\t*)/', $line, $temp);
84
			if (strlen($temp[0]) < $min_indent || $min_indent === 0)
85
			{
86
				$min_indent = strlen($temp[0]);
87
			}
88
		}
89
90
		foreach ($query as $l => $dummy)
91
		{
92
			$query[$l] = substr($dummy, $min_indent);
93
		}
94
95
		return implode("\n", $query);
96
	}
97
98
	/**
99
	 * Determines if the query has a SELECT statement and if so it is returned
100
	 *
101
	 * @param string $query_data - The query string
102
	 * @return false|string false if the query doesn't have a SELECT, otherwise
103
	 *                      returns the SELECT itself
104
	 */
105
	protected function _is_select_query($query_data)
106
	{
107
		$is_select_query = substr(trim($query_data), 0, 6) == 'SELECT' || substr(trim($query_data), 0, 4) == 'WITH';
108
		$this->_select = '';
109
110
		if ($is_select_query)
111
		{
112
			$this->_select = $query_data;
113
		}
114
		elseif (preg_match('~^INSERT(?: IGNORE)? INTO \w+(?:\s+\([^)]+\))?\s+(SELECT .+)$~s', trim($query_data), $matches) != 0)
115
		{
116
			$is_select_query = true;
117
			$this->_select = $matches[1];
118
		}
119
		elseif (preg_match('~^CREATE TEMPORARY TABLE .+?(SELECT .+)$~s', trim($query_data), $matches) != 0)
120
		{
121
			$is_select_query = true;
122
			$this->_select = $matches[1];
123
		}
124
125
		// Temporary tables created in earlier queries are not explainable.
126
		if ($is_select_query)
127
		{
128
			foreach (array('tmp_log_search_topics', 'tmp_log_search_messages') as $tmp)
129
			{
130
				if (strpos($this->_select, $tmp) !== false)
131
				{
132
					$is_select_query = false;
133
					break;
134
				}
135
			}
136
		}
137
138
		return $is_select_query;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $is_select_query returns the type boolean which is incompatible with the documented return type false|string.
Loading history...
139
	}
140
141
	/**
142
	 * Does the EXPLAIN of a query
143
	 *
144
	 * @return string[] an array with the results of the EXPLAIN with two
145
	 * possible structures depending if the EXPLAIN is successful or fails.
146
	 *  - If successful:
147
	 *      array(
148
	 *        'headers' => array( ..list of headers.. )
149
	 *        'body' => array(
150
	 *              array( ..cells.. ) // one row
151
	 *        )
152
	 *      )
153
	 *  - If the EXPLAIN fails:
154
	 *   array(
155
	 *        'is_error' => true
156
	 *        'error_text' => the error message
157
	 *     )
158
	 *
159
	 * @throws \ElkArte\Exceptions\Exception
160
	 */
161
	public function doExplain()
162
	{
163
		if (empty($this->_select))
164
		{
165
			return array();
166
		}
167
168
		// db work...
169
		$db = database();
170
171
		$result = $db->query('', '
172
			EXPLAIN ' . $this->_select,
173
			array()
174
		);
175
176
		if ($result->hasResults() === false)
177
		{
178
			$explain = array(
179
				'is_error' => true,
180
				'error_text' => $db->last_error(),
181
			);
182
		}
183
		else
184
		{
185
			$row = $result->fetch_assoc();
186
			$explain = array(
187
				'headers' => array_keys($row),
188
				'body' => array()
189
			);
190
191
			$result->data_seek(0);
192
			while (($row = $result->fetch_assoc()))
193
			{
194
				$explain['body'][] = $row;
195
			}
196
		}
197
198
		return $explain;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $explain returns an array which contains values of type array|true which are incompatible with the documented value type string.
Loading history...
199
	}
200
}
201