From 1613f8328b53d229f8fade8304156541e96e2690 Mon Sep 17 00:00:00 2001 From: Sartor Date: Wed, 15 Aug 2018 13:49:05 +0300 Subject: [PATCH 1/5] Fix undefined order in query test --- tests/CommandTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CommandTest.php b/tests/CommandTest.php index d29008b..d8acac3 100644 --- a/tests/CommandTest.php +++ b/tests/CommandTest.php @@ -39,7 +39,7 @@ public function testQuery() $this->assertInstanceOf(\Generator::class, $reader); // queryAll - $rows = $db->createCommand('SELECT * FROM {{customer}}')->queryAll(); + $rows = $db->createCommand('SELECT * FROM {{customer}} ORDER BY [[id]]')->queryAll(); $this->assertCount(3, $rows); $row = $rows[2]; $this->assertEquals(3, $row['id']); @@ -60,7 +60,7 @@ public function testQuery() $command = $db->createCommand($sql); $this->assertFalse($command->queryOne()); // queryColumn - $sql = 'SELECT * FROM {{customer}}'; + $sql = 'SELECT * FROM {{customer}} ORDER BY [[id]]'; $column = $db->createCommand($sql)->queryColumn(); $this->assertEquals(range(1, 3), $column); $command = $db->createCommand('SELECT [[id]] FROM {{customer}} WHERE [[id]] = 10'); From 08c3cadb5db11730c862001c7a4f8ff10e5fed2c Mon Sep 17 00:00:00 2001 From: Sartor Date: Wed, 15 Aug 2018 17:18:22 +0300 Subject: [PATCH 2/5] Added column builder for clickhouse types --- ColumnSchemaBuilder.php | 90 +++++++++++++ QueryBuilder.php | 34 +++++ Schema.php | 8 ++ tests/SchemaBuilderTest.php | 257 ++++++++++++++++++++++++++++++++++++ 4 files changed, 389 insertions(+) create mode 100644 ColumnSchemaBuilder.php create mode 100644 tests/SchemaBuilderTest.php diff --git a/ColumnSchemaBuilder.php b/ColumnSchemaBuilder.php new file mode 100644 index 0000000..ef956a1 --- /dev/null +++ b/ColumnSchemaBuilder.php @@ -0,0 +1,90 @@ + + */ +class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder +{ + /** + * @var bool whether the column is or not nullable. + * If this is `false`, a `Nullable` type wrapper will be added. + */ + protected $isNotNull = true; + + /** + * {@inheritdoc} + */ + public function defaultValue($default) + { + $this->default = $default; + return $this; + } + + protected function buildNotNullString() + { + if ($this->isNotNull !== true) { + return ' NULL'; + } + + return ''; + } + + /** + * {@inheritdoc} + */ + protected function buildUnsignedString() + { + return $this->isUnsigned ? 'U' : ''; + } + + /** + * {@inheritdoc} + */ + protected function buildAfterString() + { + return $this->after !== null ? + ' AFTER ' . $this->db->quoteColumnName($this->after) : + ''; + } + + /** + * {@inheritdoc} + */ + protected function buildFirstString() + { + return $this->isFirst ? ' FIRST' : ''; + } + + protected function buildDefaultString() + { + $result = parent::buildDefaultString(); + + if ($this->default === null && $this->isNotNull === false) { + return ''; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + switch ($this->getTypeCategory()) { + case self::CATEGORY_NUMERIC: + $format = "{unsigned}{type}{notnull}{default}{append}{pos}"; + break; + default: + $format = "{type}{length}{notnull}{default}{check}{append}{pos}"; + } + + return $this->buildCompleteString($format); + } +} diff --git a/QueryBuilder.php b/QueryBuilder.php index a31c1ee..464b4d4 100644 --- a/QueryBuilder.php +++ b/QueryBuilder.php @@ -14,6 +14,33 @@ */ class QueryBuilder extends \yii\db\QueryBuilder { + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = [ + Schema::TYPE_CHAR => 'FixedString(1)', + Schema::TYPE_STRING => 'String', + Schema::TYPE_TEXT => 'String', + Schema::TYPE_TINYINT => 'Int8', + Schema::TYPE_SMALLINT => 'Int16', + Schema::TYPE_INTEGER => 'Int32', + Schema::TYPE_BIGINT => 'Int64', + 'U'.Schema::TYPE_TINYINT => 'UInt8', + 'U'.Schema::TYPE_SMALLINT => 'UInt16', + 'U'.Schema::TYPE_INTEGER => 'UInt32', + 'U'.Schema::TYPE_BIGINT => 'UInt64', + Schema::TYPE_FLOAT => 'Float32', + Schema::TYPE_DOUBLE => 'Float64', + Schema::TYPE_DECIMAL => 'Float64', + Schema::TYPE_DATETIME => 'DateTime', + Schema::TYPE_TIMESTAMP => 'DateTime', + Schema::TYPE_TIME => 'DateTime', + Schema::TYPE_DATE => 'Date', + Schema::TYPE_BINARY => 'String', + Schema::TYPE_BOOLEAN => 'UInt8', + Schema::TYPE_MONEY => 'Float64', + Schema::TYPE_JSON => 'String' + ]; /** * @inheritdoc @@ -26,6 +53,13 @@ public function createTable($table, $columns, $options = null) return parent::createTable($table, $columns, $options); } + + public function getColumnType($type) + { + // Replacing NULL to Nullable() wrapper + return preg_replace('/^(\w+)(\(\d+\))? NULL(.*)$/i', 'Nullable(\1\2)\3', parent::getColumnType($type)); + } + /** * @inheritdoc */ diff --git a/Schema.php b/Schema.php index 76ea3be..898a7da 100644 --- a/Schema.php +++ b/Schema.php @@ -118,4 +118,12 @@ public function createQueryBuilder() { return new QueryBuilder($this->db); } + + /** + * {@inheritdoc} + */ + public function createColumnSchemaBuilder($type, $length = null) + { + return new ColumnSchemaBuilder($type, $length, $this->db); + } } \ No newline at end of file diff --git a/tests/SchemaBuilderTest.php b/tests/SchemaBuilderTest.php new file mode 100644 index 0000000..50314a7 --- /dev/null +++ b/tests/SchemaBuilderTest.php @@ -0,0 +1,257 @@ + + */ +class SchemaBuilderTest extends DatabaseTestCase +{ + use SchemaBuilderTrait; + + private $tableName; + + protected function getDb() + { + return $this->getConnection(); + } + + protected function setUp() + { + parent::setUp(); + + $this->tableName = uniqid('tmp_schema_test'); + } + + protected function tearDown() + { + $this->dropTable(); + + parent::tearDown(); + } + + private function dropTable() + { + $sql = "DROP TABLE IF EXISTS `{$this->tableName}`"; + $this->getConnection()->createCommand($sql)->execute(); + } + + private function tableColumnsTypes() + { + $sql = "SELECT type FROM system.columns WHERE table = '{$this->tableName}' ORDER BY name"; + + return $this->getConnection()->createCommand($sql)->queryColumn(); + } + + private function tableColumnsDefaults() + { + $sql = "SELECT default_kind, default_expression FROM system.columns WHERE table = '{$this->tableName}' ORDER BY name"; + + return $this->getConnection()->createCommand($sql)->queryAll(); + } + + public function testIntegers() + { + $this->dropTable(); + + $db = $this->getConnection(); + + $createResult = $db->createCommand()->createTable($this->tableName, [ + 'i1' => $this->smallInteger(), + 'i2' => $this->smallInteger()->unsigned(), + 'i3' => $this->integer(), + 'i4' => $this->integer()->unsigned(), + 'i5' => $this->bigInteger(), + 'i6' => $this->bigInteger()->unsigned(), + ], 'Engine=Memory')->execute(); + + $this->assertEquals(1, $createResult); + + $this->assertEquals(['Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64'], $this->tableColumnsTypes()); + } + + public function testFloats() + { + $this->dropTable(); + + $db = $this->getConnection(); + + $createResult = $db->createCommand()->createTable($this->tableName, [ + 'f1' => $this->float(), + 'f2' => $this->double(), + ], 'Engine=Memory')->execute(); + + $this->assertEquals(1, $createResult); + + $this->assertEquals(['Float32', 'Float64'], $this->tableColumnsTypes()); + } + + public function testString() + { + $this->dropTable(); + + $db = $this->getConnection(); + + $createResult = $db->createCommand()->createTable($this->tableName, [ + 's1' => $this->string(), + 's2' => $this->text(), + 's3' => $this->binary(), + 's4' => $this->string(1000000), + 's5' => $this->char(), + 's6' => $this->char(100), + 's7' => $this->json(), + ], 'Engine=Memory')->execute(); + + $this->assertEquals(1, $createResult); + + $this->assertEquals(['String', 'String', 'String', 'String', 'FixedString(1)', 'FixedString(100)', 'String'], $this->tableColumnsTypes()); + } + + public function testDate() + { + $this->dropTable(); + + $db = $this->getConnection(); + + $createResult = $db->createCommand()->createTable($this->tableName, [ + 'd1' => $this->date(), + 'd2' => $this->dateTime(), + 'd3' => $this->dateTime(0), + 'd4' => $this->dateTime(6), + ], 'Engine=Memory')->execute(); + + $this->assertEquals(1, $createResult); + + $this->assertEquals(['Date', 'DateTime', 'DateTime', 'DateTime'], $this->tableColumnsTypes()); + } + + public function testBoolean() + { + $this->dropTable(); + + $db = $this->getConnection(); + + $createResult = $db->createCommand()->createTable($this->tableName, [ + 'b1' => $this->boolean(), + ], 'Engine=Memory')->execute(); + + $this->assertEquals(1, $createResult); + + $this->assertEquals(['UInt8'], $this->tableColumnsTypes()); + } + + public function testFixed() + { + $this->dropTable(); + + $db = $this->getConnection(); + + $createResult = $db->createCommand()->createTable($this->tableName, [ + 'f1' => $this->decimal(), + 'f2' => $this->decimal(10, 4), + 'f3' => $this->money(), + 'f4' => $this->money(10, 4), + + ], 'Engine=Memory')->execute(); + + $this->assertEquals(1, $createResult); + + $this->assertEquals(['Float64', 'Float64', 'Float64', 'Float64'], $this->tableColumnsTypes()); + } + + public function testNullable() + { + $this->dropTable(); + + $db = $this->getConnection(); + + $createResult = $db->createCommand()->createTable($this->tableName, [ + 'n01' => $this->integer()->null(), + 'n02' => $this->integer()->null()->unsigned(), + 'n03' => $this->float()->null(), + 'n04' => $this->double()->null(), + 'n05' => $this->string()->null(), + 'n06' => $this->boolean()->null(), + 'n07' => $this->date()->null(), + 'n08' => $this->dateTime()->null(), + 'n09' => $this->decimal()->null(), + 'n10' => $this->money()->null(), + 'n11' => $this->char(100)->null(), + ], 'Engine=Memory')->execute(); + + $this->assertEquals(1, $createResult); + + $expected = [ + 'Nullable(Int32)', 'Nullable(UInt32)', 'Nullable(Float32)', 'Nullable(Float64)', + 'Nullable(String)', 'Nullable(UInt8)', 'Nullable(Date)', 'Nullable(DateTime)', + 'Nullable(Float64)', 'Nullable(Float64)', 'Nullable(FixedString(100))' + ]; + + $this->assertEquals($expected, $this->tableColumnsTypes()); + } + + public function testDefaultExpression() + { + $this->dropTable(); + + $db = $this->getConnection(); + + $createResult = $db->createCommand()->createTable($this->tableName, [ + 'd1' => $this->integer()->defaultExpression('100'), + 'd2' => $this->integer()->null()->unsigned()->defaultExpression('100'), + 'd3' => $this->char(10)->null()->defaultExpression("'qweasdzxc1'"), + 'd4' => $this->string()->defaultExpression("'str1'"), + 'd5' => $this->string()->defaultExpression("concat('str1', 'str2')"), + 'd6' => $this->date()->defaultExpression("today()"), + 'd7' => $this->dateTime()->defaultExpression("now()"), + ], 'Engine=Memory')->execute(); + + $this->assertEquals(1, $createResult); + + $expected = [ + ['default_kind' => 'DEFAULT', 'default_expression' => "CAST(100, 'Int32')"], + ['default_kind' => 'DEFAULT', 'default_expression' => "CAST(100, 'Nullable(UInt32)')"], + ['default_kind' => 'DEFAULT', 'default_expression' => "CAST('qweasdzxc1', 'Nullable(FixedString(10))')"], + ['default_kind' => 'DEFAULT', 'default_expression' => "'str1'"], + ['default_kind' => 'DEFAULT', 'default_expression' => "concat('str1', 'str2')"], + ['default_kind' => 'DEFAULT', 'default_expression' => "today()"], + ['default_kind' => 'DEFAULT', 'default_expression' => "now()"], + ]; + + $this->assertEquals($expected, $this->tableColumnsDefaults()); + } + + public function testDefaultValue() + { + $this->dropTable(); + + $db = $this->getConnection(); + + $createResult = $db->createCommand()->createTable($this->tableName, [ + 'd1' => $this->integer()->defaultValue('100'), + 'd2' => $this->integer()->defaultValue(100), + 'd3' => $this->integer()->null()->unsigned()->defaultValue(100), + 'd4' => $this->char(10)->null()->defaultValue('qweasdzxc1'), + 'd5' => $this->string()->defaultValue('str1'), + ], 'Engine=Memory')->execute(); + + $this->assertEquals(1, $createResult); + + $expected = [ + ['default_kind' => 'DEFAULT', 'default_expression' => "CAST('100', 'Int32')"], + ['default_kind' => 'DEFAULT', 'default_expression' => "CAST(100, 'Int32')"], + ['default_kind' => 'DEFAULT', 'default_expression' => "CAST(100, 'Nullable(UInt32)')"], + ['default_kind' => 'DEFAULT', 'default_expression' => "CAST('qweasdzxc1', 'Nullable(FixedString(10))')"], + ['default_kind' => 'DEFAULT', 'default_expression' => "'str1'"], + ]; + + $this->assertEquals($expected, $this->tableColumnsDefaults()); + } +} \ No newline at end of file From 2edbd5ccf90b8ed728e02f51873fb67571455a51 Mon Sep 17 00:00:00 2001 From: bashkarev Date: Wed, 15 Aug 2018 23:43:58 +0300 Subject: [PATCH 3/5] + tinyint test --- tests/SchemaBuilderTest.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/SchemaBuilderTest.php b/tests/SchemaBuilderTest.php index 50314a7..426276d 100644 --- a/tests/SchemaBuilderTest.php +++ b/tests/SchemaBuilderTest.php @@ -64,17 +64,19 @@ public function testIntegers() $db = $this->getConnection(); $createResult = $db->createCommand()->createTable($this->tableName, [ - 'i1' => $this->smallInteger(), - 'i2' => $this->smallInteger()->unsigned(), - 'i3' => $this->integer(), - 'i4' => $this->integer()->unsigned(), - 'i5' => $this->bigInteger(), - 'i6' => $this->bigInteger()->unsigned(), + 'i1' => $this->tinyInteger(), + 'i2' => $this->tinyInteger()->unsigned(), + 'i3' => $this->smallInteger(), + 'i4' => $this->smallInteger()->unsigned(), + 'i5' => $this->integer(), + 'i6' => $this->integer()->unsigned(), + 'i7' => $this->bigInteger(), + 'i8' => $this->bigInteger()->unsigned(), ], 'Engine=Memory')->execute(); $this->assertEquals(1, $createResult); - $this->assertEquals(['Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64'], $this->tableColumnsTypes()); + $this->assertEquals(['Int8', 'UInt8', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64'], $this->tableColumnsTypes()); } public function testFloats() From c83fd97e4f77cfb8023a99afa5bd3ae623253970 Mon Sep 17 00:00:00 2001 From: bashkarev Date: Wed, 15 Aug 2018 23:54:23 +0300 Subject: [PATCH 4/5] + timestamp test --- tests/SchemaBuilderTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/SchemaBuilderTest.php b/tests/SchemaBuilderTest.php index 426276d..386cc3b 100644 --- a/tests/SchemaBuilderTest.php +++ b/tests/SchemaBuilderTest.php @@ -127,11 +127,13 @@ public function testDate() 'd2' => $this->dateTime(), 'd3' => $this->dateTime(0), 'd4' => $this->dateTime(6), + 'd5' => $this->timestamp(), + 'd6' => $this->timestamp(6) ], 'Engine=Memory')->execute(); $this->assertEquals(1, $createResult); - $this->assertEquals(['Date', 'DateTime', 'DateTime', 'DateTime'], $this->tableColumnsTypes()); + $this->assertEquals(['Date', 'DateTime', 'DateTime', 'DateTime', 'DateTime', 'DateTime'], $this->tableColumnsTypes()); } public function testBoolean() From dbcc10d4e5f79f8d43c65d7c9505cb27b609fcfa Mon Sep 17 00:00:00 2001 From: bashkarev Date: Thu, 16 Aug 2018 00:02:00 +0300 Subject: [PATCH 5/5] missing phpdoc --- ColumnSchemaBuilder.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ColumnSchemaBuilder.php b/ColumnSchemaBuilder.php index ef956a1..dd583f1 100644 --- a/ColumnSchemaBuilder.php +++ b/ColumnSchemaBuilder.php @@ -26,6 +26,9 @@ public function defaultValue($default) return $this; } + /** + * {@inheritdoc} + */ protected function buildNotNullString() { if ($this->isNotNull !== true) { @@ -61,6 +64,9 @@ protected function buildFirstString() return $this->isFirst ? ' FIRST' : ''; } + /** + * {@inheritdoc} + */ protected function buildDefaultString() { $result = parent::buildDefaultString();