Added column casting feature for data insertion

This commit is contained in:
Denis Glushkov
2023-09-28 15:09:15 +05:00
parent 7af0c5b56b
commit 10cd5a9f73
10 changed files with 151 additions and 6 deletions
+5
View File
@@ -1,3 +1,8 @@
## 1.19.0 [2023-09-28]
### Features
1. Added column casting feature for data insertion
## 1.18.0 [2023-07-10]
### Features
+26
View File
@@ -175,6 +175,32 @@ $rows = MyTable::select(['field_one', new RawColumn('sum(field_two)', 'field_two
## Advanced usage
### Columns casting
Before insertion, the column will be converted to the required data type specified in the field `$casts`.
This feature does not apply to data selection.
The supported cast types are: `boolean`.
```php
namespace App\Models\Clickhouse;
use PhpClickHouseLaravel\BaseModel;
class MyTable extends BaseModel
{
/**
* The columns that should be cast.
*
* @var array
*/
protected $casts = ['some_bool_column' => 'boolean'];
}
// Then you can insert the data like this:
MyTable::insertAssoc([
['some_param' => 1, 'some_bool_column' => false],
]);
```
### Events
Events work just like an [eloquent model events](https://laravel.com/docs/9.x/eloquent#events)
+1 -1
View File
@@ -18,7 +18,7 @@ services:
soft: 262144
hard: 262144
ports:
- "8124:8123"
- "18123:8123"
clickhouse2:
image: yandex/clickhouse-server
+29 -3
View File
@@ -161,9 +161,7 @@ class BaseModel
throw new Exception("Clickhouse does not allow update rows");
}
$this->exists = !static::insertAssoc([$this->getAttributes()])->isError();
$this->fireModelEvent('saved', false);
return $this->exists;
}
@@ -189,6 +187,17 @@ class BaseModel
public static function insertBulk(array $rows, array $columns = []): Statement
{
$instance = new static();
if ($castsAssoc = (new static())->casts) {
$casts = [];
foreach ($castsAssoc as $castColumn => $castType) {
if ($index = array_search($castColumn, $columns)) {
$casts[$index] = $castType;
}
}
foreach ($rows as &$row) {
$row = static::castRow($row, $casts);
}
}
return $instance->getThisClient()->insert($instance->getTableForInserts(), $rows, $columns);
}
@@ -220,6 +229,11 @@ class BaseModel
$row = array_replace(array_flip($keys), $row);
}
}
if ($casts = (new static())->casts) {
foreach ($rows as &$row) {
$row = static::castRow($row, $casts);
}
}
$instance = new static();
return $instance->getThisClient()->insertAssocBulk($instance->getTableForInserts(), $rows);
}
@@ -236,7 +250,7 @@ class BaseModel
}
/**
* Prepare row to insert into DB, non associative array
* Prepare row to insert into DB, non-associative array
* Need to overwrite in nested models
* @param array $row
* @param array $columns
@@ -258,6 +272,18 @@ class BaseModel
return $row;
}
protected static function castRow(array $row, array $casts): array
{
foreach ($casts as $index => $castType) {
$value = $row[$index];
if ('boolean' == $castType) {
$value = (int)(bool)$value;
}
$row[$index] = $value;
}
return $row;
}
/**
* @param string|array|RawColumn $select optional = ['*']
* @return Builder
+4
View File
@@ -14,7 +14,11 @@ cp /src/tests/config/database.php /app/config/database.php
cp /src/tests/config/app.php /app/config/app.php
cp /src/tests/migrations/exampleTable.php /app/database/migrations/2022_01_01_000000_example.php
cp /src/tests/migrations/example2Table.php /app/database/migrations/2022_01_01_000001_example.php
cp /src/tests/migrations/example3Table.php /app/database/migrations/2022_01_01_000002_example.php
cat /src/tests/config/.env >> /app/.env
# Creating test tables
php artisan migrate
# Running tests
php artisan test
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace Tests;
use Tests\Models\Example3;
class CastsTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Example3::truncate();
}
public function testCasts()
{
Example3::insertAssoc([['f_int' => 1, 'f_bool' => false]]);
$rows = Example3::where('f_int', 1)->getRows();
$this->assertEquals(false, $rows[0]['f_bool']);
Example3::truncate();
Example3::insertBulk([[2, false]], ['f_int', 'f_bool']);
$rows = Example3::where('f_int', 2)->getRows();
$this->assertEquals(false, $rows[0]['f_bool']);
Example3::truncate();
$one = new Example3();
$one->f_int = 3;
$one->f_bool = false;
$one->save();
$rows = Example3::where('f_int', 3)->getRows();
$this->assertEquals(false, $rows[0]['f_bool']);
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Tests\Models;
use PhpClickHouseLaravel\BaseModel;
/**
* @property bool $f_bool
* @property int $f_int
*/
class Example3 extends BaseModel
{
protected $table = 'examples3';
protected $casts = ['f_bool' => 'boolean'];
}
+1 -1
View File
@@ -13,7 +13,7 @@ return new class extends \PhpClickHouseLaravel\Migration {
{
static::write(
"
CREATE TABLE examples2 (
CREATE TABLE IF NOT EXISTS examples2 (
created_at DateTime64 DEFAULT now64(),
f_int Int64,
f_int2 Int64,
+34
View File
@@ -0,0 +1,34 @@
<?php
return new class extends \PhpClickHouseLaravel\Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
static::write(
"
CREATE TABLE IF NOT EXISTS examples3 (
created_at DateTime64 DEFAULT now64(),
f_int Int64,
f_string String,
f_bool Bool
)
ENGINE = MergeTree()
ORDER BY (f_int)
"
);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
static::write('DROP TABLE examples3');
}
};
+1 -1
View File
@@ -10,7 +10,7 @@ return new class extends \PhpClickHouseLaravel\Migration {
{
static::write(
"
CREATE TABLE examples (
CREATE TABLE IF NOT EXISTS examples (
created_at DateTime64 DEFAULT now64(),
f_int Int64,
f_int2 Int64,