feat: extend Entity and adjust QBMapper to support Snowflake IDs

Signed-off-by: Anna Larch <anna@nextcloud.com>
This commit is contained in:
Anna Larch 2025-12-18 20:32:45 +01:00 committed by Carl Schwan
parent f546daada7
commit a100ede789
No known key found for this signature in database
GPG key ID: 02325448204E452A
6 changed files with 80 additions and 14 deletions

View file

@ -44,7 +44,7 @@ class VersionEntity extends Entity implements JsonSerializable {
public function jsonSerialize(): array {
return [
'id' => $this->id,
'id' => $this->getId(),
'file_id' => $this->fileId,
'timestamp' => $this->timestamp,
'size' => $this->size,

View file

@ -78,6 +78,7 @@ return array(
'OCP\\AppFramework\\Db\\IMapperException' => $baseDir . '/lib/public/AppFramework/Db/IMapperException.php',
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => $baseDir . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
'OCP\\AppFramework\\Db\\QBMapper' => $baseDir . '/lib/public/AppFramework/Db/QBMapper.php',
'OCP\\AppFramework\\Db\\SnowflakeAwareEntity' => $baseDir . '/lib/public/AppFramework/Db/SnowflakeAwareEntity.php',
'OCP\\AppFramework\\Db\\TTransactional' => $baseDir . '/lib/public/AppFramework/Db/TTransactional.php',
'OCP\\AppFramework\\Http' => $baseDir . '/lib/public/AppFramework/Http.php',
'OCP\\AppFramework\\Http\\Attribute\\ARateLimit' => $baseDir . '/lib/public/AppFramework/Http/Attribute/ARateLimit.php',

View file

@ -119,6 +119,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\AppFramework\\Db\\IMapperException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/IMapperException.php',
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php',
'OCP\\AppFramework\\Db\\QBMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBMapper.php',
'OCP\\AppFramework\\Db\\SnowflakeAwareEntity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/SnowflakeAwareEntity.php',
'OCP\\AppFramework\\Db\\TTransactional' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/TTransactional.php',
'OCP\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http.php',
'OCP\\AppFramework\\Http\\Attribute\\ARateLimit' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Attribute/ARateLimit.php',

View file

@ -13,19 +13,23 @@ use function lcfirst;
use function substr;
/**
* @method int getId()
* @method void setId(int $id)
* @since 7.0.0
* @psalm-consistent-constructor
*/
abstract class Entity {
/** @var int $id */
public $id = null;
private ?int $id = null;
private array $_updatedFields = [];
/** @var array<string, \OCP\DB\Types::*> */
/** @psalm-param $_fieldTypes array<string, Types::*> */
private array $_fieldTypes = ['id' => 'integer'];
public function setId($id): void {
$this->id = $id;
}
public function getId(): ?int {
return $this->id;
}
/**
* Simple alternative constructor for building entities from a request
* @param array $params the array which was obtained via $this->params('key')
@ -64,7 +68,7 @@ abstract class Entity {
/**
* @return array<string, \OCP\DB\Types::*> with attribute and type
* @return array<string, Types::*> with attribute and type
* @since 7.0.0
*/
public function getFieldTypes(): array {
@ -266,8 +270,8 @@ abstract class Entity {
* that value once its being returned from the database
*
* @param string $fieldName the name of the attribute
* @param \OCP\DB\Types::* $type the type which will be used to match a cast
* @since 31.0.0 Parameter $type is now restricted to {@see \OCP\DB\Types} constants. The formerly accidentally supported types 'int'|'bool'|'double' are mapped to Types::INTEGER|Types::BOOLEAN|Types::FLOAT accordingly.
* @param Types::* $type the type which will be used to match a cast
* @since 31.0.0 Parameter $type is now restricted to {@see Types} constants. The formerly accidentally supported types 'int'|'bool'|'double' are mapped to Types::INTEGER|Types::BOOLEAN|Types::FLOAT accordingly.
* @since 7.0.0
*/
protected function addType(string $fieldName, string $type): void {

View file

@ -112,13 +112,17 @@ abstract class QBMapper {
$qb->setValue($column, $qb->createNamedParameter($value, $type));
}
$qb->executeStatement();
if ($entity->id === null) {
if ($entity instanceof SnowflakeAwareEntity) {
/** @psalm-suppress DocblockTypeContradiction */
$entity->setId();
$qb->executeStatement();
} elseif ($entity->getId() === null) {
$qb->executeStatement();
// When autoincrement is used id is always an int
$entity->setId($qb->getLastInsertId());
} else {
$qb->executeStatement();
}
return $entity;
}

View file

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\AppFramework\Db;
use OCP\AppFramework\Attribute\Consumable;
use OCP\DB\Types;
use OCP\Server;
use OCP\Snowflake\ISnowflakeDecoder;
use OCP\Snowflake\ISnowflakeGenerator;
use OCP\Snowflake\Snowflake;
/**
* Entity with snowflake support
*
* @since 33.0.0
*/
#[Consumable(since: '33.0.0')]
abstract class SnowflakeAwareEntity extends Entity {
private ?string $id = null;
protected ?Snowflake $snowflake = null;
/** @psalm-param $_fieldTypes array<string, Types::*> */
private array $_fieldTypes = ['id' => Types::STRING];
/**
* Automatically creates a snowflake ID
*/
#[\Override]
public function setId($id = null): void {
if ($this->id === null) {
$this->id = Server::get(ISnowflakeGenerator::class)->nextId();
$this->markFieldUpdated('id');
}
}
public function getCreatedAt(): ?\DateTimeImmutable {
return $this->getSnowflake()?->getCreatedAt();
}
public function getSnowflake(): ?Snowflake {
if ($this->id === null) {
return null;
}
if ($this->snowflake === null) {
$this->snowflake = Server::get(ISnowflakeDecoder::class)->decode($this->id);
}
return $this->snowflake;
}
}