1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Database;
9:
10: use Nette;
11: use Tracy;
12:
13:
14: 15: 16:
17: class Helpers
18: {
19: use Nette\StaticClass;
20:
21:
22: public static $maxLength = 100;
23:
24:
25: public static $typePatterns = [
26: '^_' => IStructure::FIELD_TEXT,
27: '(TINY|SMALL|SHORT|MEDIUM|BIG|LONG)(INT)?|INT(EGER|\d+| IDENTITY)?|(SMALL|BIG|)SERIAL\d*|COUNTER|YEAR|BYTE|LONGLONG|UNSIGNED BIG INT' => IStructure::FIELD_INTEGER,
28: '(NEW)?DEC(IMAL)?(\(.*)?|NUMERIC|REAL|DOUBLE( PRECISION)?|FLOAT\d*|(SMALL)?MONEY|CURRENCY|NUMBER' => IStructure::FIELD_FLOAT,
29: 'BOOL(EAN)?' => IStructure::FIELD_BOOL,
30: 'TIME' => IStructure::FIELD_TIME,
31: 'DATE' => IStructure::FIELD_DATE,
32: '(SMALL)?DATETIME(OFFSET)?\d*|TIME(STAMP.*)?' => IStructure::FIELD_DATETIME,
33: 'BYTEA|(TINY|MEDIUM|LONG|)BLOB|(LONG )?(VAR)?BINARY|IMAGE' => IStructure::FIELD_BINARY,
34: ];
35:
36:
37: 38: 39: 40:
41: public static function dumpResult(ResultSet $result)
42: {
43: echo "\n<table class=\"dump\">\n<caption>" . htmlspecialchars($result->getQueryString(), ENT_IGNORE, 'UTF-8') . "</caption>\n";
44: if (!$result->getColumnCount()) {
45: echo "\t<tr>\n\t\t<th>Affected rows:</th>\n\t\t<td>", $result->getRowCount(), "</td>\n\t</tr>\n</table>\n";
46: return;
47: }
48: $i = 0;
49: foreach ($result as $row) {
50: if ($i === 0) {
51: echo "<thead>\n\t<tr>\n\t\t<th>#row</th>\n";
52: foreach ($row as $col => $foo) {
53: echo "\t\t<th>" . htmlspecialchars($col, ENT_NOQUOTES, 'UTF-8') . "</th>\n";
54: }
55: echo "\t</tr>\n</thead>\n<tbody>\n";
56: }
57: echo "\t<tr>\n\t\t<th>", $i, "</th>\n";
58: foreach ($row as $col) {
59: echo "\t\t<td>", htmlspecialchars($col, ENT_NOQUOTES, 'UTF-8'), "</td>\n";
60: }
61: echo "\t</tr>\n";
62: $i++;
63: }
64:
65: if ($i === 0) {
66: echo "\t<tr>\n\t\t<td><em>empty result set</em></td>\n\t</tr>\n</table>\n";
67: } else {
68: echo "</tbody>\n</table>\n";
69: }
70: }
71:
72:
73: 74: 75: 76: 77:
78: public static function dumpSql($sql, array $params = null, Connection $connection = null)
79: {
80: static $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';
81: static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE';
82:
83:
84: $sql = " $sql ";
85: $sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);
86:
87:
88: $sql = preg_replace('#[ \t]{2,}#', ' ', $sql);
89:
90: $sql = wordwrap($sql, 100);
91: $sql = preg_replace('#([ \t]*\r?\n){2,}#', "\n", $sql);
92:
93:
94: $sql = htmlspecialchars($sql, ENT_IGNORE, 'UTF-8');
95: $sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is", function ($matches) {
96: if (!empty($matches[1])) {
97: return '<em style="color:gray">' . $matches[1] . '</em>';
98:
99: } elseif (!empty($matches[2])) {
100: return '<strong style="color:red">' . $matches[2] . '</strong>';
101:
102: } elseif (!empty($matches[3])) {
103: return '<strong style="color:blue">' . $matches[3] . '</strong>';
104:
105: } elseif (!empty($matches[4])) {
106: return '<strong style="color:green">' . $matches[4] . '</strong>';
107: }
108: }, $sql);
109:
110:
111: $sql = preg_replace_callback('#\?#', function () use ($params, $connection) {
112: static $i = 0;
113: if (!isset($params[$i])) {
114: return '?';
115: }
116: $param = $params[$i++];
117: if (is_string($param) && (preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $param) || preg_last_error())) {
118: return '<i title="Length ' . strlen($param) . ' bytes"><binary></i>';
119:
120: } elseif (is_string($param)) {
121: $length = Nette\Utils\Strings::length($param);
122: $truncated = Nette\Utils\Strings::truncate($param, self::$maxLength);
123: $text = htmlspecialchars($connection ? $connection->quote($truncated) : '\'' . $truncated . '\'', ENT_NOQUOTES, 'UTF-8');
124: return '<span title="Length ' . $length . ' characters">' . $text . '</span>';
125:
126: } elseif (is_resource($param)) {
127: $type = get_resource_type($param);
128: if ($type === 'stream') {
129: $info = stream_get_meta_data($param);
130: }
131: return '<i' . (isset($info['uri']) ? ' title="' . htmlspecialchars($info['uri'], ENT_NOQUOTES, 'UTF-8') . '"' : null)
132: . '><' . htmlspecialchars($type, ENT_NOQUOTES, 'UTF-8') . ' resource></i> ';
133:
134: } else {
135: return htmlspecialchars($param, ENT_NOQUOTES, 'UTF-8');
136: }
137: }, $sql);
138:
139: return '<pre class="dump">' . trim($sql) . "</pre>\n";
140: }
141:
142:
143: 144: 145: 146:
147: public static function detectTypes(\PDOStatement $statement)
148: {
149: $types = [];
150: $count = $statement->columnCount();
151: for ($col = 0; $col < $count; $col++) {
152: $meta = $statement->getColumnMeta($col);
153: if (isset($meta['native_type'])) {
154: $types[$meta['name']] = self::detectType($meta['native_type']);
155: }
156: }
157: return $types;
158: }
159:
160:
161: 162: 163: 164: 165: 166:
167: public static function detectType($type)
168: {
169: static $cache;
170: if (!isset($cache[$type])) {
171: $cache[$type] = 'string';
172: foreach (self::$typePatterns as $s => $val) {
173: if (preg_match("#^($s)$#i", $type)) {
174: return $cache[$type] = $val;
175: }
176: }
177: }
178: return $cache[$type];
179: }
180:
181:
182: 183: 184: 185:
186: public static function loadFromFile(Connection $connection, $file)
187: {
188: @set_time_limit(0);
189:
190: $handle = @fopen($file, 'r');
191: if (!$handle) {
192: throw new Nette\FileNotFoundException("Cannot open file '$file'.");
193: }
194:
195: $count = 0;
196: $delimiter = ';';
197: $sql = '';
198: $pdo = $connection->getPdo();
199: while (!feof($handle)) {
200: $s = rtrim(fgets($handle));
201: if (!strncasecmp($s, 'DELIMITER ', 10)) {
202: $delimiter = substr($s, 10);
203:
204: } elseif (substr($s, -strlen($delimiter)) === $delimiter) {
205: $sql .= substr($s, 0, -strlen($delimiter));
206: $pdo->exec($sql);
207: $sql = '';
208: $count++;
209:
210: } else {
211: $sql .= $s . "\n";
212: }
213: }
214: if (trim($sql) !== '') {
215: $pdo->exec($sql);
216: $count++;
217: }
218: fclose($handle);
219: return $count;
220: }
221:
222:
223: public static function createDebugPanel($connection, $explain = true, $name = null)
224: {
225: $panel = new Nette\Bridges\DatabaseTracy\ConnectionPanel($connection);
226: $panel->explain = $explain;
227: $panel->name = $name;
228: Tracy\Debugger::getBar()->addPanel($panel);
229: return $panel;
230: }
231:
232:
233: 234: 235: 236:
237: public static function toPairs(array $rows, $key = null, $value = null)
238: {
239: if (!$rows) {
240: return [];
241: }
242:
243: $keys = array_keys((array) reset($rows));
244: if (!count($keys)) {
245: throw new \LogicException('Result set does not contain any column.');
246:
247: } elseif ($key === null && $value === null) {
248: if (count($keys) === 1) {
249: list($value) = $keys;
250: } else {
251: list($key, $value) = $keys;
252: }
253: }
254:
255: $return = [];
256: if ($key === null) {
257: foreach ($rows as $row) {
258: $return[] = ($value === null ? $row : $row[$value]);
259: }
260: } else {
261: foreach ($rows as $row) {
262: $return[(string) $row[$key]] = ($value === null ? $row : $row[$value]);
263: }
264: }
265:
266: return $return;
267: }
268:
269:
270: 271: 272: 273: 274:
275: public static function findDuplicates(\PDOStatement $statement)
276: {
277: $cols = [];
278: for ($i = 0; $i < $statement->columnCount(); $i++) {
279: $meta = $statement->getColumnMeta($i);
280: $cols[$meta['name']][] = isset($meta['table']) ? $meta['table'] : '';
281: }
282: $duplicates = [];
283: foreach ($cols as $name => $tables) {
284: if (count($tables) > 1) {
285: $tables = array_filter(array_unique($tables));
286: $duplicates[] = "'$name'" . ($tables ? ' (from ' . implode(', ', $tables) . ')' : '');
287: }
288: }
289: return implode(', ', $duplicates);
290: }
291: }
292: