%PDF- %PDF-
| Direktori : /www/varak.net/losik.varak.net/vendor/nette/database/src/Database/ |
| Current File : /www/varak.net/losik.varak.net/vendor/nette/database/src/Database/Helpers.php |
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Database;
use Nette;
use Nette\Bridges\DatabaseTracy\ConnectionPanel;
use Tracy;
/**
* Database helpers.
*/
class Helpers
{
use Nette\StaticClass;
/** @var int maximum SQL length */
public static $maxLength = 100;
/** @var array */
public static $typePatterns = [
'^_' => IStructure::FIELD_TEXT, // PostgreSQL arrays
'(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,
'(NEW)?DEC(IMAL)?(\(.*)?|NUMERIC|REAL|DOUBLE( PRECISION)?|FLOAT\d*|(SMALL)?MONEY|CURRENCY|NUMBER' => IStructure::FIELD_FLOAT,
'BOOL(EAN)?' => IStructure::FIELD_BOOL,
'TIME' => IStructure::FIELD_TIME,
'DATE' => IStructure::FIELD_DATE,
'(SMALL)?DATETIME(OFFSET)?\d*|TIME(STAMP.*)?' => IStructure::FIELD_DATETIME,
'BYTEA|(TINY|MEDIUM|LONG|)BLOB|(LONG )?(VAR)?BINARY|IMAGE' => IStructure::FIELD_BINARY,
];
/**
* Displays complete result set as HTML table for debug purposes.
*/
public static function dumpResult(ResultSet $result): void
{
echo "\n<table class=\"dump\">\n<caption>" . htmlspecialchars($result->getQueryString(), ENT_IGNORE, 'UTF-8') . "</caption>\n";
if (!$result->getColumnCount()) {
echo "\t<tr>\n\t\t<th>Affected rows:</th>\n\t\t<td>", $result->getRowCount(), "</td>\n\t</tr>\n</table>\n";
return;
}
$i = 0;
foreach ($result as $row) {
if ($i === 0) {
echo "<thead>\n\t<tr>\n\t\t<th>#row</th>\n";
foreach ($row as $col => $foo) {
echo "\t\t<th>" . htmlspecialchars($col, ENT_NOQUOTES, 'UTF-8') . "</th>\n";
}
echo "\t</tr>\n</thead>\n<tbody>\n";
}
echo "\t<tr>\n\t\t<th>", $i, "</th>\n";
foreach ($row as $col) {
if (is_bool($col)) {
$s = $col ? 'TRUE' : 'FALSE';
} elseif ($col === null) {
$s = 'NULL';
} else {
$s = (string) $col;
}
echo "\t\t<td>", htmlspecialchars($s, ENT_NOQUOTES, 'UTF-8'), "</td>\n";
}
echo "\t</tr>\n";
$i++;
}
if ($i === 0) {
echo "\t<tr>\n\t\t<td><em>empty result set</em></td>\n\t</tr>\n</table>\n";
} else {
echo "</tbody>\n</table>\n";
}
}
/**
* Returns syntax highlighted SQL command.
*/
public static function dumpSql(string $sql, ?array $params = null, ?Connection $connection = null): string
{
$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';
$keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|[RI]?LIKE|REGEXP|TRUE|FALSE';
// insert new lines
$sql = " $sql ";
$sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);
// reduce spaces
$sql = preg_replace('#[ \t]{2,}#', ' ', $sql);
$sql = wordwrap($sql, 100);
$sql = preg_replace('#([ \t]*\r?\n){2,}#', "\n", $sql);
// syntax highlight
$sql = htmlspecialchars($sql, ENT_IGNORE, 'UTF-8');
$sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is", function (array $matches) {
if (!empty($matches[1])) { // comment
return '<em style="color:gray">' . $matches[1] . '</em>';
} elseif (!empty($matches[2])) { // error
return '<strong style="color:red">' . $matches[2] . '</strong>';
} elseif (!empty($matches[3])) { // most important keywords
return '<strong style="color:blue">' . $matches[3] . '</strong>';
} elseif (!empty($matches[4])) { // other keywords
return '<strong style="color:green">' . $matches[4] . '</strong>';
}
}, $sql);
// parameters
$sql = preg_replace_callback('#\?#', function () use ($params, $connection): string {
static $i = 0;
if (!isset($params[$i])) {
return '?';
}
$param = $params[$i++];
if (
is_string($param)
&& (
preg_match('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u', $param)
|| preg_last_error()
)
) {
return '<i title="Length ' . strlen($param) . ' bytes"><binary></i>';
} elseif (is_string($param)) {
$length = Nette\Utils\Strings::length($param);
$truncated = Nette\Utils\Strings::truncate($param, self::$maxLength);
$text = htmlspecialchars($connection ? $connection->quote($truncated) : '\'' . $truncated . '\'', ENT_NOQUOTES, 'UTF-8');
return '<span title="Length ' . $length . ' characters">' . $text . '</span>';
} elseif (is_resource($param)) {
$type = get_resource_type($param);
if ($type === 'stream') {
$info = stream_get_meta_data($param);
}
return '<i' . (isset($info['uri']) ? ' title="' . htmlspecialchars($info['uri'], ENT_NOQUOTES, 'UTF-8') . '"' : null)
. '><' . htmlspecialchars($type, ENT_NOQUOTES, 'UTF-8') . ' resource></i> ';
} elseif (is_bool($param)) {
return (string) (int) $param;
} else {
return htmlspecialchars((string) $param, ENT_NOQUOTES, 'UTF-8');
}
}, $sql);
return '<pre class="dump">' . trim($sql) . "</pre>\n";
}
/**
* Common column type detection.
*/
public static function detectTypes(\PDOStatement $statement): array
{
$types = [];
$count = $statement->columnCount(); // driver must be meta-aware, see PHP bugs #53782, #54695
for ($col = 0; $col < $count; $col++) {
$meta = $statement->getColumnMeta($col);
if (isset($meta['native_type'])) {
$types[$meta['name']] = self::detectType($meta['native_type']);
}
}
return $types;
}
/**
* Heuristic column type detection.
* @internal
*/
public static function detectType(string $type): string
{
static $cache;
if (!isset($cache[$type])) {
$cache[$type] = 'string';
foreach (self::$typePatterns as $s => $val) {
if (preg_match("#^($s)$#i", $type)) {
return $cache[$type] = $val;
}
}
}
return $cache[$type];
}
/** @internal */
public static function normalizeRow(array $row, ResultSet $resultSet): array
{
foreach ($resultSet->getColumnTypes() as $key => $type) {
$value = $row[$key];
if ($value === null || $value === false || $type === IStructure::FIELD_TEXT) {
// do nothing
} elseif ($type === IStructure::FIELD_INTEGER) {
$row[$key] = is_float($tmp = $value * 1) ? $value : $tmp;
} elseif ($type === IStructure::FIELD_FLOAT) {
if (is_string($value) && ($pos = strpos($value, '.')) !== false) {
$value = rtrim(rtrim($pos === 0 ? "0$value" : $value, '0'), '.');
}
$row[$key] = (float) $value;
} elseif ($type === IStructure::FIELD_BOOL) {
$row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
} elseif (
$type === IStructure::FIELD_DATETIME
|| $type === IStructure::FIELD_DATE
|| $type === IStructure::FIELD_TIME
) {
$row[$key] = new Nette\Utils\DateTime($value);
} elseif ($type === IStructure::FIELD_TIME_INTERVAL) {
preg_match('#^(-?)(\d+)\D(\d+)\D(\d+)(\.\d+)?$#D', $value, $m);
$row[$key] = new \DateInterval("PT$m[2]H$m[3]M$m[4]S");
$row[$key]->f = isset($m[5]) ? (float) $m[5] : 0.0;
$row[$key]->invert = (int) (bool) $m[1];
} elseif ($type === IStructure::FIELD_UNIX_TIMESTAMP) {
$row[$key] = Nette\Utils\DateTime::from($value);
}
}
return $row;
}
/**
* Import SQL dump from file - extremely fast.
* @param array<callable(int, ?float): void> $onProgress
* @return int count of commands
*/
public static function loadFromFile(Connection $connection, string $file, ?callable $onProgress = null): int
{
@set_time_limit(0); // @ function may be disabled
$handle = @fopen($file, 'r'); // @ is escalated to exception
if (!$handle) {
throw new Nette\FileNotFoundException("Cannot open file '$file'.");
}
$stat = fstat($handle);
$count = $size = 0;
$delimiter = ';';
$sql = '';
$pdo = $connection->getPdo(); // native query without logging
while (($s = fgets($handle)) !== false) {
$size += strlen($s);
if (!strncasecmp($s, 'DELIMITER ', 10)) {
$delimiter = trim(substr($s, 10));
} elseif (substr($ts = rtrim($s), -strlen($delimiter)) === $delimiter) {
$sql .= substr($ts, 0, -strlen($delimiter));
$pdo->exec($sql);
$sql = '';
$count++;
if ($onProgress) {
$onProgress($count, isset($stat['size']) ? $size * 100 / $stat['size'] : null);
}
} else {
$sql .= $s;
}
}
if (rtrim($sql) !== '') {
$pdo->exec($sql);
$count++;
if ($onProgress) {
$onProgress($count, isset($stat['size']) ? 100 : null);
}
}
fclose($handle);
return $count;
}
/** @deprecated use Nette\Bridges\DatabaseTracy\ConnectionPanel::initialize() */
public static function createDebugPanel(
Connection $connection,
bool $explain,
string $name,
Tracy\Bar $bar,
Tracy\BlueScreen $blueScreen
): ?ConnectionPanel
{
return ConnectionPanel::initialize($connection, true, $name, $explain, $bar, $blueScreen);
}
/** @deprecated use Nette\Bridges\DatabaseTracy\ConnectionPanel::initialize() */
public static function initializeTracy(
Connection $connection,
bool $addBarPanel = false,
string $name = '',
bool $explain = true,
?Tracy\Bar $bar = null,
?Tracy\BlueScreen $blueScreen = null
): ?ConnectionPanel
{
return ConnectionPanel::initialize($connection, $addBarPanel, $name, $explain, $bar, $blueScreen);
}
/**
* Reformat source to key -> value pairs.
*/
public static function toPairs(array $rows, $key = null, $value = null): array
{
if (!$rows) {
return [];
}
$keys = array_keys((array) reset($rows));
if (!count($keys)) {
throw new \LogicException('Result set does not contain any column.');
} elseif ($key === null && $value === null) {
if (count($keys) === 1) {
[$value] = $keys;
} else {
[$key, $value] = $keys;
}
}
$return = [];
if ($key === null) {
foreach ($rows as $row) {
$return[] = ($value === null ? $row : $row[$value]);
}
} else {
foreach ($rows as $row) {
$return[(string) $row[$key]] = ($value === null ? $row : $row[$value]);
}
}
return $return;
}
/**
* Finds duplicate columns in select statement
*/
public static function findDuplicates(\PDOStatement $statement): string
{
$cols = [];
for ($i = 0; $i < $statement->columnCount(); $i++) {
$meta = $statement->getColumnMeta($i);
$cols[$meta['name']][] = $meta['table'] ?? '';
}
$duplicates = [];
foreach ($cols as $name => $tables) {
if (count($tables) > 1) {
$tables = array_filter(array_unique($tables));
$duplicates[] = "'$name'" . ($tables ? ' (from ' . implode(', ', $tables) . ')' : '');
}
}
return implode(', ', $duplicates);
}
}