Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<env name="CLICKHOUSE_PORT" value="8123" />
<env name="CLICKHOUSE_USER" value="default" />
<env name="CLICKHOUSE_PASSWORD" value="" />
<env name="CLICKHOUSE_DATABASE" value="php_clickhouse" />
<env name="CLICKHOUSE_TMPPATH" value="/tmp" />
</php>

Expand Down
20 changes: 20 additions & 0 deletions src/Exception/UnsupportedParameterType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace ClickHouseDB\Exception;

use InvalidArgumentException;
use function gettype;
use function sprintf;

final class UnsupportedParameterType extends InvalidArgumentException implements ClickHouseException
{
/**
* @param mixed $parameter
*/
public static function new($parameter) : self
{
return new self(sprintf('Parameter of type "%s" cannot be bound', gettype($parameter)));
}
}
105 changes: 43 additions & 62 deletions src/Query/Degeneration/Bindings.php
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
<?php

declare(strict_types=1);

namespace ClickHouseDB\Query\Degeneration;

use ClickHouseDB\Exception\UnsupportedParameterType;
use ClickHouseDB\Query\Degeneration;
use DateTimeInterface;
use function array_map;
use function implode;
use function is_array;
use function is_bool;
use function is_callable;
use function is_float;
use function is_int;
use function is_object;
use function is_string;
use function sprintf;
use function str_ireplace;

