Skip to content

Commit 10cd5a9

Browse files
committed
Added column casting feature for data insertion
1 parent 7af0c5b commit 10cd5a9

File tree

10 files changed

+151
-6
lines changed

10 files changed

+151
-6
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 1.19.0 [2023-09-28]
2+
3+
### Features
4+
1. Added column casting feature for data insertion
5+
16
## 1.18.0 [2023-07-10]
27

38
### Features

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,32 @@ $rows = MyTable::select(['field_one', new RawColumn('sum(field_two)', 'field_two
175175

176176
## Advanced usage
177177

178+
### Columns casting
179+
180+
Before insertion, the column will be converted to the required data type specified in the field `$casts`.
181+
This feature does not apply to data selection.
182+
The supported cast types are: `boolean`.
183+
184+
```php
185+
namespace App\Models\Clickhouse;
186+
187+
use PhpClickHouseLaravel\BaseModel;
188+
189+
class MyTable extends BaseModel
190+
{
191+
/**
192+
* The columns that should be cast.
193+
*
194+
* @var array
195+
*/
196+
protected $casts = ['some_bool_column' => 'boolean'];
197+
}
198+
// Then you can insert the data like this:
199+
MyTable::insertAssoc([
200+
['some_param' => 1, 'some_bool_column' => false],
201+
]);
202+
```
203+
178204
### Events
179205

180206
Events work just like an [eloquent model events](https://laravel.com/docs/9.x/eloquent#events)

docker-compose.test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ services:
1818
soft: 262144
1919
hard: 262144
2020
ports:
21-
- "8124:8123"
21+
- "18123:8123"
2222

2323
clickhouse2:
2424
image: yandex/clickhouse-server

src/BaseModel.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,7 @@ public function save(array $options = []): bool
161161
throw new Exception("Clickhouse does not allow update rows");
162162
}
163163
$this->exists = !static::insertAssoc([$this->getAttributes()])->isError();
164-
165164
$this->fireModelEvent('saved', false);
166-
167165
return $this->exists;
168166
}
169167

@@ -189,6 +187,17 @@ public static function insert(array $rows): Statement
189187
public static function insertBulk(array $rows, array $columns = []): Statement
190188
{
191189
$instance = new static();
190+
if ($castsAssoc = (new static())->casts) {
191+
$casts = [];
192+
foreach ($castsAssoc as $castColumn => $castType) {
193+
if ($index = array_search($castColumn, $columns)) {
194+
$casts[$index] = $castType;
195+
}
196+
}
197+
foreach ($rows as &$row) {
198+
$row = static::castRow($row, $casts);
199+
}
200+
}
192201
return $instance->getThisClient()->insert($instance->getTableForInserts(), $rows, $columns);
193202
}
194203

@@ -220,6 +229,11 @@ public static function insertAssoc(array $rows): Statement
220229
$row = array_replace(array_flip($keys), $row);
221230
}
222231
}
232+
if ($casts = (new static())->casts) {
233+
foreach ($rows as &$row) {
234+
$row = static::castRow($row, $casts);
235+
}
236+
}
223237
$instance = new static();
224238
return $instance->getThisClient()->insertAssocBulk($instance->getTableForInserts(), $rows);
225239
}
@@ -236,7 +250,7 @@ public static function prepareAndInsertAssoc(array $rows): Statement
236250
}
237251

