Completed
Pull Request — master (#4071)
by Joas
16:28 queued 05:35
created
core/Command/Db/ConvertType.php 2 patches
Indentation   +355 added lines, -355 removed lines patch added patch discarded remove patch
@@ -45,359 +45,359 @@
 block discarded – undo
45 45
 use Symfony\Component\Console\Question\Question;
46 46
 
47 47
 class ConvertType extends Command implements CompletionAwareInterface {
48
-	/**
49
-	 * @var \OCP\IConfig
50
-	 */
51
-	protected $config;
52
-
53
-	/**
54
-	 * @var \OC\DB\ConnectionFactory
55
-	 */
56
-	protected $connectionFactory;
57
-
58
-	/** @var array */
59
-	protected $columnTypes;
60
-
61
-	/**
62
-	 * @param \OCP\IConfig $config
63
-	 * @param \OC\DB\ConnectionFactory $connectionFactory
64
-	 */
65
-	public function __construct(IConfig $config, ConnectionFactory $connectionFactory) {
66
-		$this->config = $config;
67
-		$this->connectionFactory = $connectionFactory;
68
-		parent::__construct();
69
-	}
70
-
71
-	protected function configure() {
72
-		$this
73
-			->setName('db:convert-type')
74
-			->setDescription('Convert the Nextcloud database to the newly configured one')
75
-			->addArgument(
76
-				'type',
77
-				InputArgument::REQUIRED,
78
-				'the type of the database to convert to'
79
-			)
80
-			->addArgument(
81
-				'username',
82
-				InputArgument::REQUIRED,
83
-				'the username of the database to convert to'
84
-			)
85
-			->addArgument(
86
-				'hostname',
87
-				InputArgument::REQUIRED,
88
-				'the hostname of the database to convert to'
89
-			)
90
-			->addArgument(
91
-				'database',
92
-				InputArgument::REQUIRED,
93
-				'the name of the database to convert to'
94
-			)
95
-			->addOption(
96
-				'port',
97
-				null,
98
-				InputOption::VALUE_REQUIRED,
99
-				'the port of the database to convert to'
100
-			)
101
-			->addOption(
102
-				'password',
103
-				null,
104
-				InputOption::VALUE_REQUIRED,
105
-				'the password of the database to convert to. Will be asked when not specified. Can also be passed via stdin.'
106
-			)
107
-			->addOption(
108
-				'clear-schema',
109
-				null,
110
-				InputOption::VALUE_NONE,
111
-				'remove all tables from the destination database'
112
-			)
113
-			->addOption(
114
-				'all-apps',
115
-				null,
116
-				InputOption::VALUE_NONE,
117
-				'whether to create schema for all apps instead of only installed apps'
118
-			)
119
-			->addOption(
120
-				'chunk-size',
121
-				null,
122
-				InputOption::VALUE_REQUIRED,
123
-				'the maximum number of database rows to handle in a single query, bigger tables will be handled in chunks of this size. Lower this if the process runs out of memory during conversion.',
124
-				1000
125
-			)
126
-		;
127
-	}
128
-
129
-	protected function validateInput(InputInterface $input, OutputInterface $output) {
130
-		$type = $this->connectionFactory->normalizeType($input->getArgument('type'));
131
-		if ($type === 'sqlite3') {
132
-			throw new \InvalidArgumentException(
133
-				'Converting to SQLite (sqlite3) is currently not supported.'
134
-			);
135
-		}
136
-		if ($type === $this->config->getSystemValue('dbtype', '')) {
137
-			throw new \InvalidArgumentException(sprintf(
138
-				'Can not convert from %1$s to %1$s.',
139
-				$type
140
-			));
141
-		}
142
-		if ($type === 'oci' && $input->getOption('clear-schema')) {
143
-			// Doctrine unconditionally tries (at least in version 2.3)
144
-			// to drop sequence triggers when dropping a table, even though
145
-			// such triggers may not exist. This results in errors like
146
-			// "ORA-04080: trigger 'OC_STORAGES_AI_PK' does not exist".
147
-			throw new \InvalidArgumentException(
148
-				'The --clear-schema option is not supported when converting to Oracle (oci).'
149
-			);
150
-		}
151
-	}
152
-
153
-	protected function readPassword(InputInterface $input, OutputInterface $output) {
154
-		// Explicitly specified password
155
-		if ($input->getOption('password')) {
156
-			return;
157
-		}
158
-
159
-		// Read from stdin. stream_set_blocking is used to prevent blocking
160
-		// when nothing is passed via stdin.
161
-		stream_set_blocking(STDIN, 0);
162
-		$password = file_get_contents('php://stdin');
163
-		stream_set_blocking(STDIN, 1);
164
-		if (trim($password) !== '') {
165
-			$input->setOption('password', $password);
166
-			return;
167
-		}
168
-
169
-		// Read password by interacting
170
-		if ($input->isInteractive()) {
171
-			/** @var QuestionHelper $helper */
172
-			$helper = $this->getHelper('question');
173
-			$question = new Question('What is the database password?');
174
-			$question->setHidden(true);
175
-			$question->setHiddenFallback(false);
176
-			$password = $helper->ask($input, $output, $question);
177
-			$input->setOption('password', $password);
178
-			return;
179
-		}
180
-	}
181
-
182
-	protected function execute(InputInterface $input, OutputInterface $output) {
183
-		$this->validateInput($input, $output);
184
-		$this->readPassword($input, $output);
185
-
186
-		$fromDB = \OC::$server->getDatabaseConnection();
187
-		$toDB = $this->getToDBConnection($input, $output);
188
-
189
-		if ($input->getOption('clear-schema')) {
190
-			$this->clearSchema($toDB, $input, $output);
191
-		}
192
-
193
-		$this->createSchema($toDB, $input, $output);
194
-
195
-		$toTables = $this->getTables($toDB);
196
-		$fromTables = $this->getTables($fromDB);
197
-
198
-		// warn/fail if there are more tables in 'from' database
199
-		$extraFromTables = array_diff($fromTables, $toTables);
200
-		if (!empty($extraFromTables)) {
201
-			$output->writeln('<comment>The following tables will not be converted:</comment>');
202
-			$output->writeln($extraFromTables);
203
-			if (!$input->getOption('all-apps')) {
204
-				$output->writeln('<comment>Please note that tables belonging to available but currently not installed apps</comment>');
205
-				$output->writeln('<comment>can be included by specifying the --all-apps option.</comment>');
206
-			}
207
-
208
-			/** @var QuestionHelper $helper */
209
-			$helper = $this->getHelper('question');
210
-			$question = new ConfirmationQuestion('Continue with the conversion (y/n)? [n] ', false);
211
-
212
-			if (!$helper->ask($input, $output, $question)) {
213
-				return;
214
-			}
215
-		}
216
-		$intersectingTables = array_intersect($toTables, $fromTables);
217
-		$this->convertDB($fromDB, $toDB, $intersectingTables, $input, $output);
218
-	}
219
-
220
-	protected function createSchema(Connection $toDB, InputInterface $input, OutputInterface $output) {
221
-		$output->writeln('<info>Creating schema in new database</info>');
222
-		$schemaManager = new \OC\DB\MDB2SchemaManager($toDB);
223
-		$schemaManager->createDbFromStructure(\OC::$SERVERROOT.'/db_structure.xml');
224
-		$apps = $input->getOption('all-apps') ? \OC_App::getAllApps() : \OC_App::getEnabledApps();
225
-		foreach($apps as $app) {
226
-			if (file_exists(\OC_App::getAppPath($app).'/appinfo/database.xml')) {
227
-				$schemaManager->createDbFromStructure(\OC_App::getAppPath($app).'/appinfo/database.xml');
228
-			}
229
-		}
230
-	}
231
-
232
-	protected function getToDBConnection(InputInterface $input, OutputInterface $output) {
233
-		$type = $input->getArgument('type');
234
-		$connectionParams = array(
235
-			'host' => $input->getArgument('hostname'),
236
-			'user' => $input->getArgument('username'),
237
-			'password' => $input->getOption('password'),
238
-			'dbname' => $input->getArgument('database'),
239
-			'tablePrefix' => $this->config->getSystemValue('dbtableprefix', 'oc_'),
240
-		);
241
-		if ($input->getOption('port')) {
242
-			$connectionParams['port'] = $input->getOption('port');
243
-		}
244
-		return $this->connectionFactory->getConnection($type, $connectionParams);
245
-	}
246
-
247
-	protected function clearSchema(Connection $db, InputInterface $input, OutputInterface $output) {
248
-		$toTables = $this->getTables($db);
249
-		if (!empty($toTables)) {
250
-			$output->writeln('<info>Clearing schema in new database</info>');
251
-		}
252
-		foreach($toTables as $table) {
253
-			$db->getSchemaManager()->dropTable($table);
254
-		}
255
-	}
256
-
257
-	protected function getTables(Connection $db) {
258
-		$filterExpression = '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
259
-		$db->getConfiguration()->
260
-			setFilterSchemaAssetsExpression($filterExpression);
261
-		return $db->getSchemaManager()->listTableNames();
262
-	}
263
-
264
-	protected function copyTable(Connection $fromDB, Connection $toDB, $table, InputInterface $input, OutputInterface $output) {
265
-		$chunkSize = $input->getOption('chunk-size');
266
-
267
-		$query = $fromDB->getQueryBuilder();
268
-		$query->automaticTablePrefix(false);
269
-		$query->selectAlias($query->createFunction('COUNT(*)'), 'num_entries')
270
-			->from($table);
271
-		$result = $query->execute();
272
-		$count = $result->fetchColumn();
273
-		$result->closeCursor();
274
-
275
-		$numChunks = ceil($count/$chunkSize);
276
-		if ($numChunks > 1) {
277
-			$output->writeln('chunked query, ' . $numChunks . ' chunks');
278
-		}
279
-
280
-		$progress = new ProgressBar($output, $count);
281
-		$progress->start();
282
-		$redraw = $count > $chunkSize ? 100 : ($count > 100 ? 5 : 1);
283
-		$progress->setRedrawFrequency($redraw);
284
-
285
-		$query = $fromDB->getQueryBuilder();
286
-		$query->automaticTablePrefix(false);
287
-		$query->select('*')
288
-			->from($table)
289
-			->setMaxResults($chunkSize);
290
-
291
-		$insertQuery = $toDB->getQueryBuilder();
292
-		$insertQuery->automaticTablePrefix(false);
293
-		$insertQuery->insert($table);
294
-		$parametersCreated = false;
295
-
296
-		for ($chunk = 0; $chunk < $numChunks; $chunk++) {
297
-			$query->setFirstResult($chunk * $chunkSize);
298
-
299
-			$result = $query->execute();
300
-
301
-			while ($row = $result->fetch()) {
302
-				$progress->advance();
303
-				if (!$parametersCreated) {
304
-					foreach ($row as $key => $value) {
305
-						$insertQuery->setValue($key, $insertQuery->createParameter($key));
306
-					}
307
-					$parametersCreated = true;
308
-				}
309
-
310
-				foreach ($row as $key => $value) {
311
-					$insertQuery->setParameter($key, $value, $this->getColumnType($table, $key));
312
-				}
313
-				$insertQuery->execute();
314
-			}
315
-			$result->closeCursor();
316
-		}
317
-		$progress->finish();
318
-	}
319
-
320
-	protected function getColumnType($table, $column) {
321
-		if (isset($this->columnTypes[$table][$column])) {
322
-			return $this->columnTypes[$table][$column];
323
-		}
324
-
325
-		$prefix = $this->config->getSystemValue('dbtableprefix', 'oc_');
326
-		$this->columnTypes[$table][$column] = null;
327
-		if ($table === $prefix . 'cards' && $column === 'carddata') {
328
-			$this->columnTypes[$table][$column] = IQueryBuilder::PARAM_LOB;
329
-		} else if ($column === 'calendardata') {
330
-			if ($table === $prefix . 'calendarobjects' ||
331
-				$table === $prefix . 'schedulingobjects') {
332
-				$this->columnTypes[$table][$column] = IQueryBuilder::PARAM_LOB;
333
-			}
334
-		}
335
-
336
-		return $this->columnTypes[$table][$column];
337
-	}
338
-
339
-	protected function convertDB(Connection $fromDB, Connection $toDB, array $tables, InputInterface $input, OutputInterface $output) {
340
-		$this->config->setSystemValue('maintenance', true);
341
-		try {
342
-			// copy table rows
343
-			foreach($tables as $table) {
344
-				$output->writeln($table);
345
-				$this->copyTable($fromDB, $toDB, $table, $input, $output);
346
-			}
347
-			if ($input->getArgument('type') === 'pgsql') {
348
-				$tools = new \OC\DB\PgSqlTools($this->config);
349
-				$tools->resynchronizeDatabaseSequences($toDB);
350
-			}
351
-			// save new database config
352
-			$this->saveDBInfo($input);
353
-		} catch(\Exception $e) {
354
-			$this->config->setSystemValue('maintenance', false);
355
-			throw $e;
356
-		}
357
-		$this->config->setSystemValue('maintenance', false);
358
-	}
359
-
360
-	protected function saveDBInfo(InputInterface $input) {
361
-		$type = $input->getArgument('type');
362
-		$username = $input->getArgument('username');
363
-		$dbHost = $input->getArgument('hostname');
364
-		$dbName = $input->getArgument('database');
365
-		$password = $input->getOption('password');
366
-		if ($input->getOption('port')) {
367
-			$dbHost .= ':'.$input->getOption('port');
368
-		}
369
-
370
-		$this->config->setSystemValues([
371
-			'dbtype'		=> $type,
372
-			'dbname'		=> $dbName,
373
-			'dbhost'		=> $dbHost,
374
-			'dbuser'		=> $username,
375
-			'dbpassword'	=> $password,
376
-		]);
377
-	}
378
-
379
-	/**
380
-	 * Return possible values for the named option
381
-	 *
382
-	 * @param string $optionName
383
-	 * @param CompletionContext $context
384
-	 * @return string[]
385
-	 */
386
-	public function completeOptionValues($optionName, CompletionContext $context) {
387
-		return [];
388
-	}
389
-
390
-	/**
391
-	 * Return possible values for the named argument
392
-	 *
393
-	 * @param string $argumentName
394
-	 * @param CompletionContext $context
395
-	 * @return string[]
396
-	 */
397
-	public function completeArgumentValues($argumentName, CompletionContext $context) {
398
-		if ($argumentName === 'type') {
399
-			return ['mysql', 'oci', 'pgsql'];
400
-		}
401
-		return [];
402
-	}
48
+    /**
49
+     * @var \OCP\IConfig
50
+     */
51
+    protected $config;
52
+
53
+    /**
54
+     * @var \OC\DB\ConnectionFactory
55
+     */
56
+    protected $connectionFactory;
57
+
58
+    /** @var array */
59
+    protected $columnTypes;
60
+
61
+    /**
62
+     * @param \OCP\IConfig $config
63
+     * @param \OC\DB\ConnectionFactory $connectionFactory
64
+     */
65
+    public function __construct(IConfig $config, ConnectionFactory $connectionFactory) {
66
+        $this->config = $config;
67
+        $this->connectionFactory = $connectionFactory;
68
+        parent::__construct();
69
+    }
70
+
71
+    protected function configure() {
72
+        $this
73
+            ->setName('db:convert-type')
74
+            ->setDescription('Convert the Nextcloud database to the newly configured one')
75
+            ->addArgument(
76
+                'type',
77
+                InputArgument::REQUIRED,
78
+                'the type of the database to convert to'
79
+            )
80
+            ->addArgument(
81
+                'username',
82
+                InputArgument::REQUIRED,
83
+                'the username of the database to convert to'
84
+            )
85
+            ->addArgument(
86
+                'hostname',
87
+                InputArgument::REQUIRED,
88
+                'the hostname of the database to convert to'
89
+            )
90
+            ->addArgument(
91
+                'database',
92
+                InputArgument::REQUIRED,
93
+                'the name of the database to convert to'
94
+            )
95
+            ->addOption(
96
+                'port',
97
+                null,
98
+                InputOption::VALUE_REQUIRED,
99
+                'the port of the database to convert to'
100
+            )
101
+            ->addOption(
102
+                'password',
103
+                null,
104
+                InputOption::VALUE_REQUIRED,
105
+                'the password of the database to convert to. Will be asked when not specified. Can also be passed via stdin.'
106
+            )
107
+            ->addOption(
108
+                'clear-schema',
109
+                null,
110
+                InputOption::VALUE_NONE,
111
+                'remove all tables from the destination database'
112
+            )
113
+            ->addOption(
114
+                'all-apps',
115
+                null,
116
+                InputOption::VALUE_NONE,
117
+                'whether to create schema for all apps instead of only installed apps'
118
+            )
119
+            ->addOption(
120
+                'chunk-size',
121
+                null,
122
+                InputOption::VALUE_REQUIRED,
123
+                'the maximum number of database rows to handle in a single query, bigger tables will be handled in chunks of this size. Lower this if the process runs out of memory during conversion.',
124
+                1000
125
+            )
126
+        ;
127
+    }
128
+
129
+    protected function validateInput(InputInterface $input, OutputInterface $output) {
130
+        $type = $this->connectionFactory->normalizeType($input->getArgument('type'));
131
+        if ($type === 'sqlite3') {
132
+            throw new \InvalidArgumentException(
133
+                'Converting to SQLite (sqlite3) is currently not supported.'
134
+            );
135
+        }
136
+        if ($type === $this->config->getSystemValue('dbtype', '')) {
137
+            throw new \InvalidArgumentException(sprintf(
138
+                'Can not convert from %1$s to %1$s.',
139
+                $type
140
+            ));
141
+        }
142
+        if ($type === 'oci' && $input->getOption('clear-schema')) {
143
+            // Doctrine unconditionally tries (at least in version 2.3)
144
+            // to drop sequence triggers when dropping a table, even though
145
+            // such triggers may not exist. This results in errors like
146
+            // "ORA-04080: trigger 'OC_STORAGES_AI_PK' does not exist".
147
+            throw new \InvalidArgumentException(
148
+                'The --clear-schema option is not supported when converting to Oracle (oci).'
149
+            );
150
+        }
151
+    }
152
+
153
+    protected function readPassword(InputInterface $input, OutputInterface $output) {
154
+        // Explicitly specified password
155
+        if ($input->getOption('password')) {
156
+            return;
157
+        }
158
+
159
+        // Read from stdin. stream_set_blocking is used to prevent blocking
160
+        // when nothing is passed via stdin.
161
+        stream_set_blocking(STDIN, 0);
162
+        $password = file_get_contents('php://stdin');
163
+        stream_set_blocking(STDIN, 1);
164
+        if (trim($password) !== '') {
165
+            $input->setOption('password', $password);
166
+            return;
167
+        }
168
+
169
+        // Read password by interacting
170
+        if ($input->isInteractive()) {
171
+            /** @var QuestionHelper $helper */
172
+            $helper = $this->getHelper('question');
173
+            $question = new Question('What is the database password?');
174
+            $question->setHidden(true);
175
+            $question->setHiddenFallback(false);
176
+            $password = $helper->ask($input, $output, $question);
177
+            $input->setOption('password', $password);
178
+            return;
179
+        }
180
+    }
181
+
182
+    protected function execute(InputInterface $input, OutputInterface $output) {
183
+        $this->validateInput($input, $output);
184
+        $this->readPassword($input, $output);
185
+
186
+        $fromDB = \OC::$server->getDatabaseConnection();
187
+        $toDB = $this->getToDBConnection($input, $output);
188
+
189
+        if ($input->getOption('clear-schema')) {
190
+            $this->clearSchema($toDB, $input, $output);
191
+        }
192
+
193
+        $this->createSchema($toDB, $input, $output);
194
+
195
+        $toTables = $this->getTables($toDB);
196
+        $fromTables = $this->getTables($fromDB);
197
+
198
+        // warn/fail if there are more tables in 'from' database
199
+        $extraFromTables = array_diff($fromTables, $toTables);
200
+        if (!empty($extraFromTables)) {
201
+            $output->writeln('<comment>The following tables will not be converted:</comment>');
202
+            $output->writeln($extraFromTables);
203
+            if (!$input->getOption('all-apps')) {
204
+                $output->writeln('<comment>Please note that tables belonging to available but currently not installed apps</comment>');
205
+                $output->writeln('<comment>can be included by specifying the --all-apps option.</comment>');
206
+            }
207
+
208
+            /** @var QuestionHelper $helper */
209
+            $helper = $this->getHelper('question');
210
+            $question = new ConfirmationQuestion('Continue with the conversion (y/n)? [n] ', false);
211
+
212
+            if (!$helper->ask($input, $output, $question)) {
213
+                return;
214
+            }
215
+        }
216
+        $intersectingTables = array_intersect($toTables, $fromTables);
217
+        $this->convertDB($fromDB, $toDB, $intersectingTables, $input, $output);
218
+    }
219
+
220
+    protected function createSchema(Connection $toDB, InputInterface $input, OutputInterface $output) {
221
+        $output->writeln('<info>Creating schema in new database</info>');
222
+        $schemaManager = new \OC\DB\MDB2SchemaManager($toDB);
223
+        $schemaManager->createDbFromStructure(\OC::$SERVERROOT.'/db_structure.xml');
224
+        $apps = $input->getOption('all-apps') ? \OC_App::getAllApps() : \OC_App::getEnabledApps();
225
+        foreach($apps as $app) {
226
+            if (file_exists(\OC_App::getAppPath($app).'/appinfo/database.xml')) {
227
+                $schemaManager->createDbFromStructure(\OC_App::getAppPath($app).'/appinfo/database.xml');
228
+            }
229
+        }
230
+    }
231
+
232
+    protected function getToDBConnection(InputInterface $input, OutputInterface $output) {
233
+        $type = $input->getArgument('type');
234
+        $connectionParams = array(
235
+            'host' => $input->getArgument('hostname'),
236
+            'user' => $input->getArgument('username'),
237
+            'password' => $input->getOption('password'),
238
+            'dbname' => $input->getArgument('database'),
239
+            'tablePrefix' => $this->config->getSystemValue('dbtableprefix', 'oc_'),
240
+        );
241
+        if ($input->getOption('port')) {
242
+            $connectionParams['port'] = $input->getOption('port');
243
+        }
244
+        return $this->connectionFactory->getConnection($type, $connectionParams);
245
+    }
246
+
247
+    protected function clearSchema(Connection $db, InputInterface $input, OutputInterface $output) {
248
+        $toTables = $this->getTables($db);
249
+        if (!empty($toTables)) {
250
+            $output->writeln('<info>Clearing schema in new database</info>');
251
+        }
252
+        foreach($toTables as $table) {
253
+            $db->getSchemaManager()->dropTable($table);
254
+        }
255
+    }
256
+
257
+    protected function getTables(Connection $db) {
258
+        $filterExpression = '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
259
+        $db->getConfiguration()->
260
+            setFilterSchemaAssetsExpression($filterExpression);
261
+        return $db->getSchemaManager()->listTableNames();
262
+    }
263
+
264
+    protected function copyTable(Connection $fromDB, Connection $toDB, $table, InputInterface $input, OutputInterface $output) {
265
+        $chunkSize = $input->getOption('chunk-size');
266
+
267
+        $query = $fromDB->getQueryBuilder();
268
+        $query->automaticTablePrefix(false);
269
+        $query->selectAlias($query->createFunction('COUNT(*)'), 'num_entries')
270
+            ->from($table);
271
+        $result = $query->execute();
272
+        $count = $result->fetchColumn();
273
+        $result->closeCursor();
274
+
275
+        $numChunks = ceil($count/$chunkSize);
276
+        if ($numChunks > 1) {
277
+            $output->writeln('chunked query, ' . $numChunks . ' chunks');
278
+        }
279
+
280
+        $progress = new ProgressBar($output, $count);
281
+        $progress->start();
282
+        $redraw = $count > $chunkSize ? 100 : ($count > 100 ? 5 : 1);
283
+        $progress->setRedrawFrequency($redraw);
284
+
285
+        $query = $fromDB->getQueryBuilder();
286
+        $query->automaticTablePrefix(false);
287
+        $query->select('*')
288
+            ->from($table)
289
+            ->setMaxResults($chunkSize);
290
+
291
+        $insertQuery = $toDB->getQueryBuilder();
292
+        $insertQuery->automaticTablePrefix(false);
293
+        $insertQuery->insert($table);
294
+        $parametersCreated = false;
295
+
296
+        for ($chunk = 0; $chunk < $numChunks; $chunk++) {
297
+            $query->setFirstResult($chunk * $chunkSize);
298
+
299
+            $result = $query->execute();
300
+
301
+            while ($row = $result->fetch()) {
302
+                $progress->advance();
303
+                if (!$parametersCreated) {
304
+                    foreach ($row as $key => $value) {
305
+                        $insertQuery->setValue($key, $insertQuery->createParameter($key));
306
+                    }
307
+                    $parametersCreated = true;
308
+                }
309
+
310
+                foreach ($row as $key => $value) {
311
+                    $insertQuery->setParameter($key, $value, $this->getColumnType($table, $key));
312
+                }
313
+                $insertQuery->execute();
314
+            }
315
+            $result->closeCursor();
316
+        }
317
+        $progress->finish();
318
+    }
319
+
320
+    protected function getColumnType($table, $column) {
321
+        if (isset($this->columnTypes[$table][$column])) {
322
+            return $this->columnTypes[$table][$column];
323
+        }
324
+
325
+        $prefix = $this->config->getSystemValue('dbtableprefix', 'oc_');
326
+        $this->columnTypes[$table][$column] = null;
327
+        if ($table === $prefix . 'cards' && $column === 'carddata') {
328
+            $this->columnTypes[$table][$column] = IQueryBuilder::PARAM_LOB;
329
+        } else if ($column === 'calendardata') {
330
+            if ($table === $prefix . 'calendarobjects' ||
331
+                $table === $prefix . 'schedulingobjects') {
332
+                $this->columnTypes[$table][$column] = IQueryBuilder::PARAM_LOB;
333
+            }
334
+        }
335
+
336
+        return $this->columnTypes[$table][$column];
337
+    }
338
+
339
+    protected function convertDB(Connection $fromDB, Connection $toDB, array $tables, InputInterface $input, OutputInterface $output) {
340
+        $this->config->setSystemValue('maintenance', true);
341
+        try {
342
+            // copy table rows
343
+            foreach($tables as $table) {
344
+                $output->writeln($table);
345
+                $this->copyTable($fromDB, $toDB, $table, $input, $output);
346
+            }
347
+            if ($input->getArgument('type') === 'pgsql') {
348
+                $tools = new \OC\DB\PgSqlTools($this->config);
349
+                $tools->resynchronizeDatabaseSequences($toDB);
350
+            }
351
+            // save new database config
352
+            $this->saveDBInfo($input);
353
+        } catch(\Exception $e) {
354
+            $this->config->setSystemValue('maintenance', false);
355
+            throw $e;
356
+        }
357
+        $this->config->setSystemValue('maintenance', false);
358
+    }
359
+
360
+    protected function saveDBInfo(InputInterface $input) {
361
+        $type = $input->getArgument('type');
362
+        $username = $input->getArgument('username');
363
+        $dbHost = $input->getArgument('hostname');
364
+        $dbName = $input->getArgument('database');
365
+        $password = $input->getOption('password');
366
+        if ($input->getOption('port')) {
367
+            $dbHost .= ':'.$input->getOption('port');
368
+        }
369
+
370
+        $this->config->setSystemValues([
371
+            'dbtype'		=> $type,
372
+            'dbname'		=> $dbName,
373
+            'dbhost'		=> $dbHost,
374
+            'dbuser'		=> $username,
375
+            'dbpassword'	=> $password,
376
+        ]);
377
+    }
378
+
379
+    /**
380
+     * Return possible values for the named option
381
+     *
382
+     * @param string $optionName
383
+     * @param CompletionContext $context
384
+     * @return string[]
385
+     */
386
+    public function completeOptionValues($optionName, CompletionContext $context) {
387
+        return [];
388
+    }
389
+
390
+    /**
391
+     * Return possible values for the named argument
392
+     *
393
+     * @param string $argumentName
394
+     * @param CompletionContext $context
395
+     * @return string[]
396
+     */
397
+    public function completeArgumentValues($argumentName, CompletionContext $context) {
398
+        if ($argumentName === 'type') {
399
+            return ['mysql', 'oci', 'pgsql'];
400
+        }
401
+        return [];
402
+    }
403 403
 }
Please login to merge, or discard this patch.
Spacing   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -222,7 +222,7 @@  discard block
 block discarded – undo
222 222
 		$schemaManager = new \OC\DB\MDB2SchemaManager($toDB);
223 223
 		$schemaManager->createDbFromStructure(\OC::$SERVERROOT.'/db_structure.xml');
224 224
 		$apps = $input->getOption('all-apps') ? \OC_App::getAllApps() : \OC_App::getEnabledApps();
225
-		foreach($apps as $app) {
225
+		foreach ($apps as $app) {
226 226
 			if (file_exists(\OC_App::getAppPath($app).'/appinfo/database.xml')) {
227 227
 				$schemaManager->createDbFromStructure(\OC_App::getAppPath($app).'/appinfo/database.xml');
228 228
 			}
@@ -249,13 +249,13 @@  discard block
 block discarded – undo
249 249
 		if (!empty($toTables)) {
250 250
 			$output->writeln('<info>Clearing schema in new database</info>');
251 251
 		}
252
-		foreach($toTables as $table) {
252
+		foreach ($toTables as $table) {
253 253
 			$db->getSchemaManager()->dropTable($table);
254 254
 		}
255 255
 	}
256 256
 
257 257
 	protected function getTables(Connection $db) {
258
-		$filterExpression = '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/';
258
+		$filterExpression = '/^'.preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')).'/';
259 259
 		$db->getConfiguration()->
260 260
 			setFilterSchemaAssetsExpression($filterExpression);
261 261
 		return $db->getSchemaManager()->listTableNames();
@@ -272,9 +272,9 @@  discard block
 block discarded – undo
272 272
 		$count = $result->fetchColumn();
273 273
 		$result->closeCursor();
274 274
 
275
-		$numChunks = ceil($count/$chunkSize);
275
+		$numChunks = ceil($count / $chunkSize);
276 276
 		if ($numChunks > 1) {
277
-			$output->writeln('chunked query, ' . $numChunks . ' chunks');
277
+			$output->writeln('chunked query, '.$numChunks.' chunks');
278 278
 		}
279 279
 
280 280
 		$progress = new ProgressBar($output, $count);
@@ -324,11 +324,11 @@  discard block
 block discarded – undo
324 324
 
325 325
 		$prefix = $this->config->getSystemValue('dbtableprefix', 'oc_');
326 326
 		$this->columnTypes[$table][$column] = null;
327
-		if ($table === $prefix . 'cards' && $column === 'carddata') {
327
+		if ($table === $prefix.'cards' && $column === 'carddata') {
328 328
 			$this->columnTypes[$table][$column] = IQueryBuilder::PARAM_LOB;
329 329
 		} else if ($column === 'calendardata') {
330
-			if ($table === $prefix . 'calendarobjects' ||
331
-				$table === $prefix . 'schedulingobjects') {
330
+			if ($table === $prefix.'calendarobjects' ||
331
+				$table === $prefix.'schedulingobjects') {
332 332
 				$this->columnTypes[$table][$column] = IQueryBuilder::PARAM_LOB;
333 333
 			}
334 334
 		}
@@ -340,7 +340,7 @@  discard block
 block discarded – undo
340 340
 		$this->config->setSystemValue('maintenance', true);
341 341
 		try {
342 342
 			// copy table rows
343
-			foreach($tables as $table) {
343
+			foreach ($tables as $table) {
344 344
 				$output->writeln($table);
345 345
 				$this->copyTable($fromDB, $toDB, $table, $input, $output);
346 346
 			}
@@ -350,7 +350,7 @@  discard block
 block discarded – undo
350 350
 			}
351 351
 			// save new database config
352 352
 			$this->saveDBInfo($input);
353
-		} catch(\Exception $e) {
353
+		} catch (\Exception $e) {
354 354
 			$this->config->setSystemValue('maintenance', false);
355 355
 			throw $e;
356 356
 		}
Please login to merge, or discard this patch.