diff --git a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/MySQLDialect.php b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/MySQLDialect.php index 7c6985761..805d2e808 100644 --- a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/MySQLDialect.php +++ b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/MySQLDialect.php @@ -24,17 +24,6 @@ public function prepareDelete(TableDefinition $table, BulkData $bulkData) : stri { $columns = $bulkData->columns()->all(); - if (count($columns) === 1) { - $column = $columns[0]; - - return \sprintf( - 'DELETE FROM %s WHERE %s IN (%s)', - $table->name(), - $this->platform->quoteIdentifier($column), - $bulkData->toSqlPlaceholders() - ); - } - return \sprintf( 'DELETE FROM %s WHERE (%s) IN (%s)', $table->name(), @@ -78,7 +67,7 @@ public function prepareInsert(TableDefinition $table, BulkData $bulkData, ?Inser \implode(',', \array_map(fn (string $column) : string => $this->platform->quoteIdentifier($column), $bulkData->columns()->all())), $bulkData->toSqlPlaceholders(), \count($options->updateColumns) - ? $this->updateSelectedColumns($options->updateColumns, $bulkData->columns()) + ? $this->updateSelectedColumns($options->updateColumns, $bulkData->columns(), $table->name(), $options->preserveExistingValues) : $this->updateAllColumns($bulkData->columns()) ); } @@ -129,10 +118,18 @@ private function updateAllColumns(Columns $columns) : string * * @return string */ - private function updateSelectedColumns(array $updateColumns, Columns $columns) : string + private function updateSelectedColumns(array $updateColumns, Columns $columns, string $tableName, ?bool $preserveExistingValues = null) : string { return \count($updateColumns) - ? \implode(',', \array_map(fn (string $column) : string => "{$this->platform->quoteIdentifier($column)} = VALUES({$this->platform->quoteIdentifier($column)})", $updateColumns)) + ? \implode(',', \array_map(function (string $column) use ($tableName, $preserveExistingValues) : string { + $clause = "{$this->platform->quoteIdentifier($column)} = "; + + if (true === $preserveExistingValues) { + return $clause . "COALESCE(VALUES({$this->platform->quoteIdentifier($column)}), {$tableName}.{$this->platform->quoteIdentifier($column)})"; + } + + return $clause . "VALUES({$this->platform->quoteIdentifier($column)})"; + }, $updateColumns)) : $this->updateAllColumns($columns); } } diff --git a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/MySQLInsertOptions.php b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/MySQLInsertOptions.php index 47405ff52..e907366c7 100644 --- a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/MySQLInsertOptions.php +++ b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/MySQLInsertOptions.php @@ -18,6 +18,7 @@ public function __construct( public ?bool $skipConflicts = null, public ?bool $upsert = null, public array $updateColumns = [], + public ?bool $preserveExistingValues = null, ) { } @@ -29,6 +30,7 @@ public static function fromArray(array $options) : InsertOptions 'skip_conflicts' => type_optional(type_boolean()), 'upsert' => type_optional(type_boolean()), 'update_columns' => type_list(type_string()), + 'preserve_existing_values' => type_optional(type_boolean()), ] )->assert($options); @@ -36,6 +38,7 @@ public static function fromArray(array $options) : InsertOptions $options['skip_conflicts'] ?? null, $options['upsert'] ?? null, $options['update_columns'] ?? [], + $options['preserve_existing_values'] ?? null, ); } @@ -52,9 +55,9 @@ public function skipConflicts(bool $skip = true) : self /** * @param array $updateColumns */ - public function updateColumns(array $updateColumns) : self + public function updateColumns(array $updateColumns, ?bool $preserveExistingValues = null) : self { - return new self($this->skipConflicts, $this->upsert, $updateColumns); + return new self($this->skipConflicts, $this->upsert, $updateColumns, $preserveExistingValues); } public function upsert(bool $upsert = true) : self diff --git a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLDialect.php b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLDialect.php index f902f5653..a2edc77a2 100644 --- a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLDialect.php +++ b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLDialect.php @@ -24,17 +24,6 @@ public function prepareDelete(TableDefinition $table, BulkData $bulkData) : stri { $columns = $bulkData->columns()->all(); - if (count($columns) === 1) { - $column = $columns[0]; - - return \sprintf( - 'DELETE FROM %s WHERE %s IN (%s)', - $table->name(), - $this->platform->quoteIdentifier($column), - $bulkData->toSqlPlaceholders() - ); - } - return \sprintf( 'DELETE FROM %s WHERE (%s) IN (%s)', $table->name(), @@ -67,7 +56,7 @@ public function prepareInsert(TableDefinition $table, BulkData $bulkData, ?Inser $bulkData->toSqlPlaceholders(), \implode(',', $options->conflictColumns), \count($options->updateColumns) - ? $this->updatedSelectedColumns($options->updateColumns, $bulkData->columns()) + ? $this->updatedSelectedColumns($options->updateColumns, $bulkData->columns(), $table->name(), $options->preserveExistingValues) : $this->updateAllColumns($bulkData->columns()) ); } @@ -80,7 +69,7 @@ public function prepareInsert(TableDefinition $table, BulkData $bulkData, ?Inser $bulkData->toSqlPlaceholders(), $options->constraint, \count($options->updateColumns) - ? $this->updatedSelectedColumns($options->updateColumns, $bulkData->columns()) + ? $this->updatedSelectedColumns($options->updateColumns, $bulkData->columns(), $table->name(), $options->preserveExistingValues) : $this->updateAllColumns($bulkData->columns()) ); } @@ -132,7 +121,7 @@ public function prepareUpdate(TableDefinition $table, BulkData $bulkData, ?Updat 'UPDATE %s as existing_table SET %s FROM (VALUES %s) as excluded (%s) WHERE %s', $table->name(), \count($options->updateColumns) - ? $this->updatedSelectedColumns($options->updateColumns, $bulkData->columns()->without(...$options->primaryKeyColumns)) + ? $this->updatedSelectedColumns($options->updateColumns, $bulkData->columns()->without(...$options->primaryKeyColumns), $table->name(), $options->preserveExistingValues) : $this->updateAllColumns($bulkData->columns()->without(...$options->primaryKeyColumns)), $bulkData->toSqlCastedPlaceholders($table), \implode(',', \array_map(fn (string $column) : string => $this->platform->quoteIdentifier($column), $bulkData->columns()->all())), @@ -140,11 +129,6 @@ public function prepareUpdate(TableDefinition $table, BulkData $bulkData, ?Updat ); } - /** - * @param Columns $columns - * - * @return string - */ private function updateAllColumns(Columns $columns) : string { /** @@ -162,8 +146,6 @@ private function updateAllColumns(Columns $columns) : string /** * @param array $updateColumns - * - * @return string */ private function updatedIndexColumns(array $updateColumns) : string { @@ -172,11 +154,8 @@ private function updatedIndexColumns(array $updateColumns) : string /** * @param array $updateColumns - * @param Columns $columns - * - * @return string */ - private function updatedSelectedColumns(array $updateColumns, Columns $columns) : string + private function updatedSelectedColumns(array $updateColumns, Columns $columns, string $tableName, ?bool $preserveExistingValues = null) : string { /** * https://www.postgresql.org/docs/9.5/sql-insert.html#SQL-ON-CONFLICT @@ -184,7 +163,15 @@ private function updatedSelectedColumns(array $updateColumns, Columns $columns) * table's name (or an alias), and to rows proposed for insertion using the special EXCLUDED table. */ return \count($updateColumns) - ? \implode(',', \array_map(fn (string $column) : string => "{$this->platform->quoteIdentifier($column)} = {$this->platform->quoteIdentifier('excluded.' . $column)}", $updateColumns)) + ? \implode(',', \array_map(function (string $column) use ($tableName, $preserveExistingValues) : string { + $clause = "{$this->platform->quoteIdentifier($column)} = "; + + if (true === $preserveExistingValues) { + return $clause . "COALESCE({$this->platform->quoteIdentifier('excluded.' . $column)}, {$tableName}.{$this->platform->quoteIdentifier($column)})"; + } + + return $clause . "{$this->platform->quoteIdentifier('excluded.' . $column)}"; + }, $updateColumns)) : $this->updateAllColumns($columns); } } diff --git a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLInsertOptions.php b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLInsertOptions.php index 196dba144..3099d2348 100644 --- a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLInsertOptions.php +++ b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLInsertOptions.php @@ -18,6 +18,7 @@ public function __construct( public ?string $constraint = null, public array $conflictColumns = [], public array $updateColumns = [], + public ?bool $preserveExistingValues = null, ) { } @@ -27,11 +28,13 @@ public function __construct( public static function fromArray(array $options) : InsertOptions { $options = type_structure( - optional_elements: [ + [], + [ 'skip_conflicts' => type_optional(type_boolean()), 'constraint' => type_optional(type_string()), 'conflict_columns' => type_list(type_string()), 'update_columns' => type_list(type_string()), + 'preserve_existing_values' => type_optional(type_boolean()), ] )->assert($options); @@ -40,6 +43,7 @@ public static function fromArray(array $options) : InsertOptions $options['constraint'] ?? null, $options['conflict_columns'] ?? [], $options['update_columns'] ?? [], + $options['preserve_existing_values'] ?? null, ); } @@ -69,8 +73,8 @@ public function skipConflicts(bool $skip = true) : self /** * @param array $updateColumns */ - public function updateColumns(array $updateColumns) : self + public function updateColumns(array $updateColumns, ?bool $preserveExistingValues = null) : self { - return new self($this->skipConflicts, $this->constraint, $this->conflictColumns, $updateColumns); + return new self($this->skipConflicts, $this->constraint, $this->conflictColumns, $updateColumns, $preserveExistingValues); } } diff --git a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLUpdateOptions.php b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLUpdateOptions.php index 8ef707972..511325895 100644 --- a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLUpdateOptions.php +++ b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/PostgreSQLUpdateOptions.php @@ -4,7 +4,7 @@ namespace Flow\Doctrine\Bulk\Dialect; -use function Flow\Types\DSL\{type_list, type_string, type_structure}; +use function Flow\Types\DSL\{type_boolean, type_list, type_optional, type_string, type_structure}; use Flow\Doctrine\Bulk\UpdateOptions; final readonly class PostgreSQLUpdateOptions implements UpdateOptions @@ -16,6 +16,7 @@ public function __construct( public array $primaryKeyColumns = [], public array $updateColumns = [], + public ?bool $preserveExistingValues = null, ) { } @@ -28,12 +29,14 @@ public static function fromArray(array $options) : UpdateOptions optional_elements: [ 'primary_key_columns' => type_list(type_string()), 'update_columns' => type_list(type_string()), + 'preserve_existing_values' => type_optional(type_boolean()), ] )->assert($options); return new self( $options['primary_key_columns'] ?? [], $options['update_columns'] ?? [], + $options['preserve_existing_values'] ?? null, ); } diff --git a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/SqliteDialect.php b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/SqliteDialect.php index 9844f331f..237254566 100644 --- a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/SqliteDialect.php +++ b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/SqliteDialect.php @@ -23,17 +23,6 @@ public function prepareDelete(TableDefinition $table, BulkData $bulkData) : stri { $columns = $bulkData->columns()->all(); - if (count($columns) === 1) { - $column = $columns[0]; - - return \sprintf( - 'DELETE FROM %s WHERE %s IN (%s)', - $table->name(), - $this->platform->quoteIdentifier($column), - $bulkData->toSqlPlaceholders() - ); - } - return \sprintf( 'DELETE FROM %s WHERE (%s) IN (%s)', $table->name(), @@ -60,7 +49,7 @@ public function prepareInsert(TableDefinition $table, BulkData $bulkData, ?Inser $bulkData->toSqlPlaceholders(), \implode(',', $options->conflictColumns), \count($options->updateColumns) - ? $this->updateSelectedColumns($options->updateColumns, $bulkData->columns()) + ? $this->updateSelectedColumns($options->updateColumns, $bulkData->columns(), $table->name(), $options->preserveExistingValues) : $this->updateAllColumns($bulkData->columns()) ); } @@ -82,7 +71,7 @@ public function prepareInsert(TableDefinition $table, BulkData $bulkData, ?Inser ); } - public function prepareUpdate(TableDefinition $table, BulkData $bulkData, ?UpdateOptions $updateOptions = null) : string + public function prepareUpdate(TableDefinition $table, BulkData $bulkData, ?UpdateOptions $options = null) : string { return \sprintf( 'REPLACE INTO %s (%s) VALUES %s', @@ -105,10 +94,18 @@ private function updateAllColumns(Columns $columns) : string /** * @param array $updateColumns */ - private function updateSelectedColumns(array $updateColumns, Columns $columns) : string + private function updateSelectedColumns(array $updateColumns, Columns $columns, string $tableName, ?bool $preserveExistingValues = null) : string { return [] !== $updateColumns - ? \implode(',', \array_map(fn (string $column) : string => "{$this->platform->quoteIdentifier($column)} = {$this->platform->quoteIdentifier('excluded.' . $column)}", $updateColumns)) + ? \implode(',', \array_map(function (string $column) use ($tableName, $preserveExistingValues) : string { + $clause = "{$this->platform->quoteIdentifier($column)} = "; + + if (true === $preserveExistingValues) { + return $clause . "COALESCE({$this->platform->quoteIdentifier('excluded.' . $column)}, {$tableName}.{$this->platform->quoteIdentifier($column)})"; + } + + return $clause . "{$this->platform->quoteIdentifier('excluded.' . $column)}"; + }, $updateColumns)) : $this->updateAllColumns($columns); } } diff --git a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/SqliteInsertOptions.php b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/SqliteInsertOptions.php index c8181d838..7a3f3bc14 100644 --- a/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/SqliteInsertOptions.php +++ b/src/lib/doctrine-dbal-bulk/src/Flow/Doctrine/Bulk/Dialect/SqliteInsertOptions.php @@ -17,6 +17,7 @@ public function __construct( public ?bool $skipConflicts = null, public array $conflictColumns = [], public array $updateColumns = [], + public ?bool $preserveExistingValues = null, ) { } @@ -31,6 +32,7 @@ public static function fromArray(array $options) : InsertOptions 'skip_conflicts' => type_optional(type_boolean()), 'conflict_columns' => type_list(type_string()), 'update_columns' => type_list(type_string()), + 'preserve_existing_values' => type_optional(type_boolean()), ] )->assert($options); @@ -38,6 +40,7 @@ public static function fromArray(array $options) : InsertOptions $options['skip_conflicts'] ?? null, $options['conflict_columns'] ?? [], $options['update_columns'] ?? [], + $options['preserve_existing_values'] ?? null, ); } @@ -62,8 +65,8 @@ public function skipConflicts(bool $skip = true) : self /** * @param array $updateColumns */ - public function updateColumns(array $updateColumns) : self + public function updateColumns(array $updateColumns, ?bool $preserveExistingValues = null) : self { - return new self($this->skipConflicts, $this->conflictColumns, $updateColumns); + return new self($this->skipConflicts, $this->conflictColumns, $updateColumns, $preserveExistingValues); } } diff --git a/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/MySqlBulkInsertTest.php b/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/MySqlBulkInsertTest.php index f3ddce283..68d929079 100644 --- a/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/MySqlBulkInsertTest.php +++ b/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/MySqlBulkInsertTest.php @@ -155,6 +155,57 @@ public function test_inserts_new_rows_and_update_already_existed() : void ); } + public function test_inserts_new_rows_and_update_selected_columns_and_preserve_existing_values() : void + { + $this->databaseContext->createTable( + (new Table( + $table = 'flow_doctrine_bulk_test', + [ + new Column('id', Type::getType(Types::INTEGER), ['notnull' => true]), + new Column('name', Type::getType(Types::STRING), ['notnull' => false, 'length' => 255]), + new Column('description', Type::getType(Types::STRING), ['notnull' => false, 'length' => 255]), + new Column('active', Type::getType(Types::BOOLEAN), ['notnull' => true]), + ], + )) + ->setPrimaryKey(['id']) + ); + + Bulk::create()->insert( + $this->databaseContext->connection(), + $table, + new BulkData([ + ['id' => 1, 'name' => 'Name One', 'description' => 'Description One', 'active' => true], + ['id' => 2, 'name' => 'Name Two', 'description' => 'Description Two', 'active' => true], + ['id' => 3, 'name' => 'Name Three', 'description' => 'Description Three', 'active' => true], + ]) + ); + + Bulk::create()->insert( + $this->databaseContext->connection(), + $table, + new BulkData([ + ['id' => 2, 'name' => 'New Name Two', 'description' => null, 'active' => true], + ['id' => 3, 'name' => null, 'description' => 'DESCRIPTION', 'active' => true], + ]), + MySQLInsertOptions::fromArray([ + 'upsert' => true, + 'update_columns' => ['name', 'description'], + 'preserve_existing_values' => true, + ]) + ); + + self::assertEquals(3, $this->databaseContext->tableCount($table)); + self::assertEquals(2, $this->executedQueriesCount()); + self::assertEquals( + [ + ['id' => 1, 'name' => 'Name One', 'description' => 'Description One', 'active' => true], + ['id' => 2, 'name' => 'New Name Two', 'description' => 'Description Two', 'active' => true], + ['id' => 3, 'name' => 'Name Three', 'description' => 'DESCRIPTION', 'active' => true], + ], + $this->databaseContext->selectAll($table) + ); + } + public function test_inserts_new_rows_and_update_selected_columns_only_of_already_existed() : void { $this->databaseContext->createTable( diff --git a/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/PostgreSqlBulkInsertTest.php b/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/PostgreSqlBulkInsertTest.php index a01391e7c..8cafac994 100644 --- a/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/PostgreSqlBulkInsertTest.php +++ b/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/PostgreSqlBulkInsertTest.php @@ -204,6 +204,56 @@ public function test_inserts_new_rows_or_updates_already_existed_based_on_column ); } + public function test_inserts_new_rows_or_updates_already_existed_based_on_columns_with_update_only_specific_columns_and_preserve_existing_values() : void + { + $this->databaseContext->createTable( + (new Table( + $table = 'flow_doctrine_bulk_test', + [ + new Column('id', Type::getType(Types::INTEGER), ['notnull' => true]), + new Column('name', Type::getType(Types::STRING), ['notnull' => false, 'length' => 255]), + new Column('description', Type::getType(Types::STRING), ['notnull' => false, 'length' => 255]), + new Column('active', Type::getType(Types::BOOLEAN), ['notnull' => true]), + ], + )) + ->setPrimaryKey(['id']) + ); + Bulk::create()->insert( + $this->databaseContext->connection(), + $table, + new BulkData([ + ['id' => 1, 'name' => 'Name One', 'description' => 'Description One', 'active' => true], + ['id' => 2, 'name' => 'Name Two', 'description' => 'Description Two', 'active' => false], + ['id' => 3, 'name' => 'Name Three', 'description' => 'Description Three', 'active' => true], + ]) + ); + + Bulk::create()->insert( + $this->databaseContext->connection(), + $table, + new BulkData([ + ['id' => 2, 'name' => 'New Name Two', 'description' => null, 'active' => true], + ['id' => 3, 'name' => null, 'description' => 'DESCRIPTION', 'active' => true], + ]), + PostgreSQLInsertOptions::fromArray([ + 'conflict_columns' => ['id'], + 'update_columns' => ['name', 'description'], + 'preserve_existing_values' => true, + ]) + ); + + self::assertEquals(3, $this->databaseContext->tableCount($table)); + self::assertEquals(2, $this->executedQueriesCount()); + self::assertEquals( + [ + ['id' => 1, 'name' => 'Name One', 'description' => 'Description One', 'active' => true], + ['id' => 2, 'name' => 'New Name Two', 'description' => 'Description Two', 'active' => false], + ['id' => 3, 'name' => 'Name Three', 'description' => 'DESCRIPTION', 'active' => true], + ], + $this->databaseContext->selectAll($table) + ); + } + public function test_inserts_new_rows_or_updates_already_existed_based_on_primary_key() : void { $this->databaseContext->createTable( diff --git a/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/SqliteBulkInsertTest.php b/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/SqliteBulkInsertTest.php index 875173c02..fdac5dba6 100644 --- a/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/SqliteBulkInsertTest.php +++ b/src/lib/doctrine-dbal-bulk/tests/Flow/Doctrine/Bulk/Tests/Integration/SqliteBulkInsertTest.php @@ -200,4 +200,54 @@ public function test_inserts_new_rows_or_updates_already_existed_based_on_column $this->databaseContext->selectAll($table) ); } + + public function test_inserts_new_rows_or_updates_already_existed_based_on_columns_with_update_only_specific_columns_and_preserve_existing_values() : void + { + $this->databaseContext->createTable( + (new Table( + $table = 'flow_doctrine_bulk_test', + [ + new Column('id', Type::getType(Types::INTEGER), ['notnull' => true]), + new Column('name', Type::getType(Types::STRING), ['notnull' => false, 'length' => 255]), + new Column('description', Type::getType(Types::STRING), ['notnull' => false, 'length' => 255]), + new Column('active', Type::getType(Types::BOOLEAN), ['notnull' => true]), + ], + )) + ->setPrimaryKey(['id']) + ); + Bulk::create()->insert( + $this->databaseContext->connection(), + $table, + new BulkData([ + ['id' => 1, 'name' => 'Name One', 'description' => 'Description One', 'active' => true], + ['id' => 2, 'name' => 'Name Two', 'description' => 'Description Two', 'active' => false], + ['id' => 3, 'name' => 'Name Three', 'description' => 'Description Three', 'active' => true], + ]) + ); + + Bulk::create()->insert( + $this->databaseContext->connection(), + $table, + new BulkData([ + ['id' => 2, 'name' => 'New Name Two', 'description' => null, 'active' => true], + ['id' => 3, 'name' => null, 'description' => 'DESCRIPTION', 'active' => true], + ]), + SqliteInsertOptions::fromArray([ + 'conflict_columns' => ['id'], + 'update_columns' => ['name', 'description'], + 'preserve_existing_values' => true, + ]) + ); + + self::assertEquals(3, $this->databaseContext->tableCount($table)); + self::assertEquals(2, $this->executedQueriesCount()); + self::assertEquals( + [ + ['id' => 1, 'name' => 'Name One', 'description' => 'Description One', 'active' => true], + ['id' => 2, 'name' => 'New Name Two', 'description' => 'Description Two', 'active' => false], + ['id' => 3, 'name' => 'Name Three', 'description' => 'DESCRIPTION', 'active' => true], + ], + $this->databaseContext->selectAll($table) + ); + } }