Skip to content

Commit

Permalink
Merge pull request #742 from cakephp/literal-insert
Browse files Browse the repository at this point in the history
Port insert & bulkinsert compatibility from phinx
  • Loading branch information
markstory authored Sep 5, 2024
2 parents 6047ee0 + a1bc157 commit c552f42
Show file tree
Hide file tree
Showing 6 changed files with 334 additions and 27 deletions.
46 changes: 34 additions & 12 deletions src/Db/Adapter/PdoAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
use PDOException;
use Phinx\Config\Config;
use Phinx\Migration\MigrationInterface;
use Phinx\Util\Literal as PhinxLiteral;
use ReflectionMethod;
use RuntimeException;
use UnexpectedValueException;
Expand Down Expand Up @@ -316,8 +317,20 @@ public function insert(Table $table, array $row): void
$sql .= ' VALUES (' . implode(', ', array_map([$this, 'quoteValue'], $row)) . ');';
$this->io->out($sql);
} else {
$sql .= ' VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
$this->getConnection()->execute($sql, array_values($row));
$values = [];
$vals = [];
foreach ($row as $value) {
$placeholder = '?';
if ($value instanceof Literal || $value instanceof PhinxLiteral) {
$placeholder = (string)$value;
}
$values[] = $placeholder;
if ($placeholder === '?') {
$vals[] = $value;
}
}
$sql .= ' VALUES (' . implode(',', $values) . ')';
$this->getConnection()->execute($sql, $vals);
}
}

Expand All @@ -336,6 +349,9 @@ protected function quoteValue(mixed $value): mixed
if ($value === null) {
return 'null';
}
if ($value instanceof Literal || $value instanceof PhinxLiteral) {
return (string)$value;
}
// TODO remove hacks like this by using cake's database layer better.
$driver = $this->getConnection()->getDriver();
$method = new ReflectionMethod($driver, 'getPdo');
Expand Down Expand Up @@ -382,22 +398,28 @@ public function bulkinsert(Table $table, array $rows): void
$sql .= implode(', ', $values) . ';';
$this->io->out($sql);
} else {
$count_keys = count($keys);
$query = '(' . implode(', ', array_fill(0, $count_keys, '?')) . ')';
$count_vars = count($rows);
$queries = array_fill(0, $count_vars, $query);
$sql .= implode(',', $queries);
$vals = [];

$queries = [];
foreach ($rows as $row) {
$values = [];
foreach ($row as $v) {
if (is_bool($v)) {
$vals[] = $this->castToBool($v);
} else {
$vals[] = $v;
$placeholder = '?';
if ($v instanceof Literal || $v instanceof PhinxLiteral) {
$placeholder = (string)$v;
}
$values[] = $placeholder;
if ($placeholder == '?') {
if (is_bool($v)) {
$vals[] = $this->castToBool($v);
} else {
$vals[] = $v;
}
}
}
$query = '(' . implode(', ', $values) . ')';
$queries[] = $query;
}
$sql .= implode(',', $queries);
$this->getConnection()->execute($sql, $vals);
}
}
Expand Down
47 changes: 32 additions & 15 deletions src/Db/Adapter/PostgresAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Migrations\Db\Table\ForeignKey;
use Migrations\Db\Table\Index;
use Migrations\Db\Table\Table;
use Phinx\Util\Literal as PhinxLiteral;

class PostgresAdapter extends PdoAdapter
{
Expand Down Expand Up @@ -1560,8 +1561,20 @@ public function insert(Table $table, array $row): void
$sql .= ' ' . $override . 'VALUES (' . implode(', ', array_map([$this, 'quoteValue'], $row)) . ');';
$this->io->out($sql);
} else {
$sql .= ' ' . $override . 'VALUES (' . implode(', ', array_fill(0, count($columns), '?')) . ')';
$this->getConnection()->execute($sql, array_values($row));
$values = [];
$vals = [];
foreach ($row as $value) {
$placeholder = '?';
if ($value instanceof Literal || $value instanceof PhinxLiteral) {
$placeholder = (string)$value;
}
$values[] = $placeholder;
if ($placeholder === '?') {
$vals[] = $value;
}
}
$sql .= ' ' . $override . 'VALUES (' . implode(',', $values) . ')';
$this->getConnection()->execute($sql, $vals);
}
}

Expand Down Expand Up @@ -1592,25 +1605,29 @@ public function bulkinsert(Table $table, array $rows): void
$sql .= implode(', ', $values) . ';';
$this->io->out($sql);
} else {
$connection = $this->getConnection();
$count_keys = count($keys);
$query = '(' . implode(', ', array_fill(0, $count_keys, '?')) . ')';
$count_vars = count($rows);
$queries = array_fill(0, $count_vars, $query);
$sql .= implode(',', $queries);
$vals = [];

$queries = [];
foreach ($rows as $row) {
$values = [];
foreach ($row as $v) {
if (is_bool($v)) {
$vals[] = $this->castToBool($v);
} else {
$vals[] = $v;
$placeholder = '?';
if ($v instanceof Literal || $v instanceof PhinxLiteral) {
$placeholder = (string)$v;
}
$values[] = $placeholder;
if ($placeholder == '?') {
if (is_bool($v)) {
$vals[] = $this->castToBool($v);
} else {
$vals[] = $v;
}
}
}
$query = '(' . implode(', ', $values) . ')';
$queries[] = $query;
}

$connection->execute($sql, $vals);
$sql .= implode(',', $queries);
$this->getConnection()->execute($sql, $vals);
}
}
}
67 changes: 67 additions & 0 deletions tests/TestCase/Db/Adapter/MysqlAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2024,6 +2024,37 @@ public function testBulkInsertData()
$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 = [
Expand Down Expand Up @@ -2059,6 +2090,42 @@ public function testInsertData()
$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()
{
$options = $this->adapter->getOptions();
Expand Down
67 changes: 67 additions & 0 deletions tests/TestCase/Db/Adapter/PostgresAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2233,6 +2233,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);
Expand All @@ -2257,6 +2288,42 @@ public function testInsertData()
$this->assertEquals(2, $rows[1]['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 testInsertBoolean()
{
$table = new Table('table1', [], $this->adapter);
Expand Down
67 changes: 67 additions & 0 deletions tests/TestCase/Db/Adapter/SqliteAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1684,6 +1684,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);
Expand Down Expand Up @@ -1725,6 +1756,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);
Expand Down
Loading

0 comments on commit c552f42

Please sign in to comment.