Skip to content

Commit

Permalink
Merge pull request #12 from sartor/smi2-driver
Browse files Browse the repository at this point in the history
Smi2 driver alpha version
  • Loading branch information
sartor authored Apr 26, 2019
2 parents 5829b3c + 6ffc127 commit 34bb82d
Show file tree
Hide file tree
Showing 29 changed files with 297 additions and 1,438 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: php
php:
- 7
- 7.1
- 7.2
- 7.3

env:
- CLICKHOUSE_VERSION=latest
Expand All @@ -18,4 +18,4 @@ before_script:
- docker run -d -p 127.0.0.1:8123:8123 --name test-clickhouse-server --ulimit nofile=262144:262144 yandex/clickhouse-server:$CLICKHOUSE_VERSION
- docker logs test-clickhouse-server

script: ./vendor/bin/phpunit
script: ./vendor/bin/phpunit
89 changes: 89 additions & 0 deletions ArrayExpressionBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace bashkarev\clickhouse;


use yii\db\ArrayExpression;
use yii\db\ExpressionBuilderInterface;
use yii\db\ExpressionBuilderTrait;
use yii\db\ExpressionInterface;
use yii\db\Query;
use Traversable;

class ArrayExpressionBuilder implements ExpressionBuilderInterface
{
use ExpressionBuilderTrait;

/**
* {@inheritdoc}
* @param ArrayExpression|ExpressionInterface $expression the expression to be built
*/
public function build(ExpressionInterface $expression, array &$params = []): string
{
$value = $expression->getValue();
if ($value === null) {
return 'NULL';
}

if ($value instanceof Query) {
list ($sql, $params) = $this->queryBuilder->build($value, $params);
return $this->buildSubqueryArray($sql);
}

$placeholders = $this->buildPlaceholders($expression, $params);

return '[' . implode(', ', $placeholders) . ']';
}

/**
* Builds placeholders array out of $expression values
* @param ExpressionInterface|ArrayExpression $expression
* @param array $params the binding parameters.
* @return array
*/
protected function buildPlaceholders(ExpressionInterface $expression, &$params): array
{
$value = $expression->getValue();

$placeholders = [];
if ($value === null || (!is_array($value) && !$value instanceof Traversable)) {
return $placeholders;
}

if ($expression->getDimension() > 1) {
foreach ($value as $item) {
$placeholders[] = $this->build($this->unnestArrayExpression($expression, $item), $params);
}
return $placeholders;
}

foreach ($value as $item) {
if ($item instanceof Query) {
list ($sql, $params) = $this->queryBuilder->build($item, $params);
$placeholders[] = $this->buildSubqueryArray($sql);
continue;
}

if ($item instanceof ExpressionInterface) {
$placeholders[] = $this->queryBuilder->buildExpression($item, $params);
continue;
}

$placeholders[] = $this->queryBuilder->bindParam($item, $params);
}

return $placeholders;
}

private function unnestArrayExpression(ArrayExpression $expression, $value): ArrayExpression
{
$expressionClass = get_class($expression);

return new $expressionClass($value, $expression->getType(), $expression->getDimension() - 1);
}

protected function buildSubqueryArray($sql): string
{
return "array({$sql})";
}
}
5 changes: 5 additions & 0 deletions ColumnSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace bashkarev\clickhouse;

use yii\db\ArrayExpression;
use yii\db\Expression;

