diff --git a/adminer/drivers/clickhouse.inc.php b/adminer/drivers/clickhouse.inc.php new file mode 100644 index 00000000..d2e50d8c --- /dev/null +++ b/adminer/drivers/clickhouse.inc.php @@ -0,0 +1,358 @@ +_url/?database=$db", false, stream_context_create(array('http' => array( + 'method' => 'POST', + 'content' => stripos($query, 'insert') === 0 ? $query : "$query FORMAT JSONCompact", + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'ignore_errors' => 1, // available since PHP 5.2.10 + )))); + + if (!$file) { + $this->error = $php_errormsg; + return $file; + } + if (!preg_match('~^HTTP/[0-9.]+ 2~i', $http_response_header[0])) { + $this->error = $file; + return false; + } + $return = json_decode($file, true); + if ($return === null) { + $this->errno = json_last_error(); + if (function_exists('json_last_error_msg')) { + $this->error = json_last_error_msg(); + } else { + $constants = get_defined_constants(true); + foreach ($constants['json'] as $name => $value) { + if ($value == $this->errno && preg_match('~^JSON_ERROR_~', $name)) { + $this->error = $name; + break; + } + } + } + } + return new Min_Result($return); + } + + function query($query) { + return $this->rootQuery($this->_db, $query); + } + + function connect($server, $username, $password) { + preg_match('~^(https?://)?(.*)~', $server, $match); + $this->_url = ($match[1] ? $match[1] : "http://") . "$username:$password@$match[2]"; + $return = $this->query('SELECT 1'); + if ($return) { + //$this->server_info = $return['version']['number']; + } + return (bool) $return; + } + + function select_db($database) { + $this->_db = $database; + return true; + } + + function quote($string) { + return "'$string'"; + } + + function multi_query($query) { + return $this->_result = $this->query($query); + } + + function store_result() { + return $this->_result; + } + + function next_result() { + return false; + } + + function result($query, $field = 0) { + $result = $this->query($query); + return $result['data']; + } + } + + class Min_Result { + var $num_rows, $_rows, $columns, $meta, $_offset = 0; + + function __construct($result) { + $this->num_rows = $result['rows']; + $this->_rows = $result['data']; + $this->meta = $result['meta']; + $this->columns = array_column($this->meta, 'name'); + reset($this->_rows); + } + + function fetch_assoc() { + $row = current($this->_rows); + next($this->_rows); + return $row === false ? false : array_combine($this->columns, $row); + } + + function fetch_row() { + $row = current($this->_rows); + next($this->_rows); + return $row; + } + + function fetch_field() { + $column = $this->_offset++; + $return = new stdClass; + if ($column < count($this->columns)) { + $return->name = $this->meta[$column]['name']; + $return->orgname = $return->name; + $return->type = $this->meta[$column]['type']; + } + return $return; + } + } + + + class Min_Driver extends Min_SQL { + } + + function idf_escape($idf) { + return "`" . str_replace("`", "``", $idf) . "`"; + } + + function table($idf) { + return idf_escape($idf); + } + + function explain($connection, $query) { + return ''; + } + + function found_rows($table_status, $where) { + $rows = get_vals("SELECT COUNT(*) FROM " . idf_escape($table_status["Name"]) . ($where ? " WHERE " . implode(" AND ", $where) : "")); + return empty($rows) ? false : $rows[0]; + } + + function alter_table($table, $name, $fields, $foreign, $comment, $engine, $collation, $auto_increment, $partitioning) { + $alter = array(); + foreach ($fields as $field) { + if ($field[1][2] === " NULL") { + $field[1][1] = " Nullable({$field[1][1]})"; + } + unset($field[1][2]); + $alter[] = ($field[1] + ? ($table != "" ? ($field[0] != "" ? "CHANGE " . idf_escape($field[0]) : "ADD") : " ") . " " . implode($field[1]) + : "DROP " . idf_escape($field[0]) + ); + } + $alter = array_merge($alter, $foreign); + $status = ($comment !== null ? " COMMENT=" . q($comment) : "") + . ($engine ? " ENGINE=" . q($engine) : "") + . ($collation ? " COLLATE " . q($collation) : "") + . ($auto_increment != "" ? " AUTO_INCREMENT=$auto_increment" : "") + ; + if ($table == "") { + return queries("CREATE TABLE " . table($name) . " (\n" . implode(",\n", $alter) . "\n)$status$partitioning"); + } + if ($table != $name) { + $alter[] = "RENAME TO " . table($name); + } + if ($status) { + $alter[] = ltrim($status); + } + return ($alter || $partitioning ? queries("ALTER TABLE " . table($table) . "\n" . implode(",\n", $alter) . $partitioning) : true); + } + + function truncate_tables($tables) { + return apply_queries("TRUNCATE TABLE", $tables); + } + + function drop_views($views) { + return queries("DROP VIEW " . implode(", ", array_map('table', $views))); + } + + function drop_tables($tables) { + return queries("DROP TABLE " . implode(", ", array_map('table', $tables))); + } + + function connect() { + global $adminer; + $connection = new Min_DB; + $credentials = $adminer->credentials(); + if ($connection->connect($credentials[0], $credentials[1], $credentials[2])) { + return $connection; + } + return $connection->error; + } + + function get_databases($flush) { + global $connection; + $result = get_rows('SHOW DATABASES'); + + $return = array(); + foreach ($result as $row) { + $return[] = $row['name']; + } + sort($return); + return $return; + } + + function limit($query, $where, $limit, $offset = 0, $separator = " ") { + return " $query$where" . ($limit !== null ? $separator . "LIMIT $limit" . ($offset ? ", $offset" : "") : ""); + } + + function limit1($table, $query, $where, $separator = "\n") { + return limit($query, $where, 1, 0, $separator); + } + + function db_collation($db, $collations) { + } + + function engines() { + return array('MergeTree'); + } + + function logged_user() { + global $adminer; + $credentials = $adminer->credentials(); + return $credentials[1]; + } + + function tables_list() { + $result = get_rows('SHOW TABLES'); + $return = array(); + foreach ($result as $row) { + $return[$row['name']] = 'table'; + } + ksort($return); + return $return; + } + + function count_tables($databases) { + return array(); + } + + function table_status($name = "", $fast = false) { + global $connection; + $return = array(); + $tables = get_rows("SELECT name, engine FROM system.tables WHERE database = '{$connection->_db}'"); + foreach ($tables as $table) { + $return[$table['name']] = array( + 'Name' => $table['name'], + 'Engine' => $table['engine'], + ); + if ($name === $table['name']) { + return $return[$table['name']]; + } + } + return $return; + } + + function is_view($table_status) { + return false; + } + + function fk_support($table_status) { + return false; + } + + function convert_field($field) { + } + + function unconvert_field($field, $return) { + if (in_array($field['type'], ["Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64", "Float32", "Float64"])) { + return "to$field[type]($return)"; + } + return $return; + } + + function fields($table) { + $return = array(); + $result = get_rows("SELECT name, type, default_expression FROM system.columns WHERE " . idf_escape('table') . " = " . q($table)); + foreach($result as $row) { + $type = trim($row['type']); + $nullable = strpos($type, 'Nullable(') === 0; + $return[trim($row['name'])] = array( + "field" => trim($row['name']), + "full_type" => $type, + "type" => $type, + "default" => trim($row['default_expression']), + "null" => $nullable, + "auto_increment" => '0', + "privileges" => array("insert" => 1, "select" => 1, "update" => 0), + ); + } + + return $return; + } + + function indexes($table, $connection2 = null) { + return array(); + } + + function foreign_keys($table) { + return array(); + } + + function collations() { + return array(); + } + + function information_schema($db) { + return false; + } + + function error() { + global $connection; + return h($connection->error); + } + + function types() { + return array(); + } + + function schemas() { + return array(); + } + + function get_schema() { + return ""; + } + + function set_schema($schema) { + return true; + } + + function auto_increment() { + return ''; + } + + function support($feature) { + return preg_match("~^(columns|sql|status|table)$~", $feature); + } + + $jush = "clickhouse"; + $types = array(); + $structured_types = array(); + foreach (array( //! arrays + lang('Numbers') => array("Int8" => 3, "Int16" => 5, "Int32" => 10, "Int64" => 19, "UInt8" => 3, "UInt16" => 5, "UInt32" => 10, "UInt64" => 20, "Float32" => 7, "Float64" => 16), + lang('Date and time') => array("Date" => 13, "DateTime" => 20), + lang('Strings') => array("String" => 0), + lang('Binary') => array("FixedString" => 0), + ) as $key => $val) { + $types += $val; + $structured_types[$key] = array_keys($val); + } + $unsigned = array(); + $operators = array("=", "<", ">", "<=", ">=", "!=", "~", "!~", "LIKE", "LIKE %%", "IN", "IS NULL", "NOT LIKE", "NOT IN", "IS NOT NULL"); + $functions = array(); + $grouping = array("avg", "count", "count distinct", "max", "min", "sum"); + $edit_functions = array(); +} diff --git a/adminer/include/bootstrap.inc.php b/adminer/include/bootstrap.inc.php index 1db4e2ed..9f09b326 100644 --- a/adminer/include/bootstrap.inc.php +++ b/adminer/include/bootstrap.inc.php @@ -79,6 +79,7 @@ include "../adminer/drivers/firebird.inc.php"; include "../adminer/drivers/simpledb.inc.php"; include "../adminer/drivers/mongo.inc.php"; include "../adminer/drivers/elastic.inc.php"; +include "../adminer/drivers/clickhouse.inc.php"; include "../adminer/drivers/mysql.inc.php"; // must be included as last driver define("SERVER", $_GET[DRIVER]); // read from pgsql=localhost diff --git a/adminer/include/version.inc.php b/adminer/include/version.inc.php index 19a598c7..22e21827 100644 --- a/adminer/include/version.inc.php +++ b/adminer/include/version.inc.php @@ -1,2 +1,2 @@