238252
/**
239-
* Prepare row to insert into DB, non associative array
253+
* Prepare row to insert into DB, non-associative array
240254
* Need to overwrite in nested models
241255
* @param array $row
242256
* @param array $columns
@@ -258,6 +272,18 @@ public static function prepareAssocFromRequest(array $row): array
258272
return $row;
259273
}
260274

275+
protected static function castRow(array $row, array $casts): array
276+
{
277+
foreach ($casts as $index => $castType) {
278+
$value = $row[$index];
279+
if ('boolean' == $castType) {
280+
$value = (int)(bool)$value;
281+
}
282+
$row[$index] = $value;
283+
}
284+
return $row;
285+
}
286+
261287
/**
262288
* @param string|array|RawColumn $select optional = ['*']
263289
* @return Builder

tests.bootstrap.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ cp /src/tests/config/database.php /app/config/database.php
1414
cp /src/tests/config/app.php /app/config/app.php
1515
cp /src/tests/migrations/exampleTable.php /app/database/migrations/2022_01_01_000000_example.php
1616
cp /src/tests/migrations/example2Table.php /app/database/migrations/2022_01_01_000001_example.php
17+
cp /src/tests/migrations/example3Table.php /app/database/migrations/2022_01_01_000002_example.php
1718
cat /src/tests/config/.env >> /app/.env
1819

20+
# Creating test tables
21+
php artisan migrate
22+
1923
# Running tests
2024
php artisan test

tests/CastsTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Tests;
4+
5+
use Tests\Models\Example3;
6+
7+
class CastsTest extends TestCase
8+
{
9+
10+
protected function setUp(): void
11+
{
12+
parent::setUp();
13+
Example3::truncate();
14+
}
15+
16+
public function testCasts()
17+
{
18+
Example3::insertAssoc([['f_int' => 1, 'f_bool' => false]]);
19+
$rows = Example3::where('f_int', 1)->getRows();
20+
$this->assertEquals(false, $rows[0]['f_bool']);
21+
22+
Example3::truncate();
23+
Example3::insertBulk([[2, false]], ['f_int', 'f_bool']);
24+
$rows = Example3::where('f_int', 2)->getRows();
25+
$this->assertEquals(false, $rows[0]['f_bool']);
26+
27+
Example3::truncate();
28+
$one = new Example3();
29+
$one->f_int = 3;
30+
$one->f_bool = false;
31+
$one->save();
32+
$rows = Example3::where('f_int', 3)->getRows();
33+
$this->assertEquals(false, $rows[0]['f_bool']);
34+
}
35+
}

tests/Models/Example3.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Tests\Models;
4+
5+
use PhpClickHouseLaravel\BaseModel;
6+
7+
/**
8+
* @property bool $f_bool
9+
* @property int $f_int
10+
*/
11+
class Example3 extends BaseModel
12+
{
13+
protected $table = 'examples3';
14+
protected $casts = ['f_bool' => 'boolean'];
15+
}

tests/migrations/example2Table.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public function up()
1313
{
1414
static::write(
1515
"
16-
CREATE TABLE examples2 (
16+
CREATE TABLE IF NOT EXISTS examples2 (
1717
created_at DateTime64 DEFAULT now64(),
1818
f_int Int64,
1919
f_int2 Int64,

tests/migrations/example3Table.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
return new class extends \PhpClickHouseLaravel\Migration {
4+
/**
5+
* Run the migrations.
6+
*
7+
* @return void
8+
*/
9+
public function up()
10+
{
11+
static::write(
12+
"
13+
CREATE TABLE IF NOT EXISTS examples3 (
14+
created_at DateTime64 DEFAULT now64(),
15+
f_int Int64,
16+
f_string String,
17+
f_bool Bool
18+
)
19+
ENGINE = MergeTree()
20+
ORDER BY (f_int)
21+
"
22+
);
23+
}
24+
25+
/**
26+
* Reverse the migrations.
27+
*
28+
* @return void
29+
*/
30+
public function down()
31+
{
32+
static::write('DROP TABLE examples3');
33+
}
34+
};

tests/migrations/exampleTable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public function up()
1010
{
1111
static::write(
1212
"
13-
CREATE TABLE examples (
13+
CREATE TABLE IF NOT EXISTS examples (
1414
created_at DateTime64 DEFAULT now64(),
1515
f_int Int64,
1616
f_int2 Int64,

0 commit comments

Comments
 (0)