From 2f29f7a6c605084e5ae54a83f6d10458c70b62a9 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 4 Nov 2026 09:08:36 +0100 Subject: [PATCH] fix(db): type the integer columns of applications, node relations and page contents `Context`, `ContextNodeRelation` and `PageContent` registered their field types under the column names (`owner_type`, `context_id`, `node_id`, `node_type`, `page_id`, `node_rel_id`), while the entity looks them up by property name. The casts never applied and the mapper bound these columns as strings. They are now registered by property name. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Ruben van der Linde --- lib/Db/Context.php | 2 +- lib/Db/ContextNodeRelation.php | 6 +- lib/Db/PageContent.php | 4 +- tests/unit/Db/EntityFieldTypesTest.php | 107 +++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 tests/unit/Db/EntityFieldTypesTest.php diff --git a/lib/Db/Context.php b/lib/Db/Context.php index 3fa14f56b5..123700cbb0 100644 --- a/lib/Db/Context.php +++ b/lib/Db/Context.php @@ -45,7 +45,7 @@ class Context extends EntitySuper implements JsonSerializable { public function __construct() { $this->addType('id', 'integer'); - $this->addType('owner_type', 'integer'); + $this->addType('ownerType', 'integer'); } public function jsonSerialize(): array { diff --git a/lib/Db/ContextNodeRelation.php b/lib/Db/ContextNodeRelation.php index fbabd3e4da..26ddc580dc 100644 --- a/lib/Db/ContextNodeRelation.php +++ b/lib/Db/ContextNodeRelation.php @@ -29,9 +29,9 @@ class ContextNodeRelation extends Entity implements \JsonSerializable { public function __construct() { $this->addType('id', 'integer'); - $this->addType('context_id', 'integer'); - $this->addType('node_id', 'integer'); - $this->addType('node_type', 'integer'); + $this->addType('contextId', 'integer'); + $this->addType('nodeId', 'integer'); + $this->addType('nodeType', 'integer'); $this->addType('permissions', 'integer'); } diff --git a/lib/Db/PageContent.php b/lib/Db/PageContent.php index 966f48fc53..d4ea36b267 100644 --- a/lib/Db/PageContent.php +++ b/lib/Db/PageContent.php @@ -25,8 +25,8 @@ class PageContent extends Entity implements \JsonSerializable { public function __construct() { $this->addType('id', 'integer'); - $this->addType('page_id', 'integer'); - $this->addType('node_rel_id', 'integer'); + $this->addType('pageId', 'integer'); + $this->addType('nodeRelId', 'integer'); $this->addType('order', 'integer'); } diff --git a/tests/unit/Db/EntityFieldTypesTest.php b/tests/unit/Db/EntityFieldTypesTest.php new file mode 100644 index 0000000000..a87d698092 --- /dev/null +++ b/tests/unit/Db/EntityFieldTypesTest.php @@ -0,0 +1,107 @@ +, list}> + */ + public static function integerProperties(): array { + return [ + 'application' => [Context::class, ['ownerType']], + 'node relation' => [ContextNodeRelation::class, ['contextId', 'nodeId', 'nodeType', 'permissions']], + 'page content' => [PageContent::class, ['pageId', 'nodeRelId', 'order']], + ]; + } + + /** + * @dataProvider integerProperties + * @param class-string $class + * @param list $properties + */ + public function testEveryFieldTypeNamesAProperty(string $class, array $properties): void { + $types = (new $class())->getFieldTypes(); + + $this->assertSame([], array_values(array_filter(array_keys($types), static fn (string $field): bool => !property_exists($class, $field)))); + foreach ($properties as $property) { + $this->assertSame('integer', $types[$property] ?? null, $property); + } + } + + /** + * @dataProvider integerProperties + * @param class-string $class + * @param list $properties + */ + public function testTheMapperBindsTheIntegerColumnsAsIntegers(string $class, array $properties): void { + $mapper = $this->mapperFor($class); + $parameterType = new \ReflectionMethod(QBMapper::class, 'getParameterTypeForProperty'); + + foreach ($properties as $property) { + $this->assertSame(IQueryBuilder::PARAM_INT, $parameterType->invoke($mapper, new $class(), $property), $property); + } + } + + public function testANodeRelationAndAPageContentAreReadBackAsIntegers(): void { + $relationMapper = new ContextNodeRelationMapper($this->connectionAdapter); + $contentMapper = new PageContentMapper($this->connectionAdapter); + $relation = new ContextNodeRelation(); + $relation->setContextId(990701); + $relation->setNodeId(990702); + $relation->setNodeType(1); + $relation->setPermissions(3); + $relation = $relationMapper->insert($relation); + $content = new PageContent(); + $content->setPageId(990703); + $content->setNodeRelId($relation->getId()); + $content->setOrder(20); + $content = $contentMapper->insert($content); + + try { + $readRelation = $relationMapper->findById($relation->getId()); + $readContent = $contentMapper->findById($content->getId()); + + $this->assertSame([990701, 990702, 1, 3], [$readRelation->getContextId(), $readRelation->getNodeId(), $readRelation->getNodeType(), $readRelation->getPermissions()]); + $this->assertSame([990703, $relation->getId(), 20], [$readContent->getPageId(), $readContent->getNodeRelId(), $readContent->getOrder()]); + } finally { + $contentMapper->delete($content); + $relationMapper->delete($relation); + } + } + + /** + * @param class-string $class + */ + private function mapperFor(string $class): QBMapper { + return match ($class) { + Context::class => new ContextMapper($this->connectionAdapter, $this->createMock(UserHelper::class), $this->createMock(GroupHelper::class), new MenuItemMapper($this->connectionAdapter)), + ContextNodeRelation::class => new ContextNodeRelationMapper($this->connectionAdapter), + PageContent::class => new PageContentMapper($this->connectionAdapter), + }; + } +}