Passed
Push — master ( 459e0b...37feee )
by Morris
12:59 queued 12s
created

Migrator::copyTable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author martin-rueegg <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author tbelau666 <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Victor Dubiniuk <[email protected]>
14
 * @author Vincent Petry <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program. If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OC\DB;
33
34
use Doctrine\DBAL\Exception;
35
use Doctrine\DBAL\Platforms\MySQLPlatform;
36
use Doctrine\DBAL\Schema\AbstractAsset;
37
use Doctrine\DBAL\Schema\Comparator;
38
use Doctrine\DBAL\Schema\Schema;
39
use Doctrine\DBAL\Types\StringType;
40
use Doctrine\DBAL\Types\Type;
41
use OCP\IConfig;
42
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
43
use Symfony\Component\EventDispatcher\GenericEvent;
44
use function preg_match;
45
46
class Migrator {
47
48
	/** @var \Doctrine\DBAL\Connection */
49
	protected $connection;
50
51
	/** @var IConfig */
52
	protected $config;
53
54
	/** @var EventDispatcherInterface  */
55
	private $dispatcher;
56
57
	/** @var bool */
58
	private $noEmit = false;
59
60
	/**
61
	 * @param \Doctrine\DBAL\Connection $connection
62
	 * @param IConfig $config
63
	 * @param EventDispatcherInterface $dispatcher
64
	 */
65
	public function __construct(\Doctrine\DBAL\Connection $connection,
66
								IConfig $config,
67
								EventDispatcherInterface $dispatcher = null) {
68
		$this->connection = $connection;
69
		$this->config = $config;
70
		$this->dispatcher = $dispatcher;
71
	}
72
73
	/**
74
	 * @param \Doctrine\DBAL\Schema\Schema $targetSchema
75
	 *
76
	 * @throws Exception
77
	 */
78
	public function migrate(Schema $targetSchema) {
79
		$this->noEmit = true;
80
		$this->applySchema($targetSchema);
81
	}
82
83
	/**
84
	 * @param \Doctrine\DBAL\Schema\Schema $targetSchema
85
	 * @return string
86
	 */
87
	public function generateChangeScript(Schema $targetSchema) {
88
		$schemaDiff = $this->getDiff($targetSchema, $this->connection);
89
90
		$script = '';
91
		$sqls = $schemaDiff->toSql($this->connection->getDatabasePlatform());
92
		foreach ($sqls as $sql) {
93
			$script .= $this->convertStatementToScript($sql);
94
		}
95
96
		return $script;
97
	}
98
99
	/**
100
	 * @throws Exception
101
	 */
102
	public function createSchema() {
103
		$this->connection->getConfiguration()->setSchemaAssetsFilter(function ($asset) {
104
			/** @var string|AbstractAsset $asset */
105
			$filterExpression = $this->getFilterExpression();
106
			if ($asset instanceof AbstractAsset) {
107
				return preg_match($filterExpression, $asset->getName()) === 1;
108
			}
109
			return preg_match($filterExpression, $asset) === 1;
110
		});
111
		return $this->connection->getSchemaManager()->createSchema();
112
	}
113
114
	/**
115
	 * @param Schema $targetSchema
116
	 * @param \Doctrine\DBAL\Connection $connection
117
	 * @return \Doctrine\DBAL\Schema\SchemaDiff
118
	 */
119
	protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) {
120
		// adjust varchar columns with a length higher then getVarcharMaxLength to clob
121
		foreach ($targetSchema->getTables() as $table) {
122
			foreach ($table->getColumns() as $column) {
123
				if ($column->getType() instanceof StringType) {
124
					if ($column->getLength() > $connection->getDatabasePlatform()->getVarcharMaxLength()) {
125
						$column->setType(Type::getType('text'));
126
						$column->setLength(null);
127
					}
128
				}
129
			}
130
		}
131
132
		$this->connection->getConfiguration()->setSchemaAssetsFilter(function ($asset) {
133
			/** @var string|AbstractAsset $asset */
134
			$filterExpression = $this->getFilterExpression();
135
			if ($asset instanceof AbstractAsset) {
136
				return preg_match($filterExpression, $asset->getName()) === 1;
137
			}
138
			return preg_match($filterExpression, $asset) === 1;
139
		});
140
		$sourceSchema = $connection->getSchemaManager()->createSchema();
141
142
		// remove tables we don't know about
143
		foreach ($sourceSchema->getTables() as $table) {
144
			if (!$targetSchema->hasTable($table->getName())) {
145
				$sourceSchema->dropTable($table->getName());
146
			}
147
		}
148
		// remove sequences we don't know about
149
		foreach ($sourceSchema->getSequences() as $table) {
150
			if (!$targetSchema->hasSequence($table->getName())) {
151
				$sourceSchema->dropSequence($table->getName());
152
			}
153
		}
154
155
		$comparator = new Comparator();
156
		return $comparator->compare($sourceSchema, $targetSchema);
157
	}
158
159
	/**
160
	 * @param \Doctrine\DBAL\Schema\Schema $targetSchema
161
	 * @param \Doctrine\DBAL\Connection $connection
162
	 *
163
	 * @throws Exception
164
	 */
165
	protected function applySchema(Schema $targetSchema, \Doctrine\DBAL\Connection $connection = null) {
166
		if (is_null($connection)) {
167
			$connection = $this->connection;
168
		}
169
170
		$schemaDiff = $this->getDiff($targetSchema, $connection);
171
172
		if (!$connection->getDatabasePlatform() instanceof MySQLPlatform) {
173
			$connection->beginTransaction();
174
		}
175
		$sqls = $schemaDiff->toSql($connection->getDatabasePlatform());
176
		$step = 0;
177
		foreach ($sqls as $sql) {
178
			$this->emit($sql, $step++, count($sqls));
179
			$connection->query($sql);
180
		}
181
		if (!$connection->getDatabasePlatform() instanceof MySQLPlatform) {
182
			$connection->commit();
183
		}
184
	}
185
186
	/**
187
	 * @param $statement
188
	 * @return string
189
	 */
190
	protected function convertStatementToScript($statement) {
191
		$script = $statement . ';';
192
		$script .= PHP_EOL;
193
		$script .= PHP_EOL;
194
		return $script;
195
	}
196
197
	protected function getFilterExpression() {
198
		return '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
199
	}
200
201
	protected function emit($sql, $step, $max) {
202
		if ($this->noEmit) {
203
			return;
204
		}
205
		if (is_null($this->dispatcher)) {
206
			return;
207
		}
208
		$this->dispatcher->dispatch('\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step + 1, $max]));
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with new Symfony\Component\Ev...array($step + 1, $max)). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

208
		$this->dispatcher->/** @scrutinizer ignore-call */ 
209
                     dispatch('\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step + 1, $max]));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
'\OC\DB\Migrator::executeSql' of type string is incompatible with the type object expected by parameter $event of Symfony\Contracts\EventD...erInterface::dispatch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

208
		$this->dispatcher->dispatch(/** @scrutinizer ignore-type */ '\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step + 1, $max]));
Loading history...
209
	}
210
}
211