1<?php
2/**
3 * @author Morris Jobke <hey@morrisjobke.de>
4 * @author Robin Appelman <icewind@owncloud.com>
5 * @author Thomas Müller <thomas.mueller@tmit.eu>
6 *
7 * @copyright Copyright (c) 2018, ownCloud GmbH
8 * @license AGPL-3.0
9 *
10 * This code is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License, version 3,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Affero General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License, version 3,
20 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21 *
22 */
23
24namespace OC\DB;
25
26use Doctrine\DBAL\Schema\Column;
27use Doctrine\DBAL\Schema\ColumnDiff;
28use Doctrine\DBAL\Schema\Index;
29use Doctrine\DBAL\Schema\Schema;
30use Doctrine\DBAL\Schema\Table;
31use Doctrine\DBAL\Schema\ForeignKeyConstraint;
32
33class OracleMigrator extends Migrator {
34
35	/**
36	 * Quote a column's name but changing the name requires recreating
37	 * the column instance and copying over all properties.
38	 *
39	 * @param Column $column old column
40	 * @return Column new column instance with new name
41	 */
42	protected function quoteColumn($column) {
43		$newColumn = new Column(
44			$this->connection->quoteIdentifier($column->getName()),
45			$column->getType()
46		);
47		$newColumn->setAutoincrement($column->getAutoincrement());
48		$newColumn->setColumnDefinition($column->getColumnDefinition());
49		$newColumn->setComment($column->getComment());
50		$newColumn->setDefault($column->getDefault());
51		$newColumn->setFixed($column->getFixed());
52		$newColumn->setLength($column->getLength());
53		$newColumn->setNotnull($column->getNotnull());
54		$newColumn->setPrecision($column->getPrecision());
55		$newColumn->setScale($column->getScale());
56		$newColumn->setUnsigned($column->getUnsigned());
57		$newColumn->setPlatformOptions($column->getPlatformOptions());
58		$newColumn->setCustomSchemaOptions($column->getPlatformOptions());
59		return $newColumn;
60	}
61
62	/**
63	 * Quote an index's name but changing the name requires recreating
64	 * the index instance and copying over all properties.
65	 *
66	 * @param Index $index old index
67	 * @return Index new index instance with new name
68	 */
69	protected function quoteIndex($index) {
70		return new Index(
71		//TODO migrate existing uppercase indexes, then $this->connection->quoteIdentifier($index->getName()),
72			$index->getName(),
73			\array_map(function ($columnName) {
74				return $this->connection->quoteIdentifier($columnName);
75			}, $index->getColumns()),
76			$index->isUnique(),
77			$index->isPrimary(),
78			$index->getFlags(),
79			$index->getOptions()
80		);
81	}
82
83	/**
84	 * Quote an ForeignKeyConstraint's name but changing the name requires recreating
85	 * the ForeignKeyConstraint instance and copying over all properties.
86	 *
87	 * @param ForeignKeyConstraint $fkc old fkc
88	 * @return ForeignKeyConstraint new fkc instance with new name
89	 */
90	protected function quoteForeignKeyConstraint($fkc) {
91		return new ForeignKeyConstraint(
92			\array_map(function ($columnName) {
93				return $this->connection->quoteIdentifier($columnName);
94			}, $fkc->getLocalColumns()),
95			$this->connection->quoteIdentifier($fkc->getForeignTableName()),
96			\array_map(function ($columnName) {
97				return $this->connection->quoteIdentifier($columnName);
98			}, $fkc->getForeignColumns()),
99			$fkc->getName(),
100			$fkc->getOptions()
101		);
102	}
103
104	/**
105	 * @param Schema $targetSchema
106	 * @param \Doctrine\DBAL\Connection $connection
107	 * @return \Doctrine\DBAL\Schema\SchemaDiff
108	 */
109	protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) {
110		$schemaDiff = parent::getDiff($targetSchema, $connection);
111
112		// oracle forces us to quote the identifiers
113		$schemaDiff->newTables = \array_map(function (Table $table) {
114			return new Table(
115				$this->connection->quoteIdentifier($table->getName()),
116				\array_map(function (Column $column) {
117					return $this->quoteColumn($column);
118				}, $table->getColumns()),
119				\array_map(function (Index $index) {
120					return $this->quoteIndex($index);
121				}, $table->getIndexes()),
122				\array_map(function (ForeignKeyConstraint $fck) {
123					return $this->quoteForeignKeyConstraint($fck);
124				}, $table->getForeignKeys()),
125				0,
126				$table->getOptions()
127			);
128		}, $schemaDiff->newTables);
129
130		$schemaDiff->removedTables = \array_map(function (Table $table) {
131			return new Table(
132				$this->connection->quoteIdentifier($table->getName()),
133				$table->getColumns(),
134				$table->getIndexes(),
135				$table->getForeignKeys(),
136				0,
137				$table->getOptions()
138			);
139		}, $schemaDiff->removedTables);
140
141		foreach ($schemaDiff->changedTables as $tableDiff) {
142			$tableDiff->name = $this->connection->quoteIdentifier($tableDiff->name);
143
144			$tableDiff->addedColumns = \array_map(function (Column $column) {
145				return $this->quoteColumn($column);
146			}, $tableDiff->addedColumns);
147
148			foreach ($tableDiff->changedColumns as $column) {
149				$column->oldColumnName = $this->connection->quoteIdentifier($column->oldColumnName);
150				// auto increment is not relevant for oracle and can anyhow not be applied on change
151				$column->changedProperties = \array_diff($column->changedProperties, ['autoincrement', 'unsigned']);
152			}
153			// remove columns that no longer have changed (because autoincrement and unsigned are not supported)
154			$tableDiff->changedColumns = \array_filter($tableDiff->changedColumns, function (ColumnDiff $column) {
155				return \count($column->changedProperties) > 0;
156			});
157
158			$tableDiff->removedColumns = \array_map(function (Column $column) {
159				return $this->quoteColumn($column);
160			}, $tableDiff->removedColumns);
161
162			$tableDiff->renamedColumns = \array_map(function (Column $column) {
163				return $this->quoteColumn($column);
164			}, $tableDiff->renamedColumns);
165
166			$tableDiff->addedIndexes = \array_map(function (Index $index) {
167				return $this->quoteIndex($index);
168			}, $tableDiff->addedIndexes);
169
170			$tableDiff->changedIndexes = \array_map(function (Index $index) {
171				return $this->quoteIndex($index);
172			}, $tableDiff->changedIndexes);
173
174			$tableDiff->removedIndexes = \array_map(function (Index $index) {
175				return $this->quoteIndex($index);
176			}, $tableDiff->removedIndexes);
177
178			$tableDiff->renamedIndexes = \array_map(function (Index $index) {
179				return $this->quoteIndex($index);
180			}, $tableDiff->renamedIndexes);
181
182			$tableDiff->addedForeignKeys = \array_map(function (ForeignKeyConstraint $fkc) {
183				return $this->quoteForeignKeyConstraint($fkc);
184			}, $tableDiff->addedForeignKeys);
185
186			$tableDiff->changedForeignKeys = \array_map(function (ForeignKeyConstraint $fkc) {
187				return $this->quoteForeignKeyConstraint($fkc);
188			}, $tableDiff->changedForeignKeys);
189
190			$tableDiff->removedForeignKeys = \array_map(function (ForeignKeyConstraint $fkc) {
191				return $this->quoteForeignKeyConstraint($fkc);
192			}, $tableDiff->removedForeignKeys);
193		}
194
195		return $schemaDiff;
196	}
197
198	/**
199	 * @param string $name
200	 * @return string
201	 */
202	protected function generateTemporaryTableName($name) {
203		return 'oc_' . \uniqid();
204	}
205
206	/**
207	 * @param $statement
208	 * @return string
209	 */
210	protected function convertStatementToScript($statement) {
211		if (\substr($statement, -1) === ';') {
212			return $statement . PHP_EOL . '/' . PHP_EOL;
213		}
214		$script = $statement . ';';
215		$script .= PHP_EOL;
216		$script .= PHP_EOL;
217		return $script;
218	}
219
220	protected function getFilterExpression() {
221		return '/^"' . \preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
222	}
223}
224