Skip to content

Commit

Permalink
Added query with 'with' support
Browse files Browse the repository at this point in the history
  • Loading branch information
kruglov committed Aug 13, 2024
1 parent 57c4676 commit cb1298d
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 45 deletions.
61 changes: 26 additions & 35 deletions src/ClickHouseStatement.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,6 @@
use Doctrine\DBAL\Platforms\AbstractPlatform;
use FOD\DBALClickHouse\Driver\Exception\Exception;

use function array_map;
use function array_replace;
use function current;
use function implode;
use function is_array;
use function is_bool;
use function is_float;
use function is_int;
use function mb_stripos;

class ClickHouseStatement implements Statement
{
protected Client $client;
Expand All @@ -47,9 +37,9 @@ class ClickHouseStatement implements Statement

public function __construct(Client $client, string $statement, AbstractPlatform $platform)
{
$this->client = $client;
$this->client = $client;
$this->statement = $statement;
$this->platform = $platform;
$this->platform = $platform;
}

/**
Expand All @@ -58,7 +48,7 @@ public function __construct(Client $client, string $statement, AbstractPlatform
public function bindValue($param, $value, $type = ParameterType::STRING): bool
{
$this->values[$param] = $value;
$this->types[$param] = $type;
$this->types[$param] = $type;

return true;
}
Expand All @@ -69,7 +59,7 @@ public function bindValue($param, $value, $type = ParameterType::STRING): bool
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool
{
$this->values[$param] = $variable;
$this->types[$param] = $type;
$this->types[$param] = $type;

return true;
}
Expand All @@ -79,16 +69,16 @@ public function bindParam($param, &$variable, $type = ParameterType::STRING, $le
*/
public function execute($params = null): Result
{
if (is_array($params)) {
$this->values = array_replace($this->values, $params);
if (\is_array($params)) {
$this->values = \array_replace($this->values, $params);
}

$statement = $this->statement;

$firstPlaceholder = array_key_first($this->values);

$positionalPlaceholders = is_int($firstPlaceholder);
$positionalPlaceholdersIsList = $firstPlaceholder === 0;
$positionalPlaceholders = \is_int($firstPlaceholder);
$positionalPlaceholdersIsList = 0 === $firstPlaceholder;

if ($positionalPlaceholders) {
$pieces = explode('?', $statement);
Expand All @@ -101,14 +91,14 @@ public function execute($params = null): Result
}
}

$statement = implode('', $pieces);
$statement = \implode('', $pieces);
} else {
foreach (array_keys($this->values) as $key) {
$namedPlaceholder = ":$key";
$namedPlaceholderOffset = mb_stripos($statement, $namedPlaceholder);
$namedPlaceholder = ":$key";
$namedPlaceholderOffset = \mb_stripos($statement, $namedPlaceholder);
$namedPlaceholderLength = mb_strlen($namedPlaceholder);

if ($namedPlaceholderOffset !== false) {
if (false !== $namedPlaceholderOffset) {
$statement = substr_replace(
$statement,
$this->resolveType($key),
Expand All @@ -122,9 +112,10 @@ public function execute($params = null): Result
try {
return new ClickHouseResult(
new \ArrayIterator(
mb_stripos($statement, 'select') === 0 ||
mb_stripos($statement, 'show') === 0 ||
mb_stripos($statement, 'describe') === 0
0 === \mb_stripos($statement, 'select')
|| 1 === preg_match('/with(.*)\)\s*select/ms', mb_strtolower($statement))
|| 0 === \mb_stripos($statement, 'show')
|| 0 === \mb_stripos($statement, 'describe')
? $this->client->select($statement)->rows()
: $this->client->write($statement)->rows()
)
Expand All @@ -141,32 +132,32 @@ protected function resolveType(int|string $key): string
{
$value = $this->values[$key];

if ($value === null) {
if (null === $value) {
return 'NULL';
}

if (is_array($value)) {
if (is_int(current($value)) || is_float(current($value))) {
if (\is_array($value)) {
if (\is_int(\current($value)) || \is_float(\current($value))) {
foreach ($value as $item) {
if (!is_int($item) && !is_float($item)) {
if (!\is_int($item) && !\is_float($item)) {
throw new DBALException('Array values must all be int/float or string, mixes not allowed');
}
}
} else {
$value = array_map(function (?string $item): string {
return $item === null ? 'NULL' : $this->platform->quoteStringLiteral($item);
$value = \array_map(function (?string $item): string {
return null === $item ? 'NULL' : $this->platform->quoteStringLiteral($item);
}, $value);
}

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

$type = $this->types[$key] ?? null;

if ($type === null) {
if (is_int($value) || is_float($value)) {
if (null === $type) {
if (\is_int($value) || \is_float($value)) {
$type = ParameterType::INTEGER;
} elseif (is_bool($value)) {
} elseif (\is_bool($value)) {
$type = ParameterType::BOOLEAN;
}
}
Expand Down
37 changes: 27 additions & 10 deletions tests/SelectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class SelectTest extends TestCase
{
private Connection $connection;

public function setUp(): void
protected function setUp(): void
{
$this->connection = CreateConnectionTest::createConnection();

Expand All @@ -50,7 +50,7 @@ public function setUp(): void
$this->connection->executeStatement("INSERT INTO test_select_table(id, payload, hits) VALUES (1, 'v1', 101), (2, 'v2', 202), (3, 'v3', 303), (4, 'v4', 404), (5, 'v4', 505), (6, ' t1 ', 606), (7, 'aat2aaa', 707)");
}

public function tearDown(): void
protected function tearDown(): void
{
$this->connection->executeStatement('DROP TABLE test_select_table');
}
Expand Down Expand Up @@ -81,7 +81,7 @@ public function testFetchAssocSelect(): void
$this->assertEquals([['id' => 3, 'hits' => 303], ['id' => 4, 'hits' => 404]], $results);
}

public function testFetchNumSelect():void
public function testFetchNumSelect(): void
{
$result = $this->connection->executeQuery('SELECT MAX(hits) as maxHits FROM test_select_table');

Expand All @@ -90,7 +90,7 @@ public function testFetchNumSelect():void

public function testFetchAllBothSelect(): void
{
$result = $this->connection->executeQuery("SELECT * FROM test_select_table WHERE id IN (1, 3)");
$result = $this->connection->executeQuery('SELECT * FROM test_select_table WHERE id IN (1, 3)');

$this->assertEquals([
[
Expand All @@ -102,13 +102,13 @@ public function testFetchAllBothSelect(): void
'id' => 3,
'payload' => 'v3',
'hits' => 303,
]
],
], $result->fetchAllAssociative());
}

public function testFetchAllNumSelect(): void
{
$result = $this->connection->executeQuery("SELECT AVG(hits) FROM test_select_table");
$result = $this->connection->executeQuery('SELECT AVG(hits) FROM test_select_table');

$this->assertEquals([[404]], $result->fetchAllNumeric());
}
Expand All @@ -117,7 +117,7 @@ public function testFetchColumnValidOffsetSelect(): void
{
$results = [];

$result = $this->connection->executeQuery("SELECT payload, hits FROM test_select_table WHERE id > 1 ORDER BY id LIMIT 3");
$result = $this->connection->executeQuery('SELECT payload, hits FROM test_select_table WHERE id > 1 ORDER BY id LIMIT 3');

while ($row = $result->fetchNumeric()) {
$results[] = $row[1];
Expand All @@ -130,7 +130,7 @@ public function testFetchColumnInvalidOffsetSelect(): void
{
$results = [];

$result = $this->connection->executeQuery("SELECT payload, hits FROM test_select_table WHERE id > 1 ORDER BY id");
$result = $this->connection->executeQuery('SELECT payload, hits FROM test_select_table WHERE id > 1 ORDER BY id');

while ($row = $result->fetchOne()) {
$results[] = $row;
Expand All @@ -151,7 +151,8 @@ public function testQueryBuilderSelect(): void
->groupBy('payload')
->orderBy('payload')
->setMaxResults(2)
->fetchAllAssociative();
->fetchAllAssociative()
;

$this->assertEquals([
[
Expand All @@ -161,7 +162,7 @@ public function testQueryBuilderSelect(): void
[
'payload' => 'aat2aaa',
'uniques' => '1',
]
],
], $result);
}

Expand Down Expand Up @@ -243,4 +244,20 @@ public function testTrimChar(): void

$this->assertEquals('t2', $result->fetchOne());
}

public function testWith(): void
{
$result = $this->connection->executeQuery("
WITH subselect as (
SELECT id
FROM test_select_table
WHERE payload = 'v4'
)
SELECT *
FROM test_select_table tbl
JOIN subselect sub ON sub.id = tbl.id
");

$this->assertEquals(2, $result->columnCount());
}
}

0 comments on commit cb1298d

Please sign in to comment.