190 lines
4.1 KiB
PHP
190 lines
4.1 KiB
PHP
<?php
|
|
|
|
use Vn\Lib;
|
|
use Vn\Web\Security;
|
|
use Vn\Lib\Type;
|
|
|
|
class Query extends Vn\Web\JsonRequest {
|
|
const PARAMS = ['sql'];
|
|
const SECURITY = Security::INVOKER;
|
|
|
|
function __construct($app) {
|
|
parent::__construct($app);
|
|
$this->utcTz = new DateTimeZone('UTC');
|
|
|
|
$dbConf = $app->getConf()['db'];
|
|
|
|
if (isset($dbConf['tz']))
|
|
$this->tz = new DateTimeZone($dbConf['tz']);
|
|
else
|
|
$this->tz = $this->utcTz;
|
|
}
|
|
|
|
function run($db) {
|
|
$results = [];
|
|
|
|
try {
|
|
$db->multiQuery($_REQUEST['sql']);
|
|
|
|
do {
|
|
$result = $db->storeResult();
|
|
|
|
if ($result !== FALSE) {
|
|
$results[] = $this->transformResult($result);
|
|
$result->free();
|
|
} else
|
|
$results[] = TRUE;
|
|
}
|
|
while ($db->moreResults() && $db->nextResult());
|
|
|
|
// Checks for warnings
|
|
|
|
if ($db->checkWarnings()
|
|
&&($result = $db->query('SHOW WARNINGS'))) {
|
|
$sql = 'SELECT `description`, @warn `code`
|
|
FROM `message` WHERE `code` = @warn';
|
|
|
|
while ($row = $result->fetch_object()) {
|
|
if ($row->Code == 1265
|
|
&&($warning = $db->getObject($sql)))
|
|
trigger_error("{$warning->code}: {$warning->description}", E_USER_WARNING);
|
|
else
|
|
trigger_error("{$row->Code}: {$row->Message}", E_USER_WARNING);
|
|
}
|
|
}
|
|
|
|
// Checks for errors
|
|
|
|
$db->checkError();
|
|
} catch (Vn\Db\Exception $e) {
|
|
if ($e->getCode() == 1644) {
|
|
$dbMessage = $e->getMessage();
|
|
$sql = 'SELECT `description` FROM `message` WHERE `code` = #';
|
|
$message = $db->getValue($sql, [$dbMessage]);
|
|
|
|
if ($message)
|
|
throw new Lib\UserException($message, $dbMessage);
|
|
}
|
|
|
|
throw $e;
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Transforms the database result into a JSON parseable object.
|
|
**/
|
|
function transformResult($result) {
|
|
$tableMap = [];
|
|
$columns = $result->fetch_fields();
|
|
|
|
$resultMap =
|
|
[
|
|
'data' => [],
|
|
'columns' => [],
|
|
'tables' => []
|
|
];
|
|
|
|
for ($i = 0; $i < $result->field_count; $i++) {
|
|
$column = $columns[$i];
|
|
|
|
switch ($column->type) {
|
|
case MYSQLI_TYPE_BIT:
|
|
$type = Type::BOOLEAN;
|
|
break;
|
|
case MYSQLI_TYPE_TINY:
|
|
case MYSQLI_TYPE_SHORT:
|
|
case MYSQLI_TYPE_LONG:
|
|
case MYSQLI_TYPE_LONGLONG:
|
|
case MYSQLI_TYPE_INT24:
|
|
case MYSQLI_TYPE_YEAR:
|
|
$type = Type::INTEGER;
|
|
break;
|
|
case MYSQLI_TYPE_FLOAT:
|
|
case MYSQLI_TYPE_DOUBLE:
|
|
case MYSQLI_TYPE_DECIMAL:
|
|
case MYSQLI_TYPE_NEWDECIMAL:
|
|
$type = Type::DOUBLE;
|
|
break;
|
|
case MYSQLI_TYPE_DATE:
|
|
$type = Type::DATE;
|
|
break;
|
|
case MYSQLI_TYPE_DATETIME:
|
|
case MYSQLI_TYPE_TIMESTAMP:
|
|
$type = Type::DATE_TIME;
|
|
break;
|
|
default;
|
|
$type = Type::STRING;
|
|
}
|
|
|
|
if (!isset($tableMap[$column->table])) {
|
|
$resultMap['tables'][] =
|
|
[
|
|
'name' => $column->table,
|
|
'orgname' => $column->orgtable,
|
|
'schema' => $column->db,
|
|
'pks' => []
|
|
];
|
|
$tableIndex = count($resultMap['tables']) - 1;
|
|
$tableMap[$column->table] = $tableIndex;
|
|
} else
|
|
$tableIndex = $tableMap[$column->table];
|
|
|
|
if ($column->flags & MYSQLI_PRI_KEY_FLAG)
|
|
$resultMap['tables'][$tableIndex]['pks'][] = $i;
|
|
|
|
$default = $this->castValue($column->def, $type);
|
|
|
|
$resultMap['columns'][] =
|
|
[
|
|
'type' => $type,
|
|
'flags' => $column->flags,
|
|
'def' => $default,
|
|
'name' => $column->name,
|
|
'orgname' => $column->orgname,
|
|
'table' => $tableIndex
|
|
];
|
|
}
|
|
|
|
$columns = $resultMap['columns'];
|
|
|
|
while ($row = $result->fetch_row()) {
|
|
for ($j = 0; $j < $result->field_count; $j++)
|
|
$row[$j] = $this->castValue($row[$j], $columns[$j]['type']);
|
|
|
|
$resultMap['data'][] = $row;
|
|
}
|
|
|
|
return $resultMap;
|
|
}
|
|
|
|
/**
|
|
* Transforms the database value into a JSON parseable value.
|
|
**/
|
|
function castValue($value, $type) {
|
|
if ($value === '' && $type != Type::STRING)
|
|
$value = NULL;
|
|
|
|
if ($value !== NULL)
|
|
switch ($type) {
|
|
case Type::BOOLEAN:
|
|
return (bool) $value;
|
|
case Type::INTEGER:
|
|
return (int) $value;
|
|
case Type::DOUBLE:
|
|
return (float) $value;
|
|
case Type::DATE:
|
|
case Type::DATE_TIME:
|
|
$d = new DateTime($value, $this->tz);
|
|
$d->setTimeZone($this->utcTz);
|
|
return $d->format('Y-m-d\TH:i:s.v\Z');
|
|
break;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
}
|
|
|