summaryrefslogtreecommitdiff
path: root/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php')
-rw-r--r--vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php475
1 files changed, 475 insertions, 0 deletions
diff --git a/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php b/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php
new file mode 100644
index 0000000..f973eaa
--- /dev/null
+++ b/vendor/doctrine/dbal/src/Schema/OracleSchemaManager.php
@@ -0,0 +1,475 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\DBAL\Schema;
6
7use Doctrine\DBAL\Exception;
8use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
9use Doctrine\DBAL\Platforms\OraclePlatform;
10use Doctrine\DBAL\Result;
11use Doctrine\DBAL\Types\Type;
12
13use function array_change_key_case;
14use function array_key_exists;
15use function array_values;
16use function assert;
17use function implode;
18use function is_string;
19use function preg_match;
20use function str_contains;
21use function str_replace;
22use function str_starts_with;
23use function strtolower;
24use function strtoupper;
25use function trim;
26
27use const CASE_LOWER;
28
29/**
30 * Oracle Schema Manager.
31 *
32 * @extends AbstractSchemaManager<OraclePlatform>
33 */
34class OracleSchemaManager extends AbstractSchemaManager
35{
36 /**
37 * {@inheritDoc}
38 */
39 protected function _getPortableViewDefinition(array $view): View
40 {
41 $view = array_change_key_case($view, CASE_LOWER);
42
43 return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']);
44 }
45
46 /**
47 * {@inheritDoc}
48 */
49 protected function _getPortableTableDefinition(array $table): string
50 {
51 $table = array_change_key_case($table, CASE_LOWER);
52
53 return $this->getQuotedIdentifierName($table['table_name']);
54 }
55
56 /**
57 * {@inheritDoc}
58 *
59 * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
60 */
61 protected function _getPortableTableIndexesList(array $tableIndexes, string $tableName): array
62 {
63 $indexBuffer = [];
64 foreach ($tableIndexes as $tableIndex) {
65 $tableIndex = array_change_key_case($tableIndex, CASE_LOWER);
66
67 $keyName = strtolower($tableIndex['name']);
68 $buffer = [];
69
70 if ($tableIndex['is_primary'] === 'P') {
71 $keyName = 'primary';
72 $buffer['primary'] = true;
73 $buffer['non_unique'] = false;
74 } else {
75 $buffer['primary'] = false;
76 $buffer['non_unique'] = ! $tableIndex['is_unique'];
77 }
78
79 $buffer['key_name'] = $keyName;
80 $buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
81 $indexBuffer[] = $buffer;
82 }
83
84 return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
85 }
86
87 /**
88 * {@inheritDoc}
89 */
90 protected function _getPortableTableColumnDefinition(array $tableColumn): Column
91 {
92 $tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
93
94 $dbType = strtolower($tableColumn['data_type']);
95 if (str_starts_with($dbType, 'timestamp(')) {
96 if (str_contains($dbType, 'with time zone')) {
97 $dbType = 'timestamptz';
98 } else {
99 $dbType = 'timestamp';
100 }
101 }
102
103 $length = $precision = null;
104 $scale = 0;
105 $fixed = false;
106
107 if (! isset($tableColumn['column_name'])) {
108 $tableColumn['column_name'] = '';
109 }
110
111 assert(array_key_exists('data_default', $tableColumn));
112
113 // Default values returned from database sometimes have trailing spaces.
114 if (is_string($tableColumn['data_default'])) {
115 $tableColumn['data_default'] = trim($tableColumn['data_default']);
116 }
117
118 if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
119 $tableColumn['data_default'] = null;
120 }
121
122 if ($tableColumn['data_default'] !== null) {
123 // Default values returned from database are represented as literal expressions
124 if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches) === 1) {
125 $tableColumn['data_default'] = str_replace("''", "'", $matches[1]);
126 }
127 }
128
129 if ($tableColumn['data_precision'] !== null) {
130 $precision = (int) $tableColumn['data_precision'];
131 }
132
133 if ($tableColumn['data_scale'] !== null) {
134 $scale = (int) $tableColumn['data_scale'];
135 }
136
137 $type = $this->platform->getDoctrineTypeMapping($dbType);
138
139 switch ($dbType) {
140 case 'number':
141 if ($precision === 20 && $scale === 0) {
142 $type = 'bigint';
143 } elseif ($precision === 5 && $scale === 0) {
144 $type = 'smallint';
145 } elseif ($precision === 1 && $scale === 0) {
146 $type = 'boolean';
147 } elseif ($scale > 0) {
148 $type = 'decimal';
149 }
150
151 break;
152
153 case 'varchar':
154 case 'varchar2':
155 case 'nvarchar2':
156 $length = (int) $tableColumn['char_length'];
157 break;
158
159 case 'raw':
160 $length = (int) $tableColumn['data_length'];
161 $fixed = true;
162 break;
163
164 case 'char':
165 case 'nchar':
166 $length = (int) $tableColumn['char_length'];
167 $fixed = true;
168 break;
169 }
170
171 $options = [
172 'notnull' => $tableColumn['nullable'] === 'N',
173 'fixed' => $fixed,
174 'default' => $tableColumn['data_default'],
175 'length' => $length,
176 'precision' => $precision,
177 'scale' => $scale,
178 ];
179
180 if (isset($tableColumn['comments'])) {
181 $options['comment'] = $tableColumn['comments'];
182 }
183
184 return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
185 }
186
187 /**
188 * {@inheritDoc}
189 */
190 protected function _getPortableTableForeignKeysList(array $tableForeignKeys): array
191 {
192 $list = [];
193 foreach ($tableForeignKeys as $value) {
194 $value = array_change_key_case($value, CASE_LOWER);
195 if (! isset($list[$value['constraint_name']])) {
196 if ($value['delete_rule'] === 'NO ACTION') {
197 $value['delete_rule'] = null;
198 }
199
200 $list[$value['constraint_name']] = [
201 'name' => $this->getQuotedIdentifierName($value['constraint_name']),
202 'local' => [],
203 'foreign' => [],
204 'foreignTable' => $value['references_table'],
205 'onDelete' => $value['delete_rule'],
206 ];
207 }
208
209 $localColumn = $this->getQuotedIdentifierName($value['local_column']);
210 $foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
211
212 $list[$value['constraint_name']]['local'][$value['position']] = $localColumn;
213 $list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
214 }
215
216 return parent::_getPortableTableForeignKeysList($list);
217 }
218
219 /**
220 * {@inheritDoc}
221 */
222 protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey): ForeignKeyConstraint
223 {
224 return new ForeignKeyConstraint(
225 array_values($tableForeignKey['local']),
226 $this->getQuotedIdentifierName($tableForeignKey['foreignTable']),
227 array_values($tableForeignKey['foreign']),
228 $this->getQuotedIdentifierName($tableForeignKey['name']),
229 ['onDelete' => $tableForeignKey['onDelete']],
230 );
231 }
232
233 /**
234 * {@inheritDoc}
235 */
236 protected function _getPortableSequenceDefinition(array $sequence): Sequence
237 {
238 $sequence = array_change_key_case($sequence, CASE_LOWER);
239
240 return new Sequence(
241 $this->getQuotedIdentifierName($sequence['sequence_name']),
242 (int) $sequence['increment_by'],
243 (int) $sequence['min_value'],
244 );
245 }
246
247 /**
248 * {@inheritDoc}
249 */
250 protected function _getPortableDatabaseDefinition(array $database): string
251 {
252 $database = array_change_key_case($database, CASE_LOWER);
253
254 return $database['username'];
255 }
256
257 public function createDatabase(string $database): void
258 {
259 $statement = $this->platform->getCreateDatabaseSQL($database);
260
261 $params = $this->connection->getParams();
262
263 if (isset($params['password'])) {
264 $statement .= ' IDENTIFIED BY ' . $params['password'];
265 }
266
267 $this->connection->executeStatement($statement);
268
269 $statement = 'GRANT DBA TO ' . $database;
270 $this->connection->executeStatement($statement);
271 }
272
273 /** @throws Exception */
274 protected function dropAutoincrement(string $table): bool
275 {
276 $sql = $this->platform->getDropAutoincrementSql($table);
277 foreach ($sql as $query) {
278 $this->connection->executeStatement($query);
279 }
280
281 return true;
282 }
283
284 public function dropTable(string $name): void
285 {
286 try {
287 $this->dropAutoincrement($name);
288 } catch (DatabaseObjectNotFoundException) {
289 }
290
291 parent::dropTable($name);
292 }
293
294 /**
295 * Returns the quoted representation of the given identifier name.
296 *
297 * Quotes non-uppercase identifiers explicitly to preserve case
298 * and thus make references to the particular identifier work.
299 */
300 private function getQuotedIdentifierName(string $identifier): string
301 {
302 if (preg_match('/[a-z]/', $identifier) === 1) {
303 return $this->platform->quoteIdentifier($identifier);
304 }
305
306 return $identifier;
307 }
308
309 protected function selectTableNames(string $databaseName): Result
310 {
311 $sql = <<<'SQL'
312SELECT TABLE_NAME
313FROM ALL_TABLES
314WHERE OWNER = :OWNER
315ORDER BY TABLE_NAME
316SQL;
317
318 return $this->connection->executeQuery($sql, ['OWNER' => $databaseName]);
319 }
320
321 protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
322 {
323 $sql = 'SELECT';
324
325 if ($tableName === null) {
326 $sql .= ' C.TABLE_NAME,';
327 }
328
329 $sql .= <<<'SQL'
330 C.COLUMN_NAME,
331 C.DATA_TYPE,
332 C.DATA_DEFAULT,
333 C.DATA_PRECISION,
334 C.DATA_SCALE,
335 C.CHAR_LENGTH,
336 C.DATA_LENGTH,
337 C.NULLABLE,
338 D.COMMENTS
339 FROM ALL_TAB_COLUMNS C
340 INNER JOIN ALL_TABLES T
341 ON T.OWNER = C.OWNER
342 AND T.TABLE_NAME = C.TABLE_NAME
343 LEFT JOIN ALL_COL_COMMENTS D
344 ON D.OWNER = C.OWNER
345 AND D.TABLE_NAME = C.TABLE_NAME
346 AND D.COLUMN_NAME = C.COLUMN_NAME
347SQL;
348
349 $conditions = ['C.OWNER = :OWNER'];
350 $params = ['OWNER' => $databaseName];
351
352 if ($tableName !== null) {
353 $conditions[] = 'C.TABLE_NAME = :TABLE_NAME';
354 $params['TABLE_NAME'] = $tableName;
355 }
356
357 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.COLUMN_ID';
358
359 return $this->connection->executeQuery($sql, $params);
360 }
361
362 protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
363 {
364 $sql = 'SELECT';
365
366 if ($tableName === null) {
367 $sql .= ' IND_COL.TABLE_NAME,';
368 }
369
370 $sql .= <<<'SQL'
371 IND_COL.INDEX_NAME AS NAME,
372 IND.INDEX_TYPE AS TYPE,
373 DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE,
374 IND_COL.COLUMN_NAME,
375 IND_COL.COLUMN_POSITION AS COLUMN_POS,
376 CON.CONSTRAINT_TYPE AS IS_PRIMARY
377 FROM ALL_IND_COLUMNS IND_COL
378 LEFT JOIN ALL_INDEXES IND
379 ON IND.OWNER = IND_COL.INDEX_OWNER
380 AND IND.INDEX_NAME = IND_COL.INDEX_NAME
381 LEFT JOIN ALL_CONSTRAINTS CON
382 ON CON.OWNER = IND_COL.INDEX_OWNER
383 AND CON.INDEX_NAME = IND_COL.INDEX_NAME
384SQL;
385
386 $conditions = ['IND_COL.INDEX_OWNER = :OWNER'];
387 $params = ['OWNER' => $databaseName];
388
389 if ($tableName !== null) {
390 $conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME';
391 $params['TABLE_NAME'] = $tableName;
392 }
393
394 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME'
395 . ', IND_COL.COLUMN_POSITION';
396
397 return $this->connection->executeQuery($sql, $params);
398 }
399
400 protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
401 {
402 $sql = 'SELECT';
403
404 if ($tableName === null) {
405 $sql .= ' COLS.TABLE_NAME,';
406 }
407
408 $sql .= <<<'SQL'
409 ALC.CONSTRAINT_NAME,
410 ALC.DELETE_RULE,
411 COLS.COLUMN_NAME LOCAL_COLUMN,
412 COLS.POSITION,
413 R_COLS.TABLE_NAME REFERENCES_TABLE,
414 R_COLS.COLUMN_NAME FOREIGN_COLUMN
415 FROM ALL_CONS_COLUMNS COLS
416 LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME
417 LEFT JOIN ALL_CONS_COLUMNS R_COLS ON R_COLS.OWNER = ALC.R_OWNER AND
418 R_COLS.CONSTRAINT_NAME = ALC.R_CONSTRAINT_NAME AND
419 R_COLS.POSITION = COLS.POSITION
420SQL;
421
422 $conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER'];
423 $params = ['OWNER' => $databaseName];
424
425 if ($tableName !== null) {
426 $conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME';
427 $params['TABLE_NAME'] = $tableName;
428 }
429
430 $sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME'
431 . ', COLS.POSITION';
432
433 return $this->connection->executeQuery($sql, $params);
434 }
435
436 /**
437 * {@inheritDoc}
438 */
439 protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
440 {
441 $sql = 'SELECT TABLE_NAME, COMMENTS';
442
443 $conditions = ['OWNER = :OWNER'];
444 $params = ['OWNER' => $databaseName];
445
446 if ($tableName !== null) {
447 $conditions[] = 'TABLE_NAME = :TABLE_NAME';
448 $params['TABLE_NAME'] = $tableName;
449 }
450
451 $sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions);
452
453 /** @var array<string,array<string,mixed>> $metadata */
454 $metadata = $this->connection->executeQuery($sql, $params)
455 ->fetchAllAssociativeIndexed();
456
457 $tableOptions = [];
458 foreach ($metadata as $table => $data) {
459 $data = array_change_key_case($data, CASE_LOWER);
460
461 $tableOptions[$table] = [
462 'comment' => $data['comments'],
463 ];
464 }
465
466 return $tableOptions;
467 }
468
469 protected function normalizeName(string $name): string
470 {
471 $identifier = new Identifier($name);
472
473 return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name);
474 }
475}