Skip to content

Commit 34bb82d

Browse files
authored
Merge pull request #12 from sartor/smi2-driver
Smi2 driver alpha version
2 parents 5829b3c + 6ffc127 commit 34bb82d

29 files changed

+297
-1438
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
language: php
22
php:
3-
- 7
43
- 7.1
54
- 7.2
5+
- 7.3
66

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

21-
script: ./vendor/bin/phpunit
21+
script: ./vendor/bin/phpunit

ArrayExpressionBuilder.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace bashkarev\clickhouse;
4+
5+
6+
use yii\db\ArrayExpression;
7+
use yii\db\ExpressionBuilderInterface;
8+
use yii\db\ExpressionBuilderTrait;
9+
use yii\db\ExpressionInterface;
10+
use yii\db\Query;
11+
use Traversable;
12+
13+
class ArrayExpressionBuilder implements ExpressionBuilderInterface
14+
{
15+
use ExpressionBuilderTrait;
16+
17+
/**
18+
* {@inheritdoc}
19+
* @param ArrayExpression|ExpressionInterface $expression the expression to be built
20+
*/
21+
public function build(ExpressionInterface $expression, array &$params = []): string
22+
{
23+
$value = $expression->getValue();
24+
if ($value === null) {
25+
return 'NULL';
26+
}
27+
28+
if ($value instanceof Query) {
29+
list ($sql, $params) = $this->queryBuilder->build($value, $params);
30+
return $this->buildSubqueryArray($sql);
31+
}
32+
33+
$placeholders = $this->buildPlaceholders($expression, $params);
34+
35+
return '[' . implode(', ', $placeholders) . ']';
36+
}
37+
38+
/**
39+
* Builds placeholders array out of $expression values
40+
* @param ExpressionInterface|ArrayExpression $expression
41+
* @param array $params the binding parameters.
42+
* @return array
43+
*/
44+
protected function buildPlaceholders(ExpressionInterface $expression, &$params): array
45+
{
46+
$value = $expression->getValue();
47+
48+
$placeholders = [];
49+
if ($value === null || (!is_array($value) && !$value instanceof Traversable)) {
50+
return $placeholders;
51+
}
52+
53+
if ($expression->getDimension() > 1) {
54+
foreach ($value as $item) {
55+
$placeholders[] = $this->build($this->unnestArrayExpression($expression, $item), $params);
56+
}
57+
return $placeholders;
58+
}
59+
60+
foreach ($value as $item) {
61+
if ($item instanceof Query) {
62+
list ($sql, $params) = $this->queryBuilder->build($item, $params);
63+
$placeholders[] = $this->buildSubqueryArray($sql);
64+
continue;
65+
}
66+
67+
if ($item instanceof ExpressionInterface) {
68+
$placeholders[] = $this->queryBuilder->buildExpression($item, $params);
69+
continue;
70+
}
71+
72+
$placeholders[] = $this->queryBuilder->bindParam($item, $params);
73+
}
74+
75+
return $placeholders;
76+
}
77+
78+
private function unnestArrayExpression(ArrayExpression $expression, $value): ArrayExpression
79+
{
80+
$expressionClass = get_class($expression);
81+
82+
return new $expressionClass($value, $expression->getType(), $expression->getDimension() - 1);
83+
}
84+
85+
protected function buildSubqueryArray($sql): string
86+
{
87+
return "array({$sql})";
88+
}
89+
}

ColumnSchema.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace bashkarev\clickhouse;
44

5+
use yii\db\ArrayExpression;
56
use yii\db\Expression;
67

