From 8b69122b4542cd27d04115dbe1ac17bd564f8a30 Mon Sep 17 00:00:00 2001 From: Matthew Peveler Date: Sun, 4 Aug 2024 13:25:14 -0400 Subject: [PATCH] Support Literal values in inserts --- src/Phinx/Db/Adapter/PdoAdapter.php | 37 ++++++++-- tests/Phinx/Db/Adapter/MysqlAdapterTest.php | 69 +++++++++++++++++++ .../Phinx/Db/Adapter/PostgresAdapterTest.php | 67 ++++++++++++++++++ tests/Phinx/Db/Adapter/SQLiteAdapterTest.php | 67 ++++++++++++++++++ .../Phinx/Db/Adapter/SqlServerAdapterTest.php | 67 ++++++++++++++++++ 5 files changed, 300 insertions(+), 7 deletions(-) diff --git a/src/Phinx/Db/Adapter/PdoAdapter.php b/src/Phinx/Db/Adapter/PdoAdapter.php index 8a8722dcd..9edf9af6f 100644 --- a/src/Phinx/Db/Adapter/PdoAdapter.php +++ b/src/Phinx/Db/Adapter/PdoAdapter.php @@ -336,9 +336,22 @@ public function insert(Table $table, array $row): void $sql .= '(' . implode(', ', array_map([$this, 'quoteValue'], $row)) . ');'; $this->output->writeln($sql); } else { - $sql .= '(' . implode(', ', array_fill(0, count($columns), '?')) . ')'; + $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; + } + } + } + $sql .= implode(', ', $values) . ')'; $stmt = $this->getConnection()->prepare($sql); - $stmt->execute(array_values($row)); + $stmt->execute($vals); } } @@ -358,6 +371,10 @@ protected function quoteValue(mixed $value): mixed return 'null'; } + if ($value instanceof Literal) { + return (string)$value; + } + return $this->getConnection()->quote($value); } @@ -392,17 +409,23 @@ public function bulkinsert(Table $table, array $rows): void $sql .= implode(', ', $values) . ';'; $this->output->writeln($sql); } else { - $count_keys = count($keys); - $query = '(' . implode(', ', array_fill(0, $count_keys, '?')) . ')'; - $count_vars = count($rows); - $queries = array_fill(0, $count_vars, $query); + $queries = []; + foreach ($rows as $row) { + $values = []; + foreach ($row as $value) { + $values[] = $value instanceof Literal ? (string)$value : '?'; + } + $queries[] = '(' . implode(', ', $values) . ')'; + } $sql .= implode(',', $queries); $stmt = $this->getConnection()->prepare($sql); $vals = []; foreach ($rows as $row) { foreach ($row as $v) { - if (is_bool($v)) { + if ($v instanceof Literal) { + continue; + } elseif (is_bool($v)) { $vals[] = $this->castToBool($v); } else { $vals[] = $v; diff --git a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php index 20e3ade23..acb5fb355 100644 --- a/tests/Phinx/Db/Adapter/MysqlAdapterTest.php +++ b/tests/Phinx/Db/Adapter/MysqlAdapterTest.php @@ -2180,9 +2180,41 @@ public function testBulkInsertData() $this->assertEquals(2, $rows[1]['column2']); $this->assertEquals(3, $rows[2]['column2']); $this->assertEquals('test', $rows[0]['column3']); + $this->assertEquals('test', $rows[1]['column3']); $this->assertEquals('test', $rows[2]['column3']); } + public function testBulkInsertLiteral() + { + $data = [ + [ + 'column1' => 'value1', + 'column2' => Literal::from('CURRENT_TIMESTAMP'), + ], + [ + 'column1' => 'value2', + 'column2' => '2024-01-01 00:00:00', + ], + [ + 'column1' => 'value3', + 'column2' => '2025-01-01 00:00:00', + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'datetime') + ->insert($data) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals('value3', $rows[2]['column1']); + $this->assertMatchesRegularExpression('/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/', $rows[0]['column2']); + $this->assertEquals('2024-01-01 00:00:00', $rows[1]['column2']); + $this->assertEquals('2025-01-01 00:00:00', $rows[2]['column2']); + } + public function testInsertData() { $data = [ @@ -2215,9 +2247,46 @@ public function testInsertData() $this->assertEquals(2, $rows[1]['column2']); $this->assertEquals(3, $rows[2]['column2']); $this->assertEquals('test', $rows[0]['column3']); + $this->assertEquals('test', $rows[1]['column3']); $this->assertEquals('foo', $rows[2]['column3']); } + public function testInsertLiteral() + { + $data = [ + [ + 'column1' => 'value1', + 'column3' => Literal::from('CURRENT_TIMESTAMP'), + ], + [ + 'column1' => 'value2', + 'column3' => '2024-01-01 00:00:00', + ], + [ + 'column1' => 'value3', + 'column2' => 'foo', + 'column3' => '2025-01-01 00:00:00', + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'string', ['default' => 'test']) + ->addColumn('column3', 'datetime') + ->insert($data) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals('value3', $rows[2]['column1']); + $this->assertEquals('test', $rows[0]['column2']); + $this->assertEquals('test', $rows[1]['column2']); + $this->assertEquals('foo', $rows[2]['column2']); + $this->assertMatchesRegularExpression('/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/', $rows[0]['column3']); + $this->assertEquals('2024-01-01 00:00:00', $rows[1]['column3']); + $this->assertEquals('2025-01-01 00:00:00', $rows[2]['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 b7953bd2a..597a4221d 100644 --- a/tests/Phinx/Db/Adapter/PostgresAdapterTest.php +++ b/tests/Phinx/Db/Adapter/PostgresAdapterTest.php @@ -2369,6 +2369,37 @@ public function testBulkInsertBoolean() $this->assertNull($rows[2]['column1']); } + public function testBulkInsertLiteral() + { + $data = [ + [ + 'column1' => 'value1', + 'column2' => Literal::from('CURRENT_TIMESTAMP'), + ], + [ + 'column1' => 'value2', + 'column2' => '2024-01-01 00:00:00', + ], + [ + 'column1' => 'value3', + 'column2' => '2025-01-01 00:00:00', + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'datetime') + ->insert($data) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals('value3', $rows[2]['column1']); + $this->assertMatchesRegularExpression('/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/', $rows[0]['column2']); + $this->assertEquals('2024-01-01 00:00:00', $rows[1]['column2']); + $this->assertEquals('2025-01-01 00:00:00', $rows[2]['column2']); + } + public function testInsertData() { $table = new Table('table1', [], $this->adapter); @@ -2418,6 +2449,42 @@ public function testInsertBoolean() $this->assertNull($rows[2]['column1']); } + public function testInsertLiteral() + { + $data = [ + [ + 'column1' => 'value1', + 'column3' => Literal::from('CURRENT_TIMESTAMP'), + ], + [ + 'column1' => 'value2', + 'column3' => '2024-01-01 00:00:00', + ], + [ + 'column1' => 'value3', + 'column2' => 'foo', + 'column3' => '2025-01-01 00:00:00', + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'string', ['default' => 'test']) + ->addColumn('column3', 'datetime') + ->insert($data) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals('value3', $rows[2]['column1']); + $this->assertEquals('test', $rows[0]['column2']); + $this->assertEquals('test', $rows[1]['column2']); + $this->assertEquals('foo', $rows[2]['column2']); + $this->assertMatchesRegularExpression('/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/', $rows[0]['column3']); + $this->assertEquals('2024-01-01 00:00:00', $rows[1]['column3']); + $this->assertEquals('2025-01-01 00:00:00', $rows[2]['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 80bca5d90..43bd5bd45 100644 --- a/tests/Phinx/Db/Adapter/SQLiteAdapterTest.php +++ b/tests/Phinx/Db/Adapter/SQLiteAdapterTest.php @@ -1775,6 +1775,37 @@ public function testBulkInsertData() $this->assertNull($rows[3]['column2']); } + public function testBulkInsertLiteral() + { + $data = [ + [ + 'column1' => 'value1', + 'column2' => Literal::from('CURRENT_TIMESTAMP'), + ], + [ + 'column1' => 'value2', + 'column2' => '2024-01-01 00:00:00', + ], + [ + 'column1' => 'value3', + 'column2' => '2025-01-01 00:00:00', + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'datetime') + ->insert($data) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals('value3', $rows[2]['column1']); + $this->assertMatchesRegularExpression('/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/', $rows[0]['column2']); + $this->assertEquals('2024-01-01 00:00:00', $rows[1]['column2']); + $this->assertEquals('2025-01-01 00:00:00', $rows[2]['column2']); + } + public function testInsertData() { $table = new Table('table1', [], $this->adapter); @@ -1816,6 +1847,42 @@ public function testInsertData() $this->assertNull($rows[3]['column2']); } + public function testInsertLiteral() + { + $data = [ + [ + 'column1' => 'value1', + 'column3' => Literal::from('CURRENT_TIMESTAMP'), + ], + [ + 'column1' => 'value2', + 'column3' => '2024-01-01 00:00:00', + ], + [ + 'column1' => 'value3', + 'column2' => 'foo', + 'column3' => '2025-01-01 00:00:00', + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'string', ['default' => 'test']) + ->addColumn('column3', 'datetime') + ->insert($data) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals('value3', $rows[2]['column1']); + $this->assertEquals('test', $rows[0]['column2']); + $this->assertEquals('test', $rows[1]['column2']); + $this->assertEquals('foo', $rows[2]['column2']); + $this->assertMatchesRegularExpression('/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/', $rows[0]['column3']); + $this->assertEquals('2024-01-01 00:00:00', $rows[1]['column3']); + $this->assertEquals('2025-01-01 00:00:00', $rows[2]['column3']); + } + public function testBulkInsertDataEnum() { $table = new Table('table1', [], $this->adapter); diff --git a/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php b/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php index 8a07bd85f..20c88c0c3 100644 --- a/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php +++ b/tests/Phinx/Db/Adapter/SqlServerAdapterTest.php @@ -1307,6 +1307,37 @@ public function testBulkInsertData() $this->assertEquals(3, $rows[2]['column2']); } + public function testBulkInsertLiteral() + { + $data = [ + [ + 'column1' => 'value1', + 'column2' => Literal::from('CURRENT_TIMESTAMP'), + ], + [ + 'column1' => 'value2', + 'column2' => '2024-01-01 00:00:00', + ], + [ + 'column1' => 'value3', + 'column2' => '2025-01-01 00:00:00', + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'datetime') + ->insert($data) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals('value3', $rows[2]['column1']); + $this->assertMatchesRegularExpression('/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/', $rows[0]['column2']); + $this->assertEquals('2024-01-01 00:00:00.000', $rows[1]['column2']); + $this->assertEquals('2025-01-01 00:00:00.000', $rows[2]['column2']); + } + public function testInsertData() { $table = new Table('table1', [], $this->adapter); @@ -1340,6 +1371,42 @@ public function testInsertData() $this->assertEquals(3, $rows[2]['column2']); } + public function testInsertLiteral() + { + $data = [ + [ + 'column1' => 'value1', + 'column3' => Literal::from('CURRENT_TIMESTAMP'), + ], + [ + 'column1' => 'value2', + 'column3' => '2024-01-01 00:00:00', + ], + [ + 'column1' => 'value3', + 'column2' => 'foo', + 'column3' => '2025-01-01 00:00:00', + ], + ]; + $table = new Table('table1', [], $this->adapter); + $table->addColumn('column1', 'string') + ->addColumn('column2', 'string', ['default' => 'test']) + ->addColumn('column3', 'datetime') + ->insert($data) + ->save(); + + $rows = $this->adapter->fetchAll('SELECT * FROM table1'); + $this->assertEquals('value1', $rows[0]['column1']); + $this->assertEquals('value2', $rows[1]['column1']); + $this->assertEquals('value3', $rows[2]['column1']); + $this->assertEquals('test', $rows[0]['column2']); + $this->assertEquals('test', $rows[1]['column2']); + $this->assertEquals('foo', $rows[2]['column2']); + $this->assertMatchesRegularExpression('/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/', $rows[0]['column3']); + $this->assertEquals('2024-01-01 00:00:00.000', $rows[1]['column3']); + $this->assertEquals('2025-01-01 00:00:00.000', $rows[2]['column3']); + } + public function testTruncateTable() { $table = new Table('table1', [], $this->adapter);