Skip to content

Commit 39787ee

Browse files
authored
Merge pull request #79 from LibreCodeCoop/fix/oracle-index-constraints
fix(migration): set explicit primary key and index names for Oracle compatibility
2 parents cfe4265 + 71d2eef commit 39787ee

File tree

2 files changed

+162
-3
lines changed

2 files changed

+162
-3
lines changed

lib/Migration/Version1000Date20260309120000.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
6464
$table->addColumn('created_at', Types::DATETIME, []);
6565
$table->addColumn('updated_at', Types::DATETIME, []);
6666

67-
$table->setPrimaryKey(['id']);
67+
$table->setPrimaryKey(['id'], 'pf_definitions_pk');
6868
$table->addUniqueIndex(['field_key'], 'profile_fields_def_key_uk');
6969
$table->addIndex(['active', 'sort_order'], 'pf_def_active_sort_idx');
7070
}
@@ -91,8 +91,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
9191
]);
9292
$table->addColumn('updated_at', Types::DATETIME, []);
9393

94-
$table->setPrimaryKey(['id']);
95-
$table->addUniqueIndex(['field_definition_id', 'user_uid'], 'profile_fields_val_field_user_uk');
94+
$table->setPrimaryKey(['id'], 'pf_values_pk');
95+
$table->addUniqueIndex(['field_definition_id', 'user_uid'], 'pf_val_def_user_uk');
9696
$table->addIndex(['user_uid'], 'profile_fields_val_user_idx');
9797
$table->addIndex(['field_definition_id'], 'profile_fields_val_field_idx');
9898
$table->addForeignKeyConstraint(
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OCA\ProfileFields\Tests\Unit\Migration;
11+
12+
use Doctrine\DBAL\Platforms\AbstractPlatform;
13+
use Doctrine\DBAL\Schema\Schema;
14+
use Doctrine\DBAL\Schema\Table;
15+
use OCA\ProfileFields\Migration\Version1000Date20260309120000;
16+
use OCP\DB\ISchemaWrapper;
17+
use OCP\Migration\IOutput;
18+
use PHPUnit\Framework\TestCase;
19+
20+
/**
21+
* Verifies Nextcloud Oracle naming constraints for Version1000Date20260309120000.
22+
*
23+
* NC 32 ensureOracleConstraints rules (lib/private/DB/MigrationService.php):
24+
* - Table names (without prefix) must be <= 27 chars
25+
* - Column names must be <= 30 chars
26+
* - Index names must be <= 30 chars
27+
* - FK names must be <= 30 chars
28+
* - Primary key: if default name ('primary'), table name without prefix must be < 23 chars;
29+
* otherwise explicit name must be <= 30 chars
30+
*/
31+
class Version1000Date20260309120000Test extends TestCase {
32+
private const PREFIX = 'oc_';
33+
private const ORACLE_MAX_NAME = 30;
34+
private const ORACLE_MAX_TABLE = 27;
35+
/** Table name (without prefix) must be < 23 chars to use default PK name */
36+
private const ORACLE_MAX_TABLE_FOR_DEFAULT_PK = 23;
37+
38+
private Schema $schema;
39+
40+
protected function setUp(): void {
41+
parent::setUp();
42+
$this->schema = new Schema();
43+
}
44+
45+
private function buildSchemaWrapper(Schema $schema, string $prefix): ISchemaWrapper {
46+
return new class($schema, $prefix) implements ISchemaWrapper {
47+
public function __construct(
48+
private Schema $schema,
49+
private string $prefix,
50+
) {
51+
}
52+
53+
public function hasTable($tableName): bool {
54+
return $this->schema->hasTable($this->prefix . $tableName);
55+
}
56+
57+
public function createTable($tableName): Table {
58+
return $this->schema->createTable($this->prefix . $tableName);
59+
}
60+
61+
public function getTable($tableName): Table {
62+
return $this->schema->getTable($this->prefix . $tableName);
63+
}
64+
65+
public function dropTable($tableName): Schema {
66+
return $this->schema->dropTable($this->prefix . $tableName);
67+
}
68+
69+
public function getTables(): array {
70+
return $this->schema->getTables();
71+
}
72+
73+
public function getTableNames(): array {
74+
return $this->schema->getTableNames();
75+
}
76+
77+
public function getTableNamesWithoutPrefix(): array {
78+
return array_map(
79+
fn (string $n) => substr($n, strlen($this->prefix)),
80+
$this->schema->getTableNames(),
81+
);
82+
}
83+
84+
public function getDatabasePlatform(): AbstractPlatform {
85+
throw new \RuntimeException('not implemented in test');
86+
}
87+
88+
public function dropAutoincrementColumn(string $table, string $column): void {
89+
throw new \RuntimeException('not implemented in test');
90+
}
91+
};
92+
}
93+
94+
public function testAllNamesPassOracleConstraints(): void {
95+
$wrapper = $this->buildSchemaWrapper($this->schema, self::PREFIX);
96+
$output = $this->createMock(IOutput::class);
97+
$migration = new Version1000Date20260309120000();
98+
99+
$migration->changeSchema($output, fn () => $wrapper, ['tablePrefix' => self::PREFIX]);
100+
101+
$prefixLen = strlen(self::PREFIX);
102+
103+
foreach ($this->schema->getTables() as $table) {
104+
$tableNameWithoutPrefix = substr($table->getName(), $prefixLen);
105+
106+
$this->assertLessThanOrEqual(
107+
self::ORACLE_MAX_TABLE,
108+
strlen($tableNameWithoutPrefix),
109+
"Table '{$table->getName()}' name without prefix exceeds Oracle max of " . self::ORACLE_MAX_TABLE . ' chars',
110+
);
111+
112+
foreach ($table->getColumns() as $column) {
113+
$this->assertLessThanOrEqual(
114+
self::ORACLE_MAX_NAME,
115+
strlen($column->getName()),
116+
"Column '{$column->getName()}' on '{$table->getName()}' exceeds Oracle max of " . self::ORACLE_MAX_NAME . ' chars',
117+
);
118+
}
119+
120+
foreach ($table->getIndexes() as $index) {
121+
$this->assertLessThanOrEqual(
122+
self::ORACLE_MAX_NAME,
123+
strlen($index->getName()),
124+
"Index '{$index->getName()}' on '{$table->getName()}' exceeds Oracle max of " . self::ORACLE_MAX_NAME . ' chars',
125+
);
126+
}
127+
128+
foreach ($table->getForeignKeys() as $fk) {
129+
$this->assertLessThanOrEqual(
130+
self::ORACLE_MAX_NAME,
131+
strlen($fk->getName()),
132+
"FK '{$fk->getName()}' on '{$table->getName()}' exceeds Oracle max of " . self::ORACLE_MAX_NAME . ' chars',
133+
);
134+
}
135+
136+
$primaryKey = $table->getPrimaryKey();
137+
if ($primaryKey !== null) {
138+
$pkName = strtolower($primaryKey->getName());
139+
if ($pkName === 'primary') {
140+
// Default PK name: table name without prefix must be strictly < 23 chars
141+
$this->assertLessThan(
142+
self::ORACLE_MAX_TABLE_FOR_DEFAULT_PK,
143+
strlen($tableNameWithoutPrefix),
144+
"Table '{$tableNameWithoutPrefix}' uses default primary key name but its length "
145+
. strlen($tableNameWithoutPrefix)
146+
. ' >= ' . self::ORACLE_MAX_TABLE_FOR_DEFAULT_PK
147+
. '; set an explicit primary key name <= 30 chars',
148+
);
149+
} else {
150+
$this->assertLessThanOrEqual(
151+
self::ORACLE_MAX_NAME,
152+
strlen($pkName),
153+
"Primary key name '$pkName' on '{$table->getName()}' exceeds Oracle max of " . self::ORACLE_MAX_NAME . ' chars',
154+
);
155+
}
156+
}
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)