Skip to content

Commit eed0cea

Browse files
authored
IBX-8566: Fixed postgres language limit (#454)
For more details see https://issues.ibexa.co/browse/IBX-8566 and #454 Key changes: * Implemented PostgresqlGateway * Implemented SharedGateway\AbstractGateway to avoid code duplication * [Tests] Added integration test coverage for the use case * [Tests] Aligned tests with the changes * Fixed PHP deprecated error for $exp overflow for the last language
1 parent c38bd01 commit eed0cea

File tree

13 files changed

+246
-41
lines changed

13 files changed

+246
-41
lines changed

src/lib/Persistence/Legacy/Content/Gateway/DoctrineDatabase.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,16 +1357,7 @@ public function setName(int $contentId, int $version, string $name, string $lang
13571357
*/
13581358
private function getSetNameLanguageMaskSubQuery(): string
13591359
{
1360-
return <<<SQL
1361-
(SELECT
1362-
CASE
1363-
WHEN (initial_language_id = :language_id AND (language_mask & :language_id) <> 0 )
1364-
THEN (:language_id | 1)
1365-
ELSE :language_id
1366-
END
1367-
FROM ezcontentobject
1368-
WHERE id = :content_id)
1369-
SQL;
1360+
return $this->sharedGateway->getSetNameLanguageMaskSubQuery();
13701361
}
13711362

13721363
public function deleteContent(int $contentId): void

src/lib/Persistence/Legacy/Content/Language/MaskGenerator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ public function extractLanguageIdsFromMask($languageMask): array
150150
$result = [];
151151

152152
// Decomposition of $languageMask into its binary components.
153-
while ($exp <= $languageMask) {
153+
// check if $exp has not overflown and became float (happens for the last possible language in the mask)
154+
while (is_int($exp) && $exp <= $languageMask) {
154155
if ($languageMask & $exp) {
155156
$result[] = $exp;
156157
}

src/lib/Persistence/Legacy/Content/Mapper.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,8 @@ private function extractLanguageCodesFromMask(int $languageMask, array $allLangu
535535
$result = [];
536536

537537
// Decomposition of $languageMask into its binary components to extract language codes
538-
while ($exp <= $languageMask) {
538+
// check if $exp has not overflown and became float (happens for the last possible language in the mask)
539+
while (is_int($exp) && $exp <= $languageMask) {
539540
if ($languageMask & $exp) {
540541
if (isset($allLanguages[$exp])) {
541542
$result[] = $allLanguages[$exp]->languageCode;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform;
10+
11+
use Doctrine\DBAL\Connection;
12+
use Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway;
13+
14+
/**
15+
* @internal
16+
*/
17+
abstract class AbstractGateway implements Gateway
18+
{
19+
protected Connection $connection;
20+
21+
public function __construct(Connection $connection)
22+
{
23+
$this->connection = $connection;
24+
}
25+
26+
public function getColumnNextIntegerValue(
27+
string $tableName,
28+
string $columnName,
29+
string $sequenceName
30+
): ?int {
31+
return null;
32+
}
33+
34+
/**
35+
* Return a language sub select query for setName.
36+
*
37+
* The query generates the proper language mask at the runtime of the INSERT/UPDATE query
38+
* generated by setName.
39+
*
40+
* @see setName
41+
*/
42+
public function getSetNameLanguageMaskSubQuery(): string
43+
{
44+
return <<<SQL
45+
(SELECT
46+
CASE
47+
WHEN (initial_language_id = :language_id AND (language_mask & :language_id) <> 0 )
48+
THEN (:language_id | 1)
49+
ELSE :language_id
50+
END
51+
FROM ezcontentobject
52+
WHERE id = :content_id)
53+
SQL;
54+
}
55+
56+
public function getLastInsertedId(string $sequenceName): int
57+
{
58+
return (int)$this->connection->lastInsertId($sequenceName);
59+
}
60+
}

src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/FallbackGateway.php

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,8 @@
88

99
namespace Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform;
1010

11-
use Doctrine\DBAL\Connection;
12-
use Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway;
13-
14-
final class FallbackGateway implements Gateway
11+
final class FallbackGateway extends AbstractGateway
1512
{
16-
/** @var \Doctrine\DBAL\Connection */
17-
private $connection;
18-
19-
public function __construct(Connection $connection)
20-
{
21-
$this->connection = $connection;
22-
}
23-
24-
public function getColumnNextIntegerValue(
25-
string $tableName,
26-
string $columnName,
27-
string $sequenceName
28-
): ?int {
29-
return null;
30-
}
31-
32-
public function getLastInsertedId(string $sequenceName): int
33-
{
34-
return (int)$this->connection->lastInsertId($sequenceName);
35-
}
3613
}
3714

3815
class_alias(FallbackGateway::class, 'eZ\Publish\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\FallbackGateway');
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform;
10+
11+
final class PostgresqlGateway extends AbstractGateway
12+
{
13+
/**
14+
* Return a language sub select query for setName.
15+
*
16+
* The query generates the proper language mask at the runtime of the INSERT/UPDATE query
17+
* generated by setName.
18+
*
19+
* @see setName
20+
*/
21+
public function getSetNameLanguageMaskSubQuery(): string
22+
{
23+
return <<<SQL
24+
(SELECT
25+
CASE
26+
WHEN (initial_language_id = :language_id AND (language_mask & :language_id) <> 0 )
27+
THEN (cast(:language_id as BIGINT) | 1)
28+
ELSE :language_id
29+
END
30+
FROM ezcontentobject
31+
WHERE id = :content_id)
32+
SQL;
33+
}
34+
}

src/lib/Persistence/Legacy/SharedGateway/DatabasePlatform/SqliteGateway.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
namespace Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform;
1010

1111
use Ibexa\Core\Base\Exceptions\DatabaseException;
12-
use Ibexa\Core\Persistence\Legacy\SharedGateway\Gateway;
1312

14-
final class SqliteGateway implements Gateway
13+
final class SqliteGateway extends AbstractGateway
1514
{
1615
/**
1716
* Error code 7 for a fatal error - taken from an existing driver implementation.
@@ -21,7 +20,7 @@ final class SqliteGateway implements Gateway
2120
private const DB_INT_MAX = 2147483647;
2221

2322
/** @var array<string, int> */
24-
private $lastInsertedIds = [];
23+
private array $lastInsertedIds = [];
2524

2625
public function getColumnNextIntegerValue(
2726
string $tableName,

src/lib/Persistence/Legacy/SharedGateway/Gateway.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ public function getColumnNextIntegerValue(
4141
* It returns integer as all the IDs in the Ibexa Legacy Storage are (big)integers
4242
*/
4343
public function getLastInsertedId(string $sequenceName): int;
44+
45+
/**
46+
* Return a language sub select query for setName.
47+
*/
48+
public function getSetNameLanguageMaskSubQuery(): string;
4449
}
4550

4651
class_alias(Gateway::class, 'eZ\Publish\Core\Persistence\Legacy\SharedGateway\Gateway');

src/lib/Resources/settings/storage_engines/legacy/shared_gateway.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@ services:
66
bind:
77
$connection: '@ibexa.persistence.connection'
88

9+
Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\AbstractGateway: ~
10+
911
Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\FallbackGateway: ~
1012

1113
Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\SqliteGateway:
1214
tags:
1315
- { name: ibexa.storage.legacy.gateway.shared, platform: sqlite }
1416

17+
Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\PostgresqlGateway:
18+
tags:
19+
- { name: ibexa.storage.legacy.gateway.shared, platform: postgresql }
20+
1521
Ibexa\Core\Persistence\Legacy\SharedGateway\GatewayFactory:
1622
arguments:
1723
$fallbackGateway: '@Ibexa\Core\Persistence\Legacy\SharedGateway\DatabasePlatform\FallbackGateway'
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\Integration\Core\Repository\ContentService;
10+
11+
use Ibexa\Tests\Integration\Core\RepositoryTestCase;
12+
use Symfony\Component\Yaml\Yaml;
13+
14+
/**
15+
* @covers \Ibexa\Contracts\Core\Repository\ContentService
16+
*/
17+
final class MaxLanguagesContentServiceTest extends RepositoryTestCase
18+
{
19+
/** @var list<array{languageCode: string, name: string }> */
20+
private static array $languagesRawList = [];
21+
22+
public static function setUpBeforeClass(): void
23+
{
24+
parent::setUpBeforeClass();
25+
26+
self::$languagesRawList = Yaml::parseFile(dirname(__DIR__) . '/_fixtures/max_languages.yaml');
27+
}
28+
29+
/**
30+
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
31+
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
32+
*/
33+
protected function setUp(): void
34+
{
35+
parent::setUp();
36+
37+
$this->prepareMaxLanguages();
38+
}
39+
40+
/**
41+
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception
42+
*/
43+
public function testCreateContent(): void
44+
{
45+
$names = array_merge(...array_map(
46+
static fn (array $languageData): array => [
47+
$languageData['languageCode'] => $languageData['name'] . ' name',
48+
],
49+
self::$languagesRawList
50+
));
51+
$this->createFolder($names);
52+
}
53+
54+
/**
55+
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
56+
* @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
57+
*/
58+
private function prepareMaxLanguages(): void
59+
{
60+
$languageService = self::getLanguageService();
61+
62+
foreach (self::$languagesRawList as $languageData) {
63+
$languageCreateStruct = $languageService->newLanguageCreateStruct();
64+
$languageCreateStruct->languageCode = $languageData['languageCode'];
65+
$languageCreateStruct->name = $languageData['name'];
66+
$languageService->createLanguage($languageCreateStruct);
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)