Fix parameter numbering with whereIn

This commit is contained in:
Denis Glushkov
2024-09-10 13:19:38 +05:00
parent 9a13f23674
commit 3fb1861284
10 changed files with 353 additions and 2 deletions
+5
View File
@@ -1,3 +1,8 @@
## 2.1.0 [2024-09-10]
### Bug fixes
1. Parameter numbering with whereIn
## 2.0.0 [2024-07-26]
### Features
+7
View File
@@ -44,6 +44,7 @@ $ composer require glushkovds/phpclickhouse-laravel
'settings' => [ // optional
'max_partitions_per_insert_block' => 300,
],
'fix_default_query_builder' => true,
],
```
@@ -171,6 +172,10 @@ $rows = MyTable::select(['field_one', new RawColumn('sum(field_two)', 'field_two
->getRows();
```
## Known issues
[Some of the problems are described here](/docs/known_issues.md).
## Advanced usage
### Columns casting
@@ -344,6 +349,7 @@ MyTable::insertAssoc([[1, 'str', new InsertArray(['a','b'])]]);
'timeout_query' => 2,
'https' => false,
'retries' => 0,
'fix_default_query_builder' => true,
],
```
@@ -415,6 +421,7 @@ Your config/database.php should look like:
'settings' => [ // optional
'max_partitions_per_insert_block' => 300,
],
'fix_default_query_builder' => true,
],
```
+3
View File
@@ -0,0 +1,3 @@
```shell
sudo sh -c "docker compose -f docker-compose.test.yaml down --remove-orphans -v && docker compose -f docker-compose.test.yaml rm -f -v && docker compose -f docker-compose.test.yaml run php sh /src/tests.bootstrap.sh"
```
+57
View File
@@ -0,0 +1,57 @@
## Parameter numbering with whereIn
The problem is described here: https://github.com/glushkovds/phpclickhouse-laravel/pull/34
The reason for the problem is that for the `DB::table('my-table')` construction
the builder used was not from this library `\PhpClickHouseLaravel\Builder`,
but the standard `\Illuminate\Database\Query\Builder`.
### To fix this problem
Add `'fix_default_query_builder' => true,` to connection config in `config/database.php` like this:
```php
'clickhouse' => [
'driver' => 'clickhouse',
'host' => env('CLICKHOUSE_HOST'),
'port' => env('CLICKHOUSE_PORT','8123'),
'database' => env('CLICKHOUSE_DATABASE','default'),
'username' => env('CLICKHOUSE_USERNAME','default'),
'password' => env('CLICKHOUSE_PASSWORD',''),
'timeout_connect' => env('CLICKHOUSE_TIMEOUT_CONNECT',2),
'timeout_query' => env('CLICKHOUSE_TIMEOUT_QUERY',2),
'https' => (bool)env('CLICKHOUSE_HTTPS', null),
'retries' => env('CLICKHOUSE_RETRIES', 0),
'settings' => [ // optional
'max_partitions_per_insert_block' => 300,
],
'fix_default_query_builder' => true,
],
```
### Breaking changes
This fix will break your existing code if you use constructs like this:
```php
$rows = DB::table('my-table')
...
->get(); // Collection
```
You need to change your code to:
```php
$rows = DB::table('my-table')
...
->get() // Statement
->rows(); // array
```
### Why not use \Illuminate\Database\Query\Builder
Because this library use [the-tinderbox/ClickhouseBuilder](https://github.com/the-tinderbox/ClickhouseBuilder),
which offers its own builder.
It offers special methods for working with Clickhouse, which are not available in the standard builder.
And vice versa, the standard builder has methods that cannot be implemented for Clickhouse.
### In the feature release v3
In the feature release v3 this fix will be applied by default.
+1
View File
@@ -13,6 +13,7 @@ class Builder extends BaseBuilder
{
use WithClient;
use BuilderMethodsFromLaravel;
/** @var string */
protected $tableSources;
+252
View File
@@ -0,0 +1,252 @@
<?php
namespace PhpClickHouseLaravel;
use Illuminate\Contracts\Database\Query\Expression as ExpressionContract;
use Tinderbox\ClickhouseBuilder\Query\Expression;
trait BuilderMethodsFromLaravel
{
public function useWritePdo()
{
return $this;
}
/**
* Get a collection instance containing the values of a given column.
*
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
* @param string|null $key
* @return \Illuminate\Support\Collection
*/
public function pluck($column, $key = null)
{
// First, we will need to select the results of the query accounting for the
// given columns / key. Once we have the results, we will be able to take
// the results and get the exact data that was requested for the query.
$queryResult = $this->onceWithColumns(
is_null($key) ? [$column] : [$column, $key],
function () {
return $this->runSelect();
}
);
if (empty($queryResult)) {
return collect();
}
// If the columns are qualified with a table or have an alias, we cannot use
// those directly in the "pluck" operations since the results from the DB
// are only keyed by the column itself. We'll strip the table out here.
$column = $this->stripTableForPluck($column);
$key = $this->stripTableForPluck($key);
return is_array($queryResult[0])
? $this->pluckFromArrayColumn($queryResult, $column, $key)
: $this->pluckFromObjectColumn($queryResult, $column, $key);
}
/**
* Strip off the table name or alias from a column identifier.
*
* @param string $column
* @return string|null
*/
protected function stripTableForPluck($column)
{
if (is_null($column)) {
return $column;
}
$columnString = $column instanceof ExpressionContract
? $column->getValue(new \Illuminate\Database\Query\Grammars\Grammar())
: $column;
$separator = str_contains(strtolower($columnString), ' as ') ? ' as ' : '\.';
return last(preg_split('~' . $separator . '~i', $columnString));
}
/**
* Retrieve column values from rows represented as objects.
*
* @param array $queryResult
* @param string $column
* @param string $key
* @return \Illuminate\Support\Collection
*/
protected function pluckFromObjectColumn($queryResult, $column, $key)
{
$results = [];
if (is_null($key)) {
foreach ($queryResult as $row) {
$results[] = $row->$column;
}
} else {
foreach ($queryResult as $row) {
$results[$row->$key] = $row->$column;
}
}
return collect($results);
}
/**
* Retrieve column values from rows represented as arrays.
*
* @param array $queryResult
* @param string $column
* @param string $key
* @return \Illuminate\Support\Collection
*/
protected function pluckFromArrayColumn($queryResult, $column, $key)
{
$results = [];
if (is_null($key)) {
foreach ($queryResult as $row) {
$results[] = $row[$column];
}
} else {
foreach ($queryResult as $row) {
$results[$row[$key]] = $row[$column];
}
}
return collect($results);
}
/**
* Execute the given callback while selecting the given columns.
*
* After running the callback, the columns are reset to the original value.
*
* @param array $columns
* @param callable $callback
* @return mixed
*/
protected function onceWithColumns($columns, $callback)
{
$original = $this->columns;
if (is_null($original)) {
$this->columns = $columns;
}
$result = $callback();
$this->columns = $original;
return $result;
}
/**
* Run the query as a "select" statement against the connection.
*
* @return array
*/
protected function runSelect()
{
return $this->getRows();
}
/**
* Retrieve the minimum value of a given column.
*
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
* @return mixed
*/
public function min($column)
{
return $this->aggregate(__FUNCTION__, [$column]);
}
/**
* Retrieve the maximum value of a given column.
*
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
* @return mixed
*/
public function max($column)
{
return $this->aggregate(__FUNCTION__, [$column]);
}
/**
* Retrieve the sum of the values of a given column.
*
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
* @return mixed
*/
public function sum($column)
{
$result = $this->aggregate(__FUNCTION__, [$column]);
return $result ?: 0;
}
/**
* Retrieve the average of the values of a given column.
*
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
* @return mixed
*/
public function avg($column)
{
return $this->aggregate(__FUNCTION__, [$column]);
}
/**
* Alias for the "avg" method.
*
* @param \Illuminate\Contracts\Database\Query\Expression|string $column
* @return mixed
*/
public function average($column)
{
return $this->avg($column);
}
/**
* Execute an aggregate function on the database.
*
* @param string $function
* @param array $columns
* @return mixed
*/
public function aggregate($function, $columns = ['*'])
{
$results = $this->cloneWithout($this->unions || $this->havings ? [] : ['columns'])
->setAggregate($function, $columns)
->getRows();
if ($results) {
return array_change_key_case((array)$results[0])['aggregate'];
}
return null;
}
/**
* Set the aggregate property without running the query.
*
* @param string $function
* @param array $columns
* @return $this
*/
protected function setAggregate($function, $columns)
{
$this->select(new Expression("$function(" . implode(',', $columns) . ") AS aggregate"));
return $this;
}
public function insert(array $values)
{
$table = $this->tableSources ?? (string)$this->getFrom()->getTable();
$this->client->insertAssocBulk($table, $values);
}
}
+13
View File
@@ -99,4 +99,17 @@ class Connection extends BaseConnection
return $result;
}
/**
* Get a new query builder instance.
*
* @return Builder|\Illuminate\Database\Query\Builder
*/
public function query()
{
if ($this->config['fix_default_query_builder'] ?? false) {
return new Builder();
}
return parent::query();
}
}
-1
View File
@@ -11,7 +11,6 @@ class BaseTest extends TestCase
{
public function testWorkWithClient()
{
Artisan::call('migrate');
/** @var \ClickHouseDB\Client $db */
$db = DB::connection('clickhouse')->getClient();
$db->write("TRUNCATE TABLE examples");
+14 -1
View File
@@ -2,12 +2,13 @@
namespace Tests;
use Illuminate\Support\Facades\DB;
use PhpClickHouseLaravel\Builder;
use Tests\Models\Example;
class BindingsTest extends TestCase
{
public function testBindings()
public function testBindingsByModel()
{
$query = Example::select()
->where(function (Builder $q) {
@@ -18,4 +19,16 @@ class BindingsTest extends TestCase
->whereIn('f_int2', [10, 11]);
$this->assertEquals("SELECT * FROM `examples` WHERE (`f_int` = 1 OR `f_int` = 2 OR `f_int` = 3) AND `f_int2` IN (10, 11)", $query->toSql());
}
public function testBindingsByTableMethod()
{
$query = DB::table('examples')
->where(function (Builder $q) {
$q->where('f_int', 1)
->orWhere('f_int', 2)
->orWhere('f_int', 3);
})
->whereIn('f_int2', [10, 11]);
$this->assertEquals("SELECT * FROM `examples` WHERE (`f_int` = 1 OR `f_int` = 2 OR `f_int` = 3) AND `f_int2` IN (10, 11)", $query->toSql());
}
}
+1
View File
@@ -49,6 +49,7 @@ return [
'settings' => [
'max_partitions_per_insert_block' => 300,
],
'fix_default_query_builder' => true,
],
'clickhouse2' => [