1 | <?php |
||
8 | class Helper |
||
9 | { |
||
10 | /** |
||
11 | * KEYWORDS1. |
||
12 | * |
||
13 | * @var string |
||
14 | */ |
||
15 | const KEYWORDS1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE'; |
||
16 | |||
17 | /** |
||
18 | * KEYWORDS2. |
||
19 | * |
||
20 | * @var string |
||
21 | */ |
||
22 | const KEYWORDS2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE'; |
||
23 | |||
24 | /** |
||
25 | * Returns syntax highlighted SQL command. |
||
26 | * |
||
27 | * |
||
28 | * @param string $sql |
||
29 | * @param array $bindings |
||
30 | * @param \PDO $pdo |
||
31 | * @return string |
||
32 | */ |
||
33 | 4 | public static function hightlight($sql, array $bindings = [], PDO $pdo = null) |
|
34 | { |
||
35 | // insert new lines |
||
36 | 4 | $sql = " $sql "; |
|
37 | 4 | $sql = preg_replace('#(?<=[\\s,(])('.static::KEYWORDS1.')(?=[\\s,)])#i', "\n\$1", $sql); |
|
38 | |||
39 | // reduce spaces |
||
40 | 4 | $sql = preg_replace('#[ \t]{2,}#', ' ', $sql); |
|
41 | |||
42 | // syntax highlight |
||
43 | 4 | $sql = htmlspecialchars($sql, ENT_IGNORE, 'UTF-8'); |
|
44 | 4 | $sql = preg_replace_callback('#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])('.static::KEYWORDS1.')(?=[\\s,)])|(?<=[\\s,(=])('.static::KEYWORDS2.')(?=[\\s,)=])#is', function ($matches) { |
|
45 | 4 | if (! empty($matches[1])) { // comment |
|
46 | 1 | return '<em style="color:gray">'.$matches[1].'</em>'; |
|
47 | 4 | } elseif (! empty($matches[2])) { // error |
|
48 | 1 | return '<strong style="color:red">'.$matches[2].'</strong>'; |
|
49 | 4 | } elseif (! empty($matches[3])) { // most important keywords |
|
50 | 4 | return '<strong style="color:blue; text-transform: uppercase;">'.$matches[3].'</strong>'; |
|
51 | 1 | } elseif (! empty($matches[4])) { // other keywords |
|
52 | 1 | return '<strong style="color:green">'.$matches[4].'</strong>'; |
|
53 | } |
||
54 | 4 | }, $sql); |
|
55 | |||
56 | 4 | $bindings = array_map(function ($binding) use ($pdo) { |
|
57 | 3 | if (is_array($binding) === true) { |
|
58 | 1 | $binding = implode(', ', array_map(function ($value) { |
|
59 | 1 | return is_string($value) === true ? htmlspecialchars('\''.$value.'\'', ENT_NOQUOTES, 'UTF-8') : $value; |
|
60 | 1 | }, $binding)); |
|
61 | |||
62 | 1 | return htmlspecialchars('('.$binding.')', ENT_NOQUOTES, 'UTF-8'); |
|
63 | } |
||
64 | |||
65 | 3 | if (is_string($binding) === true && (preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $binding) || preg_last_error())) { |
|
66 | return '<i title="Length '.strlen($binding).' bytes"><binary></i>'; |
||
67 | } |
||
68 | |||
69 | 3 | if (is_string($binding) === true) { |
|
70 | 3 | $text = htmlspecialchars($pdo ? $pdo->quote($binding) : '\''.$binding.'\'', ENT_NOQUOTES, 'UTF-8'); |
|
71 | |||
72 | 3 | return '<span title="Length '.strlen($text).' characters">'.$text.'</span>'; |
|
73 | } |
||
74 | |||
75 | 1 | if (is_resource($binding) === true) { |
|
76 | 1 | $type = get_resource_type($binding); |
|
77 | 1 | if ($type === 'stream') { |
|
78 | 1 | $info = stream_get_meta_data($binding); |
|
79 | } |
||
80 | |||
81 | 1 | return '<i'.(isset($info['uri']) ? ' title="'.htmlspecialchars($info['uri'], ENT_NOQUOTES, 'UTF-8').'"' : null) |
|
82 | 1 | .'><'.htmlspecialchars($type, ENT_NOQUOTES, 'UTF-8').' resource></i>'; |
|
83 | } |
||
84 | |||
85 | 1 | if ($binding instanceof DateTime) { |
|
86 | 1 | return htmlspecialchars('\''.$binding->format('Y-m-d H:i:s').'\'', ENT_NOQUOTES, 'UTF-8'); |
|
87 | } |
||
88 | |||
89 | return htmlspecialchars($binding, ENT_NOQUOTES, 'UTF-8'); |
||
90 | 4 | }, $bindings); |
|
91 | 4 | $sql = str_replace(['%', '?'], ['%%', '%s'], $sql); |
|
92 | |||
93 | 4 | return '<div><code>'.nl2br(trim(vsprintf($sql, $bindings))).'</code></div>'; |
|
94 | } |
||
95 | |||
96 | /** |
||
97 | * perform quer analysis hint. |
||
98 | * |
||
99 | * @param string $sql |
||
100 | * @param string $version |
||
101 | * @param float $driver |
||
102 | * @return array |
||
103 | */ |
||
104 | 2 | public static function performQueryAnalysis($sql, $version = null, $driver = null) |
|
105 | { |
||
106 | 2 | $hints = []; |
|
107 | 2 | if (preg_match('/^\\s*SELECT\\s*`?[a-zA-Z0-9]*`?\\.?\\*/i', $sql)) { |
|
108 | 2 | $hints[] = 'Use <code>SELECT *</code> only if you need all columns from table'; |
|
109 | } |
||
110 | 2 | if (preg_match('/ORDER BY RAND()/i', $sql)) { |
|
111 | 1 | $hints[] = '<code>ORDER BY RAND()</code> is slow, try to avoid if you can. |
|
112 | You can <a href="http://stackoverflow.com/questions/2663710/how-does-mysqls-order-by-rand-work">read this</a> |
||
113 | or <a href="http://stackoverflow.com/questions/1244555/how-can-i-optimize-mysqls-order-by-rand-function">this</a>'; |
||
114 | } |
||
115 | 2 | if (strpos($sql, '!=') !== false) { |
|
116 | 1 | $hints[] = 'The <code>!=</code> operator is not standard. Use the <code><></code> operator to test for inequality instead.'; |
|
117 | } |
||
118 | 2 | if (stripos($sql, 'WHERE') === false) { |
|
119 | 1 | $hints[] = 'The <code>SELECT</code> statement has no <code>WHERE</code> clause and could examine many more rows than intended'; |
|
120 | } |
||
121 | 2 | if (preg_match('/LIMIT\\s/i', $sql) && stripos($sql, 'ORDER BY') === false) { |
|
122 | 1 | $hints[] = '<code>LIMIT</code> without <code>ORDER BY</code> causes non-deterministic results, depending on the query execution plan'; |
|
123 | } |
||
124 | 2 | if (preg_match('/LIKE\\s[\'"](%.*?)[\'"]/i', $sql, $matches)) { |
|
125 | 1 | $hints[] = 'An argument has a leading wildcard character: <code>'.$matches[1].'</code>. |
|
126 | The predicate with this argument is not sargable and cannot use an index if one exists.'; |
||
127 | } |
||
128 | 2 | if ($version < 5.5 && $driver === 'mysql') { |
|
129 | 2 | if (preg_match('/\\sIN\\s*\\(\\s*SELECT/i', $sql)) { |
|
130 | 1 | $hints[] = '<code>IN()</code> and <code>NOT IN()</code> subqueries are poorly optimized in that MySQL version : '.$version. |
|
131 | 1 | '. MySQL executes the subquery as a dependent subquery for each row in the outer query'; |
|
132 | } |
||
133 | } |
||
134 | |||
135 | 2 | return $hints; |
|
136 | } |
||
137 | |||
138 | /** |
||
139 | * explain sql. |
||
140 | * |
||
141 | * @param PDO $pdo |
||
142 | * @param string $sql |
||
143 | * @param array $bindings |
||
144 | * @return array |
||
145 | */ |
||
146 | 1 | public static function explain(PDO $pdo, $sql, $bindings = []) |
|
157 | } |
||
158 |