class Bindings implements \ClickHouseDB\Query\Degeneration
class Bindings implements Degeneration
{
/**
* @var array
*/
protected $bindings = [];


/**
* @param array $bindings
*/
Expand All @@ -37,10 +42,6 @@ public function bindParams(array $bindings)
*/
public function bindParam($column, $value)
{
if ($value instanceof DateTimeInterface) {
$value = $value->format('Y-m-d H:i:s');
}

$this->bindings[$column] = $value;
}

Expand All @@ -52,32 +53,9 @@ public function bindParam($column, $value)
*/
private function escapeString($value)
{
// return str_replace("'", "''", remove_invisible_characters($str, FALSE));
return addslashes($value);
}

/**
* Escape an array
*
* @param array $values
* @return array
*/
private function escapeArray($values)
{
$escapedValues = [];
foreach ($values as $value) {
if (is_numeric($value)) {
$escapedValues[] = $value;
} elseif (is_string($value)) {
$escapedValues[] = $this->escapeString($value);
} elseif (is_array($value)) {
$escapedValues[] = $this->escapeArray($value);
}
}

return $escapedValues;
}

/**
* Binds a list of values to the corresponding parameters.
* This is similar to [[bindValue()]] except that it binds multiple values at a time.
Expand All @@ -92,13 +70,12 @@ public function compile_binds($sql, $binds,$pattern)
return preg_replace_callback($pattern, function($m) use ($binds){
if(isset($binds[$m[1]])){ // If it exists in our array
return $binds[$m[1]]; // Then replace it from our array
}else{
return $m[0]; // Otherwise return the whole match (basically we won't change it)
}

return $m[0]; // Otherwise return the whole match (basically we won't change it)
}, $sql);
}


/**
* Compile Bindings
*
Expand All @@ -107,44 +84,23 @@ public function compile_binds($sql, $binds,$pattern)
*/
public function process($sql)
{
arsort($this->bindings);

$bindFormatted=[];
$bindRaw=[];
foreach ($this->bindings as $key => $value) {
$valueSet = null;
$formattedParameter = null;

if ($value === null || $value === false) {
$formattedParameter = '';
}

if (is_array($value)) {
$escapedValues = $this->escapeArray($value);

$escapedValues = array_map(
function ($escapedValue) {
if (is_string($escapedValue)) {
return $this->formatStringParameter($escapedValue);
}
$valueSet = implode(', ', $value);

return $escapedValue;
$values = array_map(
function ($value) {
return $this->formatParameter($value);
},
$escapedValues
$value
);

$formattedParameter = implode(',', $escapedValues);
$valueSet = implode(', ', $escapedValues);
}

if (is_float($value) || is_int($value)) {
$formattedParameter = $value;
$valueSet = $value;
}

if (is_string($value)) {
$formattedParameter = implode(',', $values);
} else {
$valueSet = $value;
$formattedParameter = $this->formatStringParameter($this->escapeString($value));
$formattedParameter = $this->formatParameter($value);
}

if ($formattedParameter !== null) {
Expand All @@ -167,6 +123,31 @@ function ($escapedValue) {
return $sql;
}

/**
* @param mixed $value
* @return mixed
*/
private function formatParameter($value)
{
if ($value instanceof DateTimeInterface) {
$value = $value->format('Y-m-d H:i:s');
}

if (is_float($value) || is_int($value) || is_bool($value) || $value === null) {
return $value;
}

if (is_object($value) && is_callable([$value, '__toString'])) {
$value = (string) $value;
}

if (is_string($value)) {
return $this->formatStringParameter($this->escapeString($value));
}

throw UnsupportedParameterType::new($value);
}

/**
* @return string
*/
Expand Down
36 changes: 35 additions & 1 deletion tests/BindingsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace ClickHouseDB\Tests;

use ClickHouseDB\Exception\UnsupportedParameterType;
use ClickHouseDB\Query\Degeneration\Bindings;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use function curl_init;

/**
* Class BindingsTest
Expand All @@ -30,6 +33,26 @@ public function escapeDataProvider()
['id' => '1'],
"select * from test. WHERE id = '1'",
],
[
'select * from test. WHERE date_column = :dateParam',
['dateParam' => new DateTimeImmutable('2018-08-31 23:54:02')],
"select * from test. WHERE date_column = '2018-08-31 23:54:02'",
],
[
'select * from test. WHERE a_column = :objectWithToString',
[
'objectWithToString' => new class() {
/**
* @return string
*/
public function __toString()
{
return 'expectedValue';
}
},
],
"select * from test. WHERE a_column = 'expectedValue'",
],
[
'select * from test. WHERE id IN (:id)',
['id' => [1, 2]],
Expand Down Expand Up @@ -153,13 +176,24 @@ public function testBindselectAsync()
*/
public function testEscape($sql, $params, $expectedSql)
{

$bindings = new Bindings();
$bindings->bindParams($params);
$sql = $bindings->process($sql);
$this->assertSame($expectedSql, $sql);
}

/**
* @return void
*/
public function testEscapeFail()
{
$this->expectException(UnsupportedParameterType::class);

$bindings = new Bindings();
$bindings->bindParams(['unsupportedParam' => curl_init()]);
$bindings->process('SELECT * FROM test WHERE id = :unsupportedParam');
}

public function testSelectAsKeys()
{
// chr(0....255);
Expand Down
5 changes: 2 additions & 3 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -504,11 +504,10 @@ private function make_csv_SelectWhereIn($file_name, $array)
fclose($handle);
}

/**
*
*/
public function testSelectWhereIn()
{
$this->create_table_summing_url_views();

$file_data_names = [
$this->tmpPath . '_testInsertCSV_clickHouseDB_test.1.data'
];
Expand Down
8 changes: 8 additions & 0 deletions tests/WithClient.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<?php

declare(strict_types=1);

namespace ClickHouseDB\Tests;

use ClickHouseDB\Client;
use function getenv;
use function sprintf;

trait WithClient
{
Expand Down Expand Up @@ -30,5 +34,9 @@ public function restartClickHouseClient()

];
$this->client = new Client($config);
$databaseName = getenv('CLICKHOUSE_DATABASE');
$this->client->write(sprintf('DROP DATABASE IF EXISTS "%s"', $databaseName));
$this->client->write(sprintf('CREATE DATABASE "%s"', $databaseName));
$this->client->database($databaseName);
}
}