Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions lib/public/AppFramework/Db/QBMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,42 @@ public function insertOrUpdate(Entity $entity): Entity {
}
}

/**
* Creates a new entry in the db from an entity, ignoring conflicts on
* unique constraints
*
* @param Entity $entity the entity that should be created
* @psalm-param T $entity the entity that should be created
* @return int number of inserted rows (0 if a conflicting row already exists)
* @throws Exception
* @since 36.0.0
*/
public function insertIgnoreConflict(Entity $entity): int {
if ($entity instanceof SnowflakeAwareEntity) {
/** @psalm-suppress DocblockTypeContradiction */
$entity->generateId();
}

// get updated fields to save, fields have to be set using a setter to
// be saved
$properties = $entity->getUpdatedFields();

$values = [];

// build the fields
foreach ($properties as $property => $updated) {
if ($property === 'id' && $entity->id === null) {
continue;
}

$column = $entity->propertyToColumn($property);
$getter = 'get' . ucfirst($property);
$values[$column] = $entity->$getter();
}

return $this->db->insertIgnoreConflict($this->tableName, $values);
}

/**
* Updates an entry in the db from an entity
*
Expand Down
19 changes: 19 additions & 0 deletions tests/lib/AppFramework/Db/QBMapperDBTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,25 @@ public function testUpdateDateTime(): void {
$this->assertEquals($datetime->format('Y-m-d H:i:s'), $dbEntity->getDatetime()->format('Y-m-d H:i:s'));
}

public function testInsertIgnoreConflict(): void {
$mapper = new QBDBTestMapper($this->connection);

$entity = new class extends Entity {
protected ?string $datetime = null;
};
$entity->setId(200001);
$entity->setDatetime('2000-01-01 23:45:00');
$this->assertSame(1, $mapper->insertIgnoreConflict($entity));

$conflicting = clone $entity;
$conflicting->setDatetime('2010-05-05 05:05:05');
$this->assertSame(0, $mapper->insertIgnoreConflict($conflicting));

// the conflicting insert must not have modified the existing row
$dbEntity = $mapper->getById(200001);
$this->assertEquals('2000-01-01 23:45:00', $dbEntity->getDatetime()->format('Y-m-d H:i:s'));
}

protected function prepareTestingTable(): void {
if ($this->schemaSetup) {
$this->connection->getQueryBuilder()->delete('testing')->executeStatement();
Expand Down
69 changes: 69 additions & 0 deletions tests/lib/AppFramework/Db/QBMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\AppFramework\Db\SnowflakeAwareEntity;
use OCP\DB\QueryBuilder\IExpressionBuilder;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\Types;
Expand Down Expand Up @@ -212,6 +213,74 @@ public function testUpdateEntityParameterTypeMapping(): void {
$this->mapper->update($entity);
}

public function testInsertIgnoreConflictEntityValueMapping(): void {
$entity = new QBTestEntity();
$entity->setIntProp(123);
$entity->setBoolProp(true);
$entity->setStringProp('string');
$entity->setIntegerProp(456);
$entity->setBooleanProp(false);

$this->db->expects($this->never())->method('getQueryBuilder');
$this->db->expects($this->once())
->method('insertIgnoreConflict')
->with('table', [
'int_prop' => 123,
'bool_prop' => true,
'string_prop' => 'string',
'integer_prop' => 456,
'boolean_prop' => false,
])
->willReturn(1);

$this->assertSame(1, $this->mapper->insertIgnoreConflict($entity));
}

public function testInsertIgnoreConflictReturnsZero(): void {
$entity = new QBTestEntity();
$entity->setId(123);
$entity->setStringProp('string');

$this->db->expects($this->once())
->method('insertIgnoreConflict')
->with('table', ['id' => 123, 'string_prop' => 'string'])
->willReturn(0);

$this->assertSame(0, $this->mapper->insertIgnoreConflict($entity));
}

public function testInsertIgnoreConflictSkipsNullId(): void {
$entity = new QBTestEntity();
$entity->setId(123);
$entity->setId(null);
$entity->setStringProp('string');

$this->db->expects($this->once())
->method('insertIgnoreConflict')
->with('table', ['string_prop' => 'string'])
->willReturn(1);

$this->assertSame(1, $this->mapper->insertIgnoreConflict($entity));
}

public function testInsertIgnoreConflictGeneratesSnowflakeId(): void {
$entity = new class extends SnowflakeAwareEntity {
#[\Override]
public function generateId(): void {
$this->id = '123';
$this->markFieldUpdated('id');
}
};

$this->db->expects($this->once())
->method('insertIgnoreConflict')
->with('table', ['id' => '123'])
->willReturn(1);

$this->assertSame(1, $this->mapper->insertIgnoreConflict($entity));
$this->assertSame('123', $entity->getId());
}

public function testGetParameterTypeForProperty(): void {
$entity = new QBTestEntity();

Expand Down
Loading