diff --git a/composer.json b/composer.json index 1b4ccbe0c..405255b8f 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,7 @@ "ext-json": "*", "ext-pdo": "*", "cakephp/cakephp-codesniffer": "^5.0", + "cakephp/i18n": "^5.0", "phpunit/phpunit": "^9.5.19", "symfony/yaml": "^3.4|^4.0|^5.0|^6.0|^7.0" }, diff --git a/src/Phinx/Db/Adapter/PdoAdapter.php b/src/Phinx/Db/Adapter/PdoAdapter.php index 4b025805a..8c7f8e40d 100644 --- a/src/Phinx/Db/Adapter/PdoAdapter.php +++ b/src/Phinx/Db/Adapter/PdoAdapter.php @@ -15,6 +15,8 @@ use Cake\Database\Query\InsertQuery; use Cake\Database\Query\SelectQuery; use Cake\Database\Query\UpdateQuery; +use Cake\I18n\Date; +use Cake\I18n\DateTime; use InvalidArgumentException; use PDO; use PDOException; @@ -319,6 +321,32 @@ public function fetchAll(string $sql): array return $this->query($sql)->fetchAll(); } + /** + * Get the parameters array for prepared insert statement + * + * @param array $row Row to be inserted into DB + * @return array + */ + protected function getInsertParameters(array $row): array + { + $params = []; + foreach ($row as $value) { + if ($value instanceof Literal) { + continue; + } elseif ($value instanceof DateTime) { + $params[] = $value->toDateTimeString(); + } elseif ($value instanceof Date) { + $params[] = $value->toDateString(); + } elseif (is_bool($value)) { + $params[] = $this->castToBool($value); + } else { + $params[] = $value; + } + } + + return $params; + } + /** * @inheritDoc */ @@ -342,21 +370,14 @@ public function insert(Table $table, array $row): void $this->output->writeln($sql); } else { $sql .= '('; - $vals = []; $values = []; foreach ($row as $value) { $values[] = $value instanceof Literal ? (string)$value : '?'; - if (!($value instanceof Literal)) { - if (is_bool($value)) { - $vals[] = $this->castToBool($value); - } else { - $vals[] = $value; - } - } } + $params = $this->getInsertParameters($row); $sql .= implode(', ', $values) . ')'; $stmt = $this->getConnection()->prepare($sql); - $stmt->execute($vals); + $stmt->execute($params); } } @@ -424,21 +445,13 @@ public function bulkinsert(Table $table, array $rows): void } $sql .= implode(',', $queries); $stmt = $this->getConnection()->prepare($sql); - $vals = []; + $params = []; foreach ($rows as $row) { - foreach ($row as $v) { - if ($v instanceof Literal) { - continue; - } elseif (is_bool($v)) { - $vals[] = $this->castToBool($v); - } else { - $vals[] = $v; - } - } + $params = array_merge($params, $this->getInsertParameters($row)); } - $stmt->execute($vals); + $stmt->execute($params); } } diff --git a/tests/Phinx/Console/Command/SeedRunTest.php b/tests/Phinx/Console/Command/SeedRunTest.php index 17ede581a..0c6f7be5a 100644 --- a/tests/Phinx/Console/Command/SeedRunTest.php +++ b/tests/Phinx/Console/Command/SeedRunTest.php @@ -63,7 +63,7 @@ public function testExecute() $command = $application->find('seed:run'); // mock the manager class - /** @var Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ + /** @var \Phinx\Migration\Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ $managerStub = $this->getMockBuilder('\Phinx\Migration\Manager') ->setConstructorArgs([$this->config, $this->input, $this->output]) ->getMock(); @@ -102,7 +102,7 @@ public function testExecuteWithDsn() ]); // mock the manager class - /** @var Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ + /** @var \Phinx\Migration\Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ $managerStub = $this->getMockBuilder('\Phinx\Migration\Manager') ->setConstructorArgs([$config, $this->input, $this->output]) ->getMock(); @@ -127,7 +127,7 @@ public function testExecuteWithEnvironmentOption() $command = $application->find('seed:run'); // mock the manager class - /** @var Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ + /** @var \Phinx\Migration\Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ $managerStub = $this->getMockBuilder('\Phinx\Migration\Manager') ->setConstructorArgs([$this->config, $this->input, $this->output]) ->getMock(); @@ -153,7 +153,7 @@ public function testExecuteWithInvalidEnvironmentOption() $command = $application->find('seed:run'); // mock the manager class - /** @var Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ + /** @var \Phinx\Migration\Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ $managerStub = $this->getMockBuilder('\Phinx\Migration\Manager') ->setConstructorArgs([$this->config, $this->input, $this->output]) ->getMock(); @@ -180,7 +180,7 @@ public function testDatabaseNameSpecified() $command = $application->find('seed:run'); // mock the manager class - /** @var Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ + /** @var \Phinx\Migration\Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ $managerStub = $this->getMockBuilder('\Phinx\Migration\Manager') ->setConstructorArgs([$this->config, $this->input, $this->output]) ->getMock(); @@ -205,7 +205,7 @@ public function testExecuteMultipleSeeders() $command = $application->find('seed:run'); // mock the manager class - /** @var Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ + /** @var \Phinx\Migration\Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ $managerStub = $this->getMockBuilder('\Phinx\Migration\Manager') ->setConstructorArgs([$this->config, $this->input, $this->output]) ->getMock(); @@ -256,7 +256,7 @@ public function testSeedRunMemorySqlite() $command = $application->find('seed:run'); // mock the manager class - /** @var Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ + /** @var \Phinx\Migration\Manager|\PHPUnit\Framework\MockObject\MockObject $managerStub */ $managerStub = $this->getMockBuilder('\Phinx\Migration\Manager') ->setConstructorArgs([$config, $this->input, $this->output]) ->getMock(); diff --git a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php index 95fba2e77..24b2d24e8 100644 --- a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php +++ b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -4,6 +4,8 @@ namespace Test\Phinx\Db\Adapter; use Cake\Database\Query; +use Cake\I18n\Date; +use Cake\I18n\DateTime; use InvalidArgumentException; use PDO; use PDOException; @@ -2215,6 +2217,30 @@ public function testBulkInsertLiteral() $this->assertEquals('2025-01-01 00:00:00', $rows[2]['column2']); } + public function testBulkInsertDates(): void + { + $data = [ + [ + 'name' => 'foo', + 'created' => new Date(), + ], + [ + 'name' => 'bar', + 'created' => new DateTime(), + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('name', 'string') + ->addColumn('created', 'datetime') + ->insert($data) + ->save(); + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('foo', $rows[0]['name']); + $this->assertEquals('bar', $rows[1]['name']); + $this->assertEquals($data[0]['created']->toDateTimeString(), $rows[0]['created']); + $this->assertEquals($data[1]['created']->toDateTimeString(), $rows[1]['created']); + } + public function testInsertData() { $data = [ @@ -2287,6 +2313,34 @@ public function testInsertLiteral() $this->assertEquals('2025-01-01 00:00:00', $rows[2]['column3']); } + public function testInsertDates(): void + { + $data = [ + [ + 'name' => 'foo', + 'created' => new Date(), + 'column3' => 'foo', + ], + [ + 'name' => 'bar', + 'created' => new DateTime(), + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('name', 'string') + ->addColumn('created', 'datetime') + ->addColumn('column3', 'string', ['null' => true, 'default' => null]) + ->insert($data) + ->save(); + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('foo', $rows[0]['name']); + $this->assertEquals('bar', $rows[1]['name']); + $this->assertEquals($data[0]['created']->toDateTimeString(), $rows[0]['created']); + $this->assertEquals($data[1]['created']->toDateTimeString(), $rows[1]['created']); + $this->assertEquals('foo', $rows[0]['column3']); + $this->assertNull($rows[1]['column3']); + } + public function testDumpCreateTable() { $inputDefinition = new InputDefinition([new InputOption('dry-run')]); diff --git a/tests/Phinx/Db/Adapter/PostgresAdapterTest.php b/tests/Phinx/Db/Adapter/PostgresAdapterTest.php index ec4019f89..46714f992 100644 --- a/tests/Phinx/Db/Adapter/PostgresAdapterTest.php +++ b/tests/Phinx/Db/Adapter/PostgresAdapterTest.php @@ -4,6 +4,8 @@ namespace Test\Phinx\Db\Adapter; use Cake\Database\Query; +use Cake\I18n\Date; +use Cake\I18n\DateTime; use InvalidArgumentException; use PDO; use Phinx\Db\Adapter\AbstractAdapter; @@ -2447,6 +2449,30 @@ public function testBulkInsertLiteral() $this->assertEquals('2025-01-01 00:00:00', $rows[2]['column2']); } + public function testBulkInsertDates(): void + { + $data = [ + [ + 'name' => 'foo', + 'created' => new Date(), + ], + [ + 'name' => 'bar', + 'created' => new DateTime(), + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('name', 'string') + ->addColumn('created', 'datetime') + ->insert($data) + ->save(); + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('foo', $rows[0]['name']); + $this->assertEquals('bar', $rows[1]['name']); + $this->assertEquals($data[0]['created']->toDateTimeString(), $rows[0]['created']); + $this->assertEquals($data[1]['created']->toDateTimeString(), $rows[1]['created']); + } + public function testInsertData() { $table = new Table('table1', [], $this->adapter); @@ -2532,6 +2558,34 @@ public function testInsertLiteral() $this->assertEquals('2025-01-01 00:00:00', $rows[2]['column3']); } + public function testInsertDates(): void + { + $data = [ + [ + 'name' => 'foo', + 'created' => new Date(), + 'column3' => 'foo', + ], + [ + 'name' => 'bar', + 'created' => new DateTime(), + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('name', 'string') + ->addColumn('created', 'datetime') + ->addColumn('column3', 'string', ['null' => true, 'default' => null]) + ->insert($data) + ->save(); + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('foo', $rows[0]['name']); + $this->assertEquals('bar', $rows[1]['name']); + $this->assertEquals($data[0]['created']->toDateTimeString(), $rows[0]['created']); + $this->assertEquals($data[1]['created']->toDateTimeString(), $rows[1]['created']); + $this->assertEquals('foo', $rows[0]['column3']); + $this->assertNull($rows[1]['column3']); + } + public function testInsertDataWithSchema() { $this->adapter->createSchema('schema1'); diff --git a/tests/Phinx/Db/Adapter/SQLiteAdapterTest.php b/tests/Phinx/Db/Adapter/SQLiteAdapterTest.php index ebe738af2..622583a8e 100644 --- a/tests/Phinx/Db/Adapter/SQLiteAdapterTest.php +++ b/tests/Phinx/Db/Adapter/SQLiteAdapterTest.php @@ -5,6 +5,8 @@ use BadMethodCallException; use Cake\Database\Query; +use Cake\I18n\Date; +use Cake\I18n\DateTime; use Exception; use InvalidArgumentException; use PDO; @@ -1901,6 +1903,58 @@ public function testBulkInsertDataEnum() $this->assertEquals('c', $rows[0]['column3']); } + public function testBulkInsertDates(): void + { + $data = [ + [ + 'name' => 'foo', + 'created' => new Date(), + ], + [ + 'name' => 'bar', + 'created' => new DateTime(), + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('name', 'string') + ->addColumn('created', 'datetime') + ->insert($data) + ->save(); + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('foo', $rows[0]['name']); + $this->assertEquals('bar', $rows[1]['name']); + $this->assertEquals($data[0]['created']->toDateString(), $rows[0]['created']); + $this->assertEquals($data[1]['created']->toDateTimeString(), $rows[1]['created']); + } + + public function testInsertDates(): void + { + $data = [ + [ + 'name' => 'foo', + 'created' => new Date(), + 'column3' => 'foo', + ], + [ + 'name' => 'bar', + 'created' => new DateTime(), + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('name', 'string') + ->addColumn('created', 'datetime') + ->addColumn('column3', 'string', ['null' => true, 'default' => null]) + ->insert($data) + ->save(); + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('foo', $rows[0]['name']); + $this->assertEquals('bar', $rows[1]['name']); + $this->assertEquals($data[0]['created']->toDateString(), $rows[0]['created']); + $this->assertEquals($data[1]['created']->toDateTimeString(), $rows[1]['created']); + $this->assertEquals('foo', $rows[0]['column3']); + $this->assertNull($rows[1]['column3']); + } + public function testNullWithoutDefaultValue() { $this->markTestSkipped('Skipping for now. See Github Issue #265.'); diff --git a/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php b/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php index 8a5a96c7f..e442e1aaa 100644 --- a/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php +++ b/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php @@ -5,6 +5,8 @@ use BadMethodCallException; use Cake\Database\Query; +use Cake\I18n\Date; +use Cake\I18n\DateTime; use InvalidArgumentException; use PDO; use Phinx\Db\Adapter\SqlServerAdapter; @@ -1345,6 +1347,30 @@ public function testBulkInsertLiteral() $this->assertEquals('2025-01-01 00:00:00.000', $rows[2]['column2']); } + public function testBulkInsertDates(): void + { + $data = [ + [ + 'name' => 'foo', + 'created' => new Date(), + ], + [ + 'name' => 'bar', + 'created' => new DateTime(), + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('name', 'string') + ->addColumn('created', 'datetime') + ->insert($data) + ->save(); + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('foo', $rows[0]['name']); + $this->assertEquals('bar', $rows[1]['name']); + $this->assertEquals($data[0]['created']->format('Y-m-d H:i:s.000'), $rows[0]['created']); + $this->assertEquals($data[1]['created']->format('Y-m-d H:i:s.000'), $rows[1]['created']); + } + public function testInsertData() { $table = new Table('table1', [], $this->adapter); @@ -1414,6 +1440,34 @@ public function testInsertLiteral() $this->assertEquals('2025-01-01 00:00:00.000', $rows[2]['column3']); } + public function testInsertDates(): void + { + $data = [ + [ + 'name' => 'foo', + 'created' => new Date(), + 'column3' => 'foo', + ], + [ + 'name' => 'bar', + 'created' => new DateTime(), + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('name', 'string') + ->addColumn('created', 'datetime') + ->addColumn('column3', 'string', ['null' => true, 'default' => null]) + ->insert($data) + ->save(); + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('foo', $rows[0]['name']); + $this->assertEquals('bar', $rows[1]['name']); + $this->assertEquals($data[0]['created']->format('Y-m-d H:i:s.000'), $rows[0]['created']); + $this->assertEquals($data[1]['created']->format('Y-m-d H:i:s.000'), $rows[1]['created']); + $this->assertEquals('foo', $rows[0]['column3']); + $this->assertNull($rows[1]['column3']); + } + public function testTruncateTable() { $table = new Table('table1', [], $this->adapter); diff --git a/tests/Phinx/Migration/_files/seeds/UserSeeder.php b/tests/Phinx/Migration/_files/seeds/UserSeeder.php index db9dfdffa..60db1e7c8 100644 --- a/tests/Phinx/Migration/_files/seeds/UserSeeder.php +++ b/tests/Phinx/Migration/_files/seeds/UserSeeder.php @@ -1,5 +1,7 @@