diff --git a/src/Db/Query.php b/src/Db/Query.php index ec8a436..eb75643 100644 --- a/src/Db/Query.php +++ b/src/Db/Query.php @@ -2,6 +2,7 @@ namespace Lagdo\DbAdmin\Driver\Db; +use Exception; use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity; use Lagdo\DbAdmin\Driver\Entity\TableSelectEntity; use Lagdo\DbAdmin\Driver\Entity\TableEntity; @@ -11,6 +12,10 @@ use function implode; use function array_keys; +use function preg_match; +use function preg_replace; +use function substr; +use function strlen; abstract class Query implements QueryInterface { @@ -160,4 +165,125 @@ public function view(string $name) { return []; } + + /** + * Remove current user definer from SQL command + * + * @param string $query + * + * @return string + */ + public function removeDefiner(string $query): string + { + return preg_replace('~^([A-Z =]+) DEFINER=`' . + preg_replace('~@(.*)~', '`@`(%|\1)', $this->user()) . + '`~', '\1', $query); //! proper escaping of user + } + + /** + * Query printed after execution in the message + * + * @param string $query Executed query + * + * @return string + */ + private function queryToLog(string $query/*, string $time*/): string + { + if (strlen($query) > 1e6) { + // [\x80-\xFF] - valid UTF-8, \n - can end by one-line comment + $query = preg_replace('~[\x80-\xFF]+$~', '', substr($query, 0, 1e6)) . "\n…"; + } + return $query; + } + + /** + * Execute query + * + * @param string $query + * @param bool $execute + * @param bool $failed + * + * @return bool + * @throws Exception + */ + public function executeQuery(string $query, bool $execute = true, + bool $failed = false/*, string $time = ''*/): bool + { + if ($execute) { + // $start = microtime(true); + $failed = !$this->driver->execute($query); + // $time = $this->trans->formatTime($start); + } + if ($failed) { + $sql = ''; + if ($query) { + $sql = $this->queryToLog($query/*, $time*/); + } + throw new Exception($this->driver->error() . $sql); + } + return true; + } + + /** + * @param TableFieldEntity $field + * @param string $column + * @param string $value + * + * @return string + */ + private function getWhereColumnClause(TableFieldEntity $field, string $column, string $value): string + { + $bUseSqlLike = $this->driver->jush() === 'sql' && is_numeric($value) && preg_match('~\.~', $value); + return $column . ($bUseSqlLike ? + // LIKE because of floats but slow with ints + " LIKE " . $this->driver->quote($value) : + ($this->driver->jush() === 'mssql' ? + // LIKE because of text + " LIKE " . $this->driver->quote(preg_replace('~[_%[]~', '[\0]', $value)) : + //! enum and set + " = " . $this->driver->unconvertField($field, $this->driver->quote($value)))); + } + + /** + * @param TableFieldEntity $field + * @param string $column + * @param string $value + * + * @return string + */ + private function getWhereCollateClause(TableFieldEntity $field, string $column, string $value): string + { + $bCollate = $this->driver->jush() === 'sql' && + preg_match('~char|text~', $field->type) && preg_match("~[^ -@]~", $value); + return !$bCollate ? '' : + // not just [a-z] to catch non-ASCII characters + "$column = " . $this->driver->quote($value) . " COLLATE " . $this->driver->charset() . "_bin"; + } + + /** + * Create SQL condition from parsed query string + * + * @param array $where Parsed query string + * @param array $fields + * + * @return string + */ + public function where(array $where, array $fields = []): string + { + $clauses = []; + $wheres = $where["where"] ?? []; + foreach ((array) $wheres as $key => $value) { + $key = $this->util->bracketEscape($key, 1); // 1 - back + $column = $this->util->escapeKey($key); + $clauses[] = $this->getWhereColumnClause($fields[$key], $column, $value); + if (($clause = $this->getWhereCollateClause($fields[$key], $column, $value))) { + $clauses[] = $clause; + } + } + $nulls = $where["null"] ?? []; + foreach ((array) $nulls as $key) { + $clauses[] = $this->util->escapeKey($key) . " IS NULL"; + } + return implode(" AND ", $clauses); + } } diff --git a/src/Db/QueryInterface.php b/src/Db/QueryInterface.php index be05b09..6ffc6dd 100644 --- a/src/Db/QueryInterface.php +++ b/src/Db/QueryInterface.php @@ -2,6 +2,7 @@ namespace Lagdo\DbAdmin\Driver\Db; +use Exception; use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity; use Lagdo\DbAdmin\Driver\Entity\TableEntity; @@ -98,6 +99,38 @@ public function lastAutoIncrementId(); */ public function slowQuery(string $query, int $timeout); + /** + * Remove current user definer from SQL command + * + * @param string $query + * + * @return string + */ + public function removeDefiner(string $query): string; + + /** + * Execute query + * + * @param string $query + * @param bool $execute + * @param bool $failed + * + * @return bool + * @throws Exception + */ + public function executeQuery(string $query, bool $execute = true, + bool $failed = false/*, string $time = ''*/): bool; + + /** + * Create SQL condition from parsed query string + * + * @param array $where Parsed query string + * @param array $fields + * + * @return string + */ + public function where(array $where, array $fields = []): string; + /** * Explain select * diff --git a/src/Driver.php b/src/Driver.php index 6ae537e..615b915 100644 --- a/src/Driver.php +++ b/src/Driver.php @@ -2,7 +2,6 @@ namespace Lagdo\DbAdmin\Driver; -use Exception; use Lagdo\DbAdmin\Driver\Db\ConnectionInterface; use Lagdo\DbAdmin\Driver\Db\ServerInterface; use Lagdo\DbAdmin\Driver\Db\DatabaseInterface; @@ -14,9 +13,6 @@ use function is_object; use function preg_match; -use function preg_replace; -use function substr; -use function strlen; use function version_compare; abstract class Driver implements DriverInterface @@ -306,96 +302,4 @@ public function rows(string $query) } return $rows; } - - /** - * Remove current user definer from SQL command - * - * @param string $query - * - * @return string - */ - public function removeDefiner(string $query): string - { - return preg_replace('~^([A-Z =]+) DEFINER=`' . - preg_replace('~@(.*)~', '`@`(%|\1)', $this->user()) . - '`~', '\1', $query); //! proper escaping of user - } - - /** - * Query printed after execution in the message - * - * @param string $query Executed query - * - * @return string - */ - private function queryToLog(string $query/*, string $time*/): string - { - if (strlen($query) > 1e6) { - // [\x80-\xFF] - valid UTF-8, \n - can end by one-line comment - $query = preg_replace('~[\x80-\xFF]+$~', '', substr($query, 0, 1e6)) . "\n…"; - } - return $query; - } - - /** - * Execute query - * - * @param string $query - * @param bool $execute - * @param bool $failed - * - * @return bool - * @throws Exception - */ - public function executeQuery(string $query, bool $execute = true, - bool $failed = false/*, string $time = ''*/): bool - { - if ($execute) { - // $start = microtime(true); - $failed = !$this->execute($query); - // $time = $this->trans->formatTime($start); - } - if ($failed) { - $sql = ''; - if ($query) { - $sql = $this->queryToLog($query/*, $time*/); - } - throw new Exception($this->error() . $sql); - } - return true; - } - - /** - * Create SQL condition from parsed query string - * - * @param array $where Parsed query string - * @param array $fields - * - * @return string - */ - public function where(array $where, array $fields = []): string - { - $clauses = []; - $wheres = $where["where"] ?? []; - foreach ((array) $wheres as $key => $value) { - $key = $this->util->bracketEscape($key, 1); // 1 - back - $column = $this->util->escapeKey($key); - $clauses[] = $column . - // LIKE because of floats but slow with ints - ($this->jush() == "sql" && is_numeric($value) && preg_match('~\.~', $value) ? " LIKE " . - $this->quote($value) : ($this->jush() == "mssql" ? " LIKE " . - $this->quote(preg_replace('~[_%[]~', '[\0]', $value)) : " = " . // LIKE because of text - $this->unconvertField($fields[$key], $this->quote($value)))); //! enum and set - if ($this->jush() == "sql" && - preg_match('~char|text~', $fields[$key]->type) && preg_match("~[^ -@]~", $value)) { - // not just [a-z] to catch non-ASCII characters - $clauses[] = "$column = " . $this->quote($value) . " COLLATE " . $this->charset() . "_bin"; - } - } - $nulls = $where["null"] ?? []; - foreach ((array) $nulls as $key) { - $clauses[] = $this->util->escapeKey($key) . " IS NULL"; - } - return implode(" AND ", $clauses); - } } diff --git a/src/DriverInterface.php b/src/DriverInterface.php index 2e45f36..65e9c90 100644 --- a/src/DriverInterface.php +++ b/src/DriverInterface.php @@ -2,7 +2,6 @@ namespace Lagdo\DbAdmin\Driver; -use Exception; use Lagdo\DbAdmin\Driver\Db\ConnectionInterface; use Lagdo\DbAdmin\Driver\Db\DriverConnectionInterface; use Lagdo\DbAdmin\Driver\Db\ServerInterface; @@ -260,36 +259,4 @@ public function quote(string $string); * @return string */ public function quoteBinary(string $string); - - /** - * Remove current user definer from SQL command - * - * @param string $query - * - * @return string - */ - public function removeDefiner(string $query): string; - - /** - * Execute query - * - * @param string $query - * @param bool $execute - * @param bool $failed - * - * @return bool - * @throws Exception - */ - public function executeQuery(string $query, bool $execute = true, - bool $failed = false/*, string $time = ''*/): bool; - - /** - * Create SQL condition from parsed query string - * - * @param array $where Parsed query string - * @param array $fields - * - * @return string - */ - public function where(array $where, array $fields = []): string; } diff --git a/src/QueryTrait.php b/src/QueryTrait.php index e823fd5..73ef8c0 100644 --- a/src/QueryTrait.php +++ b/src/QueryTrait.php @@ -2,6 +2,7 @@ namespace Lagdo\DbAdmin\Driver; +use Exception; use Lagdo\DbAdmin\Driver\Entity\TableEntity; use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity; use Lagdo\DbAdmin\Driver\Db\ConnectionInterface; @@ -127,6 +128,47 @@ public function slowQuery(string $query, int $timeout) return $this->query->slowQuery($query, $timeout); } + /** + * Remove current user definer from SQL command + * + * @param string $query + * + * @return string + */ + public function removeDefiner(string $query): string + { + return $this->query->removeDefiner($query); + } + + /** + * Execute query + * + * @param string $query + * @param bool $execute + * @param bool $failed + * + * @return bool + * @throws Exception + */ + public function executeQuery(string $query, bool $execute = true, + bool $failed = false/*, string $time = ''*/): bool + { + return $this->query->executeQuery($query, $execute, $failed/*, $time*/); + } + + /** + * Create SQL condition from parsed query string + * + * @param array $where Parsed query string + * @param array $fields + * + * @return string + */ + public function where(array $where, array $fields = []): string + { + return $this->query->where($where, $fields); + } + /** * Explain select *