/**
Expand All @@ -20,6 +21,10 @@ public function dbTypecast($value)
$value = new Expression($value);
}

if (strpos($this->dbType, 'Array(') === 0) {
return new ArrayExpression($value, $this->type);
}

return parent::dbTypecast($value);
}
}
168 changes: 58 additions & 110 deletions Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

namespace bashkarev\clickhouse;

use ClickHouseDB\Exception\DatabaseException;
use ClickHouseDB\Statement;
use Yii;
use yii\db\Exception;

/**
* @property Connection $db
Expand All @@ -23,12 +26,6 @@ protected function queryInternal($method, $fetchMode = null)
{
$rawSql = $this->getRawSql();

// Add LIMIT 1 for single result SELECT queries to save transmission bandwidth and properly reuse socket
if (in_array($fetchMode, ['fetch', 'fetchColumn']) && !preg_match('/LIMIT\s+\d+$/i', $rawSql)) {
Yii::trace('LIMIT 1 added for single result query explicitly! Try to add LIMIT 1 manually', 'bashkarev\clickhouse\Command::query');
$rawSql = $rawSql.' LIMIT 1';
}

Yii::info($rawSql, 'bashkarev\clickhouse\Command::query');
if ($method !== '') {
$info = $this->db->getQueryCacheInfo($this->queryCacheDuration, $this->queryCacheDependency);
Expand All @@ -50,17 +47,19 @@ protected function queryInternal($method, $fetchMode = null)
}
}
}
$generator = $this->db->execute();

$token = $rawSql;
try {
Yii::beginProfile($token, 'bashkarev\clickhouse\Command::query');
$generator->send($this->createRequest($rawSql, true));
$generator->send(false);
$statement = $this->db->executeSelect($rawSql);
if ($method === '') {
return $generator;
return $statement->rows();
}
$result = call_user_func_array([$this, $method], [$generator, $fetchMode]);
$result = call_user_func_array([$this, $method], [$statement, $fetchMode]);
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
} catch (DatabaseException $e) {
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
throw new Exception($e->getMessage());
} catch (\Exception $e) {
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
throw $e;
Expand All @@ -85,18 +84,16 @@ public function execute()
if ($this->sql == '') {
return 0;
}
$generator = $this->db->execute();

$token = $rawSql;
try {
Yii::beginProfile($token, __METHOD__);
$generator->send($this->createRequest($rawSql, false));
$generator->send(false);
while ($generator->valid()) {
$generator->next();
}
Yii::endProfile($token, __METHOD__);
$statement = $this->db->execute($rawSql);
$this->refreshTableSchema();
return 1;
return (int)(!$statement->isError());
} catch (DatabaseException $e) {
Yii::endProfile($token, __METHOD__);
throw new Exception($e->getMessage());
} catch (\Exception $e) {
Yii::endProfile($token, __METHOD__);
throw $e;
Expand All @@ -110,128 +107,79 @@ public function execute()
*/
public function queryBatchInternal($size)
{
$rawSql = $this->getRawSql();
// TODO: real batch select
$allRows = $this->queryAll();

$count = 0;
$index = 0;
$rows = [];
Yii::info($rawSql, 'bashkarev\clickhouse\Command::query');
$generator = $this->db->execute();
$token = $rawSql;
try {
Yii::beginProfile($token, 'bashkarev\clickhouse\Command::query');
$generator->send($this->createRequest($rawSql, true));
$generator->send(false);
$index = 0;
while ($generator->valid()) {
$count++;
$rows[$index] = $generator->current();
if ($count >= $size) {
yield $rows;
$rows = [];
$count = 0;
}
++$index;
$generator->next();
}
if ($rows !== []) {
foreach ($allRows as $row) {
$count++;
$rows[$index] = $row;
if ($count >= $size) {
yield $rows;
$rows = [];
$count = 0;
}
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
} catch (\Exception $e) {
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
throw $e;
$index++;
}
}

/**
* @param string $table
* @param array $files
* @param array $columns
* @return InsertFiles
*/
public function batchInsertFiles($table, $files = [], $columns = [])
{
return new InsertFiles($this->db, $table, $files, $columns);
}

/**
* @param string $sql
* @param bool $forRead
* @return string
*/
protected function createRequest($sql, $forRead)
{
$data = $sql;
$url = $this->db->getConfiguration()->prepareUrl();
if ($forRead === true) {
$data .= ' FORMAT JSONEachRow';
if ($rows !== []) {
yield $rows;
}
$header = "POST $url HTTP/1.1\r\n";
$header .= "Content-Length: " . strlen($data) . "\r\n";
$header .= "\r\n";
$header .= $data;

return $header;
}

/**
* @param \Generator $generator
* @param \ClickHouseDB\Statement $statement
* @param int $mode
* @return array
*/
protected function fetchAll(\Generator $generator, $mode)
protected function fetchAll(Statement $statement, $mode)
{
$result = [];
if ($mode === \PDO::FETCH_COLUMN) {
while ($generator->valid()) {
$result[] = current($generator->current());
$generator->next();
}
} else {
while ($generator->valid()) {
$result[] = $generator->current();
$generator->next();
}
$result = $statement->rows();

if ($result === null) {
return [];
}
return $result;

if ($mode !== \PDO::FETCH_COLUMN) {
return $result;
}

$firstRow = current($result);
if ($firstRow === false) {
return [];
}

$firstKey = current(array_keys($firstRow));

return array_column($result, $firstKey);

}

/**
* @param \Generator $generator
* @param \ClickHouseDB\Statement $statement
* @param $mode
* @return bool|mixed
*/
protected function fetchColumn(\Generator $generator, $mode)
protected function fetchColumn(Statement $statement, $mode)
{
if (!$generator->valid()) {
$row = $statement->fetchOne();

if ($row === null) {
return false;
}
$result = current($generator->current());
$this->readRest($generator);

return $result;
return current($row);
}

/**
* @param \Generator $generator
* @param \ClickHouseDB\Statement $statement
* @param $mode
* @return bool|mixed
*/
protected function fetch(\Generator $generator, $mode)
protected function fetch(Statement $statement, $mode)
{
if (!$generator->valid()) {
return false;
}
$result = $generator->current();
$this->readRest($generator);

return $result;
}

private function readRest(\Generator $generator)
{
while ($generator->valid()) {
$generator->next();
}
return $statement->fetchOne()??false;
}

}
Loading

0 comments on commit 34bb82d

Please sign in to comment.