78
/**
@@ -20,6 +21,10 @@ public function dbTypecast($value)
2021
$value = new Expression($value);
2122
}
2223

24+
if (strpos($this->dbType, 'Array(') === 0) {
25+
return new ArrayExpression($value, $this->type);
26+
}
27+
2328
return parent::dbTypecast($value);
2429
}
2530
}

Command.php

Lines changed: 58 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88
namespace bashkarev\clickhouse;
99

10+
use ClickHouseDB\Exception\DatabaseException;
11+
use ClickHouseDB\Statement;
1012
use Yii;
13+
use yii\db\Exception;
1114

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

26-
// Add LIMIT 1 for single result SELECT queries to save transmission bandwidth and properly reuse socket
27-
if (in_array($fetchMode, ['fetch', 'fetchColumn']) && !preg_match('/LIMIT\s+\d+$/i', $rawSql)) {
28-
Yii::trace('LIMIT 1 added for single result query explicitly! Try to add LIMIT 1 manually', 'bashkarev\clickhouse\Command::query');
29-
$rawSql = $rawSql.' LIMIT 1';
30-
}
31-
3229
Yii::info($rawSql, 'bashkarev\clickhouse\Command::query');
3330
if ($method !== '') {
3431
$info = $this->db->getQueryCacheInfo($this->queryCacheDuration, $this->queryCacheDependency);
@@ -50,17 +47,19 @@ protected function queryInternal($method, $fetchMode = null)
5047
}
5148
}
5249
}
53-
$generator = $this->db->execute();
50+
5451
$token = $rawSql;
5552
try {
5653
Yii::beginProfile($token, 'bashkarev\clickhouse\Command::query');
57-
$generator->send($this->createRequest($rawSql, true));
58-
$generator->send(false);
54+
$statement = $this->db->executeSelect($rawSql);
5955
if ($method === '') {
60-
return $generator;
56+
return $statement->rows();
6157
}
62-
$result = call_user_func_array([$this, $method], [$generator, $fetchMode]);
58+
$result = call_user_func_array([$this, $method], [$statement, $fetchMode]);
59+
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
60+
} catch (DatabaseException $e) {
6361
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
62+
throw new Exception($e->getMessage());
6463
} catch (\Exception $e) {
6564
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
6665
throw $e;
@@ -85,18 +84,16 @@ public function execute()
8584
if ($this->sql == '') {
8685
return 0;
8786
}
88-
$generator = $this->db->execute();
87+
8988
$token = $rawSql;
9089
try {
9190
Yii::beginProfile($token, __METHOD__);
92-
$generator->send($this->createRequest($rawSql, false));
93-
$generator->send(false);
94-
while ($generator->valid()) {
95-
$generator->next();
96-
}
97-
Yii::endProfile($token, __METHOD__);
91+
$statement = $this->db->execute($rawSql);
9892
$this->refreshTableSchema();
99-
return 1;
93+
return (int)(!$statement->isError());
94+
} catch (DatabaseException $e) {
95+
Yii::endProfile($token, __METHOD__);
96+
throw new Exception($e->getMessage());
10097
} catch (\Exception $e) {
10198
Yii::endProfile($token, __METHOD__);
10299
throw $e;
@@ -110,128 +107,79 @@ public function execute()
110107
*/
111108
public function queryBatchInternal($size)
112109
{
113-
$rawSql = $this->getRawSql();
110+
// TODO: real batch select
111+
$allRows = $this->queryAll();
112+
114113
$count = 0;
114+
$index = 0;
115115
$rows = [];
116-
Yii::info($rawSql, 'bashkarev\clickhouse\Command::query');
117-
$generator = $this->db->execute();
118-
$token = $rawSql;
119-
try {
120-
Yii::beginProfile($token, 'bashkarev\clickhouse\Command::query');
121-
$generator->send($this->createRequest($rawSql, true));
122-
$generator->send(false);
123-
$index = 0;
124-
while ($generator->valid()) {
125-
$count++;
126-
$rows[$index] = $generator->current();
127-
if ($count >= $size) {
128-
yield $rows;
129-
$rows = [];
130-
$count = 0;
131-
}
132-
++$index;
133-
$generator->next();
134-
}
135-
if ($rows !== []) {
116+
foreach ($allRows as $row) {
117+
$count++;
118+
$rows[$index] = $row;
119+
if ($count >= $size) {
136120
yield $rows;
121+
$rows = [];
122+
$count = 0;
137123
}
138-
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
139-
} catch (\Exception $e) {
140-
Yii::endProfile($token, 'bashkarev\clickhouse\Command::query');
141-
throw $e;
124+
$index++;
142125
}
143-
}
144-
145-
/**
146-
* @param string $table
147-
* @param array $files
148-
* @param array $columns
149-
* @return InsertFiles
150-
*/
151-
public function batchInsertFiles($table, $files = [], $columns = [])
152-
{
153-
return new InsertFiles($this->db, $table, $files, $columns);
154-
}
155126

156-
/**
157-
* @param string $sql
158-
* @param bool $forRead
159-
* @return string
160-
*/
161-
protected function createRequest($sql, $forRead)
162-
{
163-
$data = $sql;
164-
$url = $this->db->getConfiguration()->prepareUrl();
165-
if ($forRead === true) {
166-
$data .= ' FORMAT JSONEachRow';
127+
if ($rows !== []) {
128+
yield $rows;
167129
}
168-
$header = "POST $url HTTP/1.1\r\n";
169-
$header .= "Content-Length: " . strlen($data) . "\r\n";
170-
$header .= "\r\n";
171-
$header .= $data;
172-
173-
return $header;
174130
}
175131

176132
/**
177-
* @param \Generator $generator
133+
* @param \ClickHouseDB\Statement $statement
178134
* @param int $mode
179135
* @return array
180136
*/
181-
protected function fetchAll(\Generator $generator, $mode)
137+
protected function fetchAll(Statement $statement, $mode)
182138
{
183-
$result = [];
184-
if ($mode === \PDO::FETCH_COLUMN) {
185-
while ($generator->valid()) {
186-
$result[] = current($generator->current());
187-
$generator->next();
188-
}
189-
} else {
190-
while ($generator->valid()) {
191-
$result[] = $generator->current();
192-
$generator->next();
193-
}
139+
$result = $statement->rows();
140+
141+
if ($result === null) {
142+
return [];
194143
}
195-
return $result;
144+
145+
if ($mode !== \PDO::FETCH_COLUMN) {
146+
return $result;
147+
}
148+
149+
$firstRow = current($result);
150+
if ($firstRow === false) {
151+
return [];
152+
}
153+
154+
$firstKey = current(array_keys($firstRow));
155+
156+
return array_column($result, $firstKey);
157+
196158
}
197159

198160
/**
199-
* @param \Generator $generator
161+
* @param \ClickHouseDB\Statement $statement
200162
* @param $mode
201163
* @return bool|mixed
202164
*/
203-
protected function fetchColumn(\Generator $generator, $mode)
165+
protected function fetchColumn(Statement $statement, $mode)
204166
{
205-
if (!$generator->valid()) {
167+
$row = $statement->fetchOne();
168+
169+
if ($row === null) {
206170
return false;
207171
}
208-
$result = current($generator->current());
209-
$this->readRest($generator);
210172

211-
return $result;
173+
return current($row);
212174
}
213175

214176
/**
215-
* @param \Generator $generator
177+
* @param \ClickHouseDB\Statement $statement
216178
* @param $mode
217179
* @return bool|mixed
218180
*/
219-
protected function fetch(\Generator $generator, $mode)
181+
protected function fetch(Statement $statement, $mode)
220182
{
221-
if (!$generator->valid()) {
222-
return false;
223-
}
224-
$result = $generator->current();
225-
$this->readRest($generator);
226-
227-
return $result;
228-
}
229-
230-
private function readRest(\Generator $generator)
231-
{
232-
while ($generator->valid()) {
233-
$generator->next();
234-
}
183+
return $statement->fetchOne()??false;
235184
}
236-
237185
}

0 commit comments

Comments
 (0)