summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorMorris Jobke <hey@morrisjobke.de>2017-07-05 17:32:40 +0200
committerGitHub <noreply@github.com>2017-07-05 17:32:40 +0200
commit60398b919be713ddb1813956061fee638c54317a (patch)
tree483d8f03b2a2b2b11cfebefbf0c3eb3c4b408d8f /lib
parentca565644b36930b63bb996268e848460843f0433 (diff)
parent163e8774a40bf35a650b41f1af415f89397511a5 (diff)
downloadnextcloud-server-60398b919be713ddb1813956061fee638c54317a.tar.gz
nextcloud-server-60398b919be713ddb1813956061fee638c54317a.zip
Merge pull request #5231 from nextcloud/migrations
Migrations
Diffstat (limited to 'lib')
-rw-r--r--lib/composer/composer/autoload_classmap.php11
-rw-r--r--lib/composer/composer/autoload_static.php11
-rw-r--r--lib/private/App/CodeChecker/CodeChecker.php13
-rw-r--r--lib/private/App/CodeChecker/MigrationSchemaChecker.php201
-rw-r--r--lib/private/DB/Connection.php24
-rw-r--r--lib/private/DB/MigrationService.php422
-rw-r--r--lib/private/DB/Migrator.php17
-rw-r--r--lib/private/DB/OracleConnection.php23
-rw-r--r--lib/private/DB/OracleMigrator.php56
-rw-r--r--lib/private/DB/SchemaWrapper.php139
-rw-r--r--lib/private/Installer.php9
-rw-r--r--lib/private/Migration/SimpleOutput.php84
-rw-r--r--lib/private/Setup.php2
-rw-r--r--lib/private/Setup/AbstractDatabase.php9
-rw-r--r--lib/private/Updater.php6
-rw-r--r--lib/private/legacy/app.php10
-rw-r--r--lib/public/IDBConnection.php17
-rw-r--r--lib/public/Migration/IMigrationStep.php55
-rw-r--r--lib/public/Migration/SimpleMigrationStep.php59
19 files changed, 1148 insertions, 20 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 43ed4db9307..ee1dc08021c 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -220,8 +220,10 @@ return array(
'OCP\\Lockdown\\ILockdownManager' => $baseDir . '/lib/public/Lockdown/ILockdownManager.php',
'OCP\\Mail\\IEMailTemplate' => $baseDir . '/lib/public/Mail/IEMailTemplate.php',
'OCP\\Mail\\IMailer' => $baseDir . '/lib/public/Mail/IMailer.php',
+ 'OCP\\Migration\\IMigrationStep' => $baseDir . '/lib/public/Migration/IMigrationStep.php',
'OCP\\Migration\\IOutput' => $baseDir . '/lib/public/Migration/IOutput.php',
'OCP\\Migration\\IRepairStep' => $baseDir . '/lib/public/Migration/IRepairStep.php',
+ 'OCP\\Migration\\SimpleMigrationStep' => $baseDir . '/lib/public/Migration/SimpleMigrationStep.php',
'OCP\\Notification\\IAction' => $baseDir . '/lib/public/Notification/IAction.php',
'OCP\\Notification\\IApp' => $baseDir . '/lib/public/Notification/IApp.php',
'OCP\\Notification\\IManager' => $baseDir . '/lib/public/Notification/IManager.php',
@@ -339,6 +341,7 @@ return array(
'OC\\App\\CodeChecker\\ICheck' => $baseDir . '/lib/private/App/CodeChecker/ICheck.php',
'OC\\App\\CodeChecker\\InfoChecker' => $baseDir . '/lib/private/App/CodeChecker/InfoChecker.php',
'OC\\App\\CodeChecker\\LanguageParseChecker' => $baseDir . '/lib/private/App/CodeChecker/LanguageParseChecker.php',
+ 'OC\\App\\CodeChecker\\MigrationSchemaChecker' => $baseDir . '/lib/private/App/CodeChecker/MigrationSchemaChecker.php',
'OC\\App\\CodeChecker\\NodeVisitor' => $baseDir . '/lib/private/App/CodeChecker/NodeVisitor.php',
'OC\\App\\CodeChecker\\PrivateCheck' => $baseDir . '/lib/private/App/CodeChecker/PrivateCheck.php',
'OC\\App\\CodeChecker\\StrongComparisonCheck' => $baseDir . '/lib/private/App/CodeChecker/StrongComparisonCheck.php',
@@ -420,6 +423,10 @@ return array(
'OC\\Core\\Command\\Db\\ConvertMysqlToMB4' => $baseDir . '/core/Command/Db/ConvertMysqlToMB4.php',
'OC\\Core\\Command\\Db\\ConvertType' => $baseDir . '/core/Command/Db/ConvertType.php',
'OC\\Core\\Command\\Db\\GenerateChangeScript' => $baseDir . '/core/Command/Db/GenerateChangeScript.php',
+ 'OC\\Core\\Command\\Db\\Migrations\\ExecuteCommand' => $baseDir . '/core/Command/Db/Migrations/ExecuteCommand.php',
+ 'OC\\Core\\Command\\Db\\Migrations\\GenerateCommand' => $baseDir . '/core/Command/Db/Migrations/GenerateCommand.php',
+ 'OC\\Core\\Command\\Db\\Migrations\\MigrateCommand' => $baseDir . '/core/Command/Db/Migrations/MigrateCommand.php',
+ 'OC\\Core\\Command\\Db\\Migrations\\StatusCommand' => $baseDir . '/core/Command/Db/Migrations/StatusCommand.php',
'OC\\Core\\Command\\Encryption\\ChangeKeyStorageRoot' => $baseDir . '/core/Command/Encryption/ChangeKeyStorageRoot.php',
'OC\\Core\\Command\\Encryption\\DecryptAll' => $baseDir . '/core/Command/Encryption/DecryptAll.php',
'OC\\Core\\Command\\Encryption\\Disable' => $baseDir . '/core/Command/Encryption/Disable.php',
@@ -480,6 +487,7 @@ return array(
'OC\\Core\\Controller\\TwoFactorChallengeController' => $baseDir . '/core/Controller/TwoFactorChallengeController.php',
'OC\\Core\\Controller\\UserController' => $baseDir . '/core/Controller/UserController.php',
'OC\\Core\\Middleware\\TwoFactorMiddleware' => $baseDir . '/core/Middleware/TwoFactorMiddleware.php',
+ 'OC\\Core\\Migrations\\Version13000Date20170705121758' => $baseDir . '/core/Migrations/Version13000Date20170705121758.php',
'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
'OC\\DB\\AdapterMySQL' => $baseDir . '/lib/private/DB/AdapterMySQL.php',
'OC\\DB\\AdapterOCI8' => $baseDir . '/lib/private/DB/AdapterOCI8.php',
@@ -491,6 +499,7 @@ return array(
'OC\\DB\\MDB2SchemaReader' => $baseDir . '/lib/private/DB/MDB2SchemaReader.php',
'OC\\DB\\MDB2SchemaWriter' => $baseDir . '/lib/private/DB/MDB2SchemaWriter.php',
'OC\\DB\\MigrationException' => $baseDir . '/lib/private/DB/MigrationException.php',
+ 'OC\\DB\\MigrationService' => $baseDir . '/lib/private/DB/MigrationService.php',
'OC\\DB\\Migrator' => $baseDir . '/lib/private/DB/Migrator.php',
'OC\\DB\\MySQLMigrator' => $baseDir . '/lib/private/DB/MySQLMigrator.php',
'OC\\DB\\MySqlTools' => $baseDir . '/lib/private/DB/MySqlTools.php',
@@ -517,6 +526,7 @@ return array(
'OC\\DB\\QueryBuilder\\QuoteHelper' => $baseDir . '/lib/private/DB/QueryBuilder/QuoteHelper.php',
'OC\\DB\\SQLiteMigrator' => $baseDir . '/lib/private/DB/SQLiteMigrator.php',
'OC\\DB\\SQLiteSessionInit' => $baseDir . '/lib/private/DB/SQLiteSessionInit.php',
+ 'OC\\DB\\SchemaWrapper' => $baseDir . '/lib/private/DB/SchemaWrapper.php',
'OC\\DatabaseException' => $baseDir . '/lib/private/DatabaseException.php',
'OC\\DatabaseSetupException' => $baseDir . '/lib/private/DatabaseSetupException.php',
'OC\\DateTimeFormatter' => $baseDir . '/lib/private/DateTimeFormatter.php',
@@ -682,6 +692,7 @@ return array(
'OC\\Memcache\\XCache' => $baseDir . '/lib/private/Memcache/XCache.php',
'OC\\Migration\\BackgroundRepair' => $baseDir . '/lib/private/Migration/BackgroundRepair.php',
'OC\\Migration\\ConsoleOutput' => $baseDir . '/lib/private/Migration/ConsoleOutput.php',
+ 'OC\\Migration\\SimpleOutput' => $baseDir . '/lib/private/Migration/SimpleOutput.php',
'OC\\NaturalSort' => $baseDir . '/lib/private/NaturalSort.php',
'OC\\NaturalSort_DefaultCollator' => $baseDir . '/lib/private/NaturalSort_DefaultCollator.php',
'OC\\NavigationManager' => $baseDir . '/lib/private/NavigationManager.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 00b7908f848..7b133d54a20 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -250,8 +250,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\Lockdown\\ILockdownManager' => __DIR__ . '/../../..' . '/lib/public/Lockdown/ILockdownManager.php',
'OCP\\Mail\\IEMailTemplate' => __DIR__ . '/../../..' . '/lib/public/Mail/IEMailTemplate.php',
'OCP\\Mail\\IMailer' => __DIR__ . '/../../..' . '/lib/public/Mail/IMailer.php',
+ 'OCP\\Migration\\IMigrationStep' => __DIR__ . '/../../..' . '/lib/public/Migration/IMigrationStep.php',
'OCP\\Migration\\IOutput' => __DIR__ . '/../../..' . '/lib/public/Migration/IOutput.php',
'OCP\\Migration\\IRepairStep' => __DIR__ . '/../../..' . '/lib/public/Migration/IRepairStep.php',
+ 'OCP\\Migration\\SimpleMigrationStep' => __DIR__ . '/../../..' . '/lib/public/Migration/SimpleMigrationStep.php',
'OCP\\Notification\\IAction' => __DIR__ . '/../../..' . '/lib/public/Notification/IAction.php',
'OCP\\Notification\\IApp' => __DIR__ . '/../../..' . '/lib/public/Notification/IApp.php',
'OCP\\Notification\\IManager' => __DIR__ . '/../../..' . '/lib/public/Notification/IManager.php',
@@ -369,6 +371,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\App\\CodeChecker\\ICheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/ICheck.php',
'OC\\App\\CodeChecker\\InfoChecker' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/InfoChecker.php',
'OC\\App\\CodeChecker\\LanguageParseChecker' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/LanguageParseChecker.php',
+ 'OC\\App\\CodeChecker\\MigrationSchemaChecker' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/MigrationSchemaChecker.php',
'OC\\App\\CodeChecker\\NodeVisitor' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/NodeVisitor.php',
'OC\\App\\CodeChecker\\PrivateCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/PrivateCheck.php',
'OC\\App\\CodeChecker\\StrongComparisonCheck' => __DIR__ . '/../../..' . '/lib/private/App/CodeChecker/StrongComparisonCheck.php',
@@ -450,6 +453,10 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Command\\Db\\ConvertMysqlToMB4' => __DIR__ . '/../../..' . '/core/Command/Db/ConvertMysqlToMB4.php',
'OC\\Core\\Command\\Db\\ConvertType' => __DIR__ . '/../../..' . '/core/Command/Db/ConvertType.php',
'OC\\Core\\Command\\Db\\GenerateChangeScript' => __DIR__ . '/../../..' . '/core/Command/Db/GenerateChangeScript.php',
+ 'OC\\Core\\Command\\Db\\Migrations\\ExecuteCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/ExecuteCommand.php',
+ 'OC\\Core\\Command\\Db\\Migrations\\GenerateCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/GenerateCommand.php',
+ 'OC\\Core\\Command\\Db\\Migrations\\MigrateCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/MigrateCommand.php',
+ 'OC\\Core\\Command\\Db\\Migrations\\StatusCommand' => __DIR__ . '/../../..' . '/core/Command/Db/Migrations/StatusCommand.php',
'OC\\Core\\Command\\Encryption\\ChangeKeyStorageRoot' => __DIR__ . '/../../..' . '/core/Command/Encryption/ChangeKeyStorageRoot.php',
'OC\\Core\\Command\\Encryption\\DecryptAll' => __DIR__ . '/../../..' . '/core/Command/Encryption/DecryptAll.php',
'OC\\Core\\Command\\Encryption\\Disable' => __DIR__ . '/../../..' . '/core/Command/Encryption/Disable.php',
@@ -510,6 +517,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Controller\\TwoFactorChallengeController' => __DIR__ . '/../../..' . '/core/Controller/TwoFactorChallengeController.php',
'OC\\Core\\Controller\\UserController' => __DIR__ . '/../../..' . '/core/Controller/UserController.php',
'OC\\Core\\Middleware\\TwoFactorMiddleware' => __DIR__ . '/../../..' . '/core/Middleware/TwoFactorMiddleware.php',
+ 'OC\\Core\\Migrations\\Version13000Date20170705121758' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170705121758.php',
'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
'OC\\DB\\AdapterMySQL' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterMySQL.php',
'OC\\DB\\AdapterOCI8' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterOCI8.php',
@@ -521,6 +529,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\DB\\MDB2SchemaReader' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaReader.php',
'OC\\DB\\MDB2SchemaWriter' => __DIR__ . '/../../..' . '/lib/private/DB/MDB2SchemaWriter.php',
'OC\\DB\\MigrationException' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationException.php',
+ 'OC\\DB\\MigrationService' => __DIR__ . '/../../..' . '/lib/private/DB/MigrationService.php',
'OC\\DB\\Migrator' => __DIR__ . '/../../..' . '/lib/private/DB/Migrator.php',
'OC\\DB\\MySQLMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/MySQLMigrator.php',
'OC\\DB\\MySqlTools' => __DIR__ . '/../../..' . '/lib/private/DB/MySqlTools.php',
@@ -547,6 +556,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\DB\\QueryBuilder\\QuoteHelper' => __DIR__ . '/../../..' . '/lib/private/DB/QueryBuilder/QuoteHelper.php',
'OC\\DB\\SQLiteMigrator' => __DIR__ . '/../../..' . '/lib/private/DB/SQLiteMigrator.php',
'OC\\DB\\SQLiteSessionInit' => __DIR__ . '/../../..' . '/lib/private/DB/SQLiteSessionInit.php',
+ 'OC\\DB\\SchemaWrapper' => __DIR__ . '/../../..' . '/lib/private/DB/SchemaWrapper.php',
'OC\\DatabaseException' => __DIR__ . '/../../..' . '/lib/private/DatabaseException.php',
'OC\\DatabaseSetupException' => __DIR__ . '/../../..' . '/lib/private/DatabaseSetupException.php',
'OC\\DateTimeFormatter' => __DIR__ . '/../../..' . '/lib/private/DateTimeFormatter.php',
@@ -712,6 +722,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Memcache\\XCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/XCache.php',
'OC\\Migration\\BackgroundRepair' => __DIR__ . '/../../..' . '/lib/private/Migration/BackgroundRepair.php',
'OC\\Migration\\ConsoleOutput' => __DIR__ . '/../../..' . '/lib/private/Migration/ConsoleOutput.php',
+ 'OC\\Migration\\SimpleOutput' => __DIR__ . '/../../..' . '/lib/private/Migration/SimpleOutput.php',
'OC\\NaturalSort' => __DIR__ . '/../../..' . '/lib/private/NaturalSort.php',
'OC\\NaturalSort_DefaultCollator' => __DIR__ . '/../../..' . '/lib/private/NaturalSort_DefaultCollator.php',
'OC\\NavigationManager' => __DIR__ . '/../../..' . '/lib/private/NavigationManager.php',
diff --git a/lib/private/App/CodeChecker/CodeChecker.php b/lib/private/App/CodeChecker/CodeChecker.php
index 291bedee92f..a6368ab683f 100644
--- a/lib/private/App/CodeChecker/CodeChecker.php
+++ b/lib/private/App/CodeChecker/CodeChecker.php
@@ -51,8 +51,12 @@ class CodeChecker extends BasicEmitter {
/** @var ICheck */
protected $checkList;
- public function __construct(ICheck $checkList) {
+ /** @var bool */
+ protected $checkMigrationSchema;
+
+ public function __construct(ICheck $checkList, $checkMigrationSchema) {
$this->checkList = $checkList;
+ $this->checkMigrationSchema = $checkMigrationSchema;
$this->parser = new Parser(new Lexer);
}
@@ -120,11 +124,16 @@ class CodeChecker extends BasicEmitter {
$statements = $this->parser->parse($code);
$visitor = new NodeVisitor($this->checkList);
+ $migrationVisitor = new MigrationSchemaChecker();
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
+ if ($this->checkMigrationSchema && preg_match('#^.+\\/Migration\\/Version[^\\/]{1,255}\\.php$#i', $file)) {
+ $traverser->addVisitor($migrationVisitor);
+ }
+
$traverser->traverse($statements);
- return $visitor->errors;
+ return array_merge($visitor->errors, $migrationVisitor->errors);
}
}
diff --git a/lib/private/App/CodeChecker/MigrationSchemaChecker.php b/lib/private/App/CodeChecker/MigrationSchemaChecker.php
new file mode 100644
index 00000000000..9dee358327d
--- /dev/null
+++ b/lib/private/App/CodeChecker/MigrationSchemaChecker.php
@@ -0,0 +1,201 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\App\CodeChecker;
+
+use PhpParser\Node;
+use PhpParser\Node\Name;
+use PhpParser\NodeVisitorAbstract;
+
+class MigrationSchemaChecker extends NodeVisitorAbstract {
+
+ /** @var string */
+ protected $schemaVariableName = null;
+ /** @var array */
+ protected $tableVariableNames = [];
+ /** @var array */
+ public $errors = [];
+
+ public function enterNode(Node $node) {
+ /**
+ * Check tables
+ */
+ if ($this->schemaVariableName !== null &&
+ $node instanceof Node\Expr\Assign &&
+ $node->var instanceof Node\Expr\Variable &&
+ $node->expr instanceof Node\Expr\MethodCall &&
+ $node->expr->var instanceof Node\Expr\Variable &&
+ $node->expr->var->name === $this->schemaVariableName) {
+
+ if ($node->expr->name === 'createTable') {
+ if (isset($node->expr->args[0]) && $node->expr->args[0]->value instanceof Node\Scalar\String_) {
+ if (!$this->checkNameLength($node->expr->args[0]->value->value)) {
+ $this->errors[] = [
+ 'line' => $node->getLine(),
+ 'disallowedToken' => $node->expr->args[0]->value->value,
+ 'reason' => 'Table name is too long (max. 27)',
+ ];
+ } else {
+ $this->tableVariableNames[$node->var->name] = $node->expr->args[0]->value->value;
+ }
+ }
+ } else if ($node->expr->name === 'getTable') {
+ if (isset($node->expr->args[0]) && $node->expr->args[0]->value instanceof Node\Scalar\String_) {
+ $this->tableVariableNames[$node->var->name] = $node->expr->args[0]->value->value;
+ }
+ }
+ } else if ($this->schemaVariableName !== null &&
+ $node instanceof Node\Expr\MethodCall &&
+ $node->var instanceof Node\Expr\Variable &&
+ $node->var->name === $this->schemaVariableName) {
+
+ if ($node->name === 'renameTable') {
+ $this->errors[] = [
+ 'line' => $node->getLine(),
+ 'disallowedToken' => 'Deprecated method',
+ 'reason' => sprintf(
+ '`$%s->renameTable()` must not be used',
+ $node->var->name
+ ),
+ ];
+ }
+
+ /**
+ * Check columns and Indexes
+ */
+ } else if (!empty($this->tableVariableNames) &&
+ $node instanceof Node\Expr\MethodCall &&
+ $node->var instanceof Node\Expr\Variable &&
+ isset($this->tableVariableNames[$node->var->name])) {
+
+ if ($node->name === 'addColumn' || $node->name === 'changeColumn') {
+ if (isset($node->args[0]) && $node->args[0]->value instanceof Node\Scalar\String_) {
+ if (!$this->checkNameLength($node->args[0]->value->value)) {
+ $this->errors[] = [
+ 'line' => $node->getLine(),
+ 'disallowedToken' => $node->args[0]->value->value,
+ 'reason' => sprintf(
+ 'Column name is too long on table `%s` (max. 27)',
+ $this->tableVariableNames[$node->var->name]
+ ),
+ ];
+ }
+
+ // On autoincrement the max length of the table name is 21 instead of 27
+ if (isset($node->args[2]) && $node->args[2]->value instanceof Node\Expr\Array_) {
+ /** @var Node\Expr\Array_ $options */
+ $options = $node->args[2]->value;
+ if ($this->checkColumnForAutoincrement($options)) {
+ if (!$this->checkNameLength($this->tableVariableNames[$node->var->name], true)) {
+ $this->errors[] = [
+ 'line' => $node->getLine(),
+ 'disallowedToken' => $this->tableVariableNames[$node->var->name],
+ 'reason' => 'Table name is too long because of autoincrement (max. 21)',
+ ];
+ }
+ }
+ }
+ }
+ } else if ($node->name === 'addIndex' ||
+ $node->name === 'addUniqueIndex' ||
+ $node->name === 'renameIndex' ||
+ $node->name === 'setPrimaryKey') {
+ if (isset($node->args[1]) && $node->args[1]->value instanceof Node\Scalar\String_) {
+ if (!$this->checkNameLength($node->args[1]->value->value)) {
+ $this->errors[] = [
+ 'line' => $node->getLine(),
+ 'disallowedToken' => $node->args[1]->value->value,
+ 'reason' => sprintf(
+ 'Index name is too long on table `%s` (max. 27)',
+ $this->tableVariableNames[$node->var->name]
+ ),
+ ];
+ }
+ }
+ } else if ($node->name === 'addForeignKeyConstraint') {
+ if (isset($node->args[4]) && $node->args[4]->value instanceof Node\Scalar\String_) {
+ if (!$this->checkNameLength($node->args[4]->value->value)) {
+ $this->errors[] = [
+ 'line' => $node->getLine(),
+ 'disallowedToken' => $node->args[4]->value->value,
+ 'reason' => sprintf(
+ 'Constraint name is too long on table `%s` (max. 27)',
+ $this->tableVariableNames[$node->var->name]
+ ),
+ ];
+ }
+ }
+ } else if ($node->name === 'renameColumn') {
+ $this->errors[] = [
+ 'line' => $node->getLine(),
+ 'disallowedToken' => 'Deprecated method',
+ 'reason' => sprintf(
+ '`$%s->renameColumn()` must not be used',
+ $node->var->name
+ ),
+ ];
+ }
+
+ /**
+ * Find the schema
+ */
+ } else if ($node instanceof Node\Expr\Assign &&
+ $node->expr instanceof Node\Expr\FuncCall &&
+ $node->var instanceof Node\Expr\Variable &&
+ $node->expr->name instanceof Node\Expr\Variable &&
+ $node->expr->name->name === 'schemaClosure') {
+ // E.g. $schema = $schemaClosure();
+ $this->schemaVariableName = $node->var->name;
+ }
+ }
+
+ protected function checkNameLength($tableName, $hasAutoincrement = false) {
+ if ($hasAutoincrement) {
+ return strlen($tableName) <= 21;
+ }
+ return strlen($tableName) <= 27;
+ }
+
+ /**
+ * @param Node\Expr\Array_ $optionsArray
+ * @return bool Whether the column is an autoincrement column
+ */
+ protected function checkColumnForAutoincrement(Node\Expr\Array_ $optionsArray) {
+ foreach ($optionsArray->items as $option) {
+ if ($option->key instanceof Node\Scalar\String_) {
+ if ($option->key->value === 'autoincrement' &&
+ $option->value instanceof Node\Expr\ConstFetch) {
+ /** @var Node\Expr\ConstFetch $const */
+ $const = $option->value;
+
+ if ($const->name instanceof Name &&
+ $const->name->parts === ['true']) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php
index 6b56ae8ad5c..563c077b04a 100644
--- a/lib/private/DB/Connection.php
+++ b/lib/private/DB/Connection.php
@@ -35,6 +35,7 @@ use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\DBAL\Exception\ConstraintViolationException;
+use Doctrine\DBAL\Schema\Schema;
use OC\DB\QueryBuilder\QueryBuilder;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
@@ -418,4 +419,27 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection {
}
return $this->getParams()['charset'] === 'utf8mb4';
}
+
+
+ /**
+ * Create the schema of the connected database
+ *
+ * @return Schema
+ */
+ public function createSchema() {
+ $schemaManager = new MDB2SchemaManager($this);
+ $migrator = $schemaManager->getMigrator();
+ return $migrator->createSchema();
+ }
+
+ /**
+ * Migrate the database to the given schema
+ *
+ * @param Schema $toSchema
+ */
+ public function migrateToSchema(Schema $toSchema) {
+ $schemaManager = new MDB2SchemaManager($this);
+ $migrator = $schemaManager->getMigrator();
+ $migrator->migrate($toSchema);
+ }
}
diff --git a/lib/private/DB/MigrationService.php b/lib/private/DB/MigrationService.php
new file mode 100644
index 00000000000..a4276b08c12
--- /dev/null
+++ b/lib/private/DB/MigrationService.php
@@ -0,0 +1,422 @@
+<?php
+/**
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ * @copyright Copyright (c) 2017, ownCloud GmbH
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OC\DB;
+
+use Doctrine\DBAL\Schema\Schema;
+use OC\IntegrityCheck\Helpers\AppLocator;
+use OC\Migration\SimpleOutput;
+use OCP\AppFramework\App;
+use OCP\AppFramework\QueryException;
+use OCP\IDBConnection;
+use OCP\Migration\IMigrationStep;
+use OCP\Migration\IOutput;
+use Doctrine\DBAL\Schema\Column;
+use Doctrine\DBAL\Schema\Table;
+use Doctrine\DBAL\Types\Type;
+
+class MigrationService {
+
+ /** @var boolean */
+ private $migrationTableCreated;
+ /** @var array */
+ private $migrations;
+ /** @var IOutput */
+ private $output;
+ /** @var Connection */
+ private $connection;
+ /** @var string */
+ private $appName;
+
+ /**
+ * MigrationService constructor.
+ *
+ * @param $appName
+ * @param IDBConnection $connection
+ * @param AppLocator $appLocator
+ * @param IOutput|null $output
+ * @throws \Exception
+ */
+ public function __construct($appName, IDBConnection $connection, IOutput $output = null, AppLocator $appLocator = null) {
+ $this->appName = $appName;
+ $this->connection = $connection;
+ $this->output = $output;
+ if (null === $this->output) {
+ $this->output = new SimpleOutput(\OC::$server->getLogger(), $appName);
+ }
+
+ if ($appName === 'core') {
+ $this->migrationsPath = \OC::$SERVERROOT . '/core/Migrations';
+ $this->migrationsNamespace = 'OC\\Core\\Migrations';
+ } else {
+ if (null === $appLocator) {
+ $appLocator = new AppLocator();
+ }
+ $appPath = $appLocator->getAppPath($appName);
+ $namespace = App::buildAppNamespace($appName);
+ $this->migrationsPath = "$appPath/lib/Migration";
+ $this->migrationsNamespace = $namespace . '\\Migration';
+
+ if (!@mkdir($appPath . '/lib') && !is_dir($appPath . '/lib')) {
+ throw new \RuntimeException("Could not create migration folder \"{$this->migrationsPath}\"");
+ }
+ }
+
+ if (!@mkdir($this->migrationsPath) && !is_dir($this->migrationsPath)) {
+ throw new \RuntimeException("Could not create migration folder \"{$this->migrationsPath}\"");
+ }
+ }
+
+ /**
+ * Returns the name of the app for which this migration is executed
+ *
+ * @return string
+ */
+ public function getApp() {
+ return $this->appName;
+ }
+
+ /**
+ * @return bool
+ * @codeCoverageIgnore - this will implicitly tested on installation
+ */
+ private function createMigrationTable() {
+ if ($this->migrationTableCreated) {
+ return false;
+ }
+
+ if ($this->connection->tableExists('migrations')) {
+ $this->migrationTableCreated = true;
+ return false;
+ }
+
+ $tableName = $this->connection->getPrefix() . 'migrations';
+ $tableName = $this->connection->getDatabasePlatform()->quoteIdentifier($tableName);
+
+ $columns = [
+ 'app' => new Column($this->connection->getDatabasePlatform()->quoteIdentifier('app'), Type::getType('string'), ['length' => 255]),
+ 'version' => new Column($this->connection->getDatabasePlatform()->quoteIdentifier('version'), Type::getType('string'), ['length' => 255]),
+ ];
+ $table = new Table($tableName, $columns);
+ $table->setPrimaryKey([
+ $this->connection->getDatabasePlatform()->quoteIdentifier('app'),
+ $this->connection->getDatabasePlatform()->quoteIdentifier('version')]);
+ $this->connection->getSchemaManager()->createTable($table);
+
+ $this->migrationTableCreated = true;
+
+ return true;
+ }
+
+ /**
+ * Returns all versions which have already been applied
+ *
+ * @return string[]
+ * @codeCoverageIgnore - no need to test this
+ */
+ public function getMigratedVersions() {
+ $this->createMigrationTable();
+ $qb = $this->connection->getQueryBuilder();
+
+ $qb->select('version')
+ ->from('migrations')
+ ->where($qb->expr()->eq('app', $qb->createNamedParameter($this->getApp())))
+ ->orderBy('version');
+
+ $result = $qb->execute();
+ $rows = $result->fetchAll(\PDO::FETCH_COLUMN);
+ $result->closeCursor();
+
+ return $rows;
+ }
+
+ /**
+ * Returns all versions which are available in the migration folder
+ *
+ * @return array
+ */
+ public function getAvailableVersions() {
+ $this->ensureMigrationsAreLoaded();
+ return array_map('strval', array_keys($this->migrations));
+ }
+
+ protected function findMigrations() {
+ $directory = realpath($this->migrationsPath);
+ $iterator = new \RegexIterator(
+ new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ ),
+ '#^.+\\/Version[^\\/]{1,255}\\.php$#i',
+ \RegexIterator::GET_MATCH);
+
+ $files = array_keys(iterator_to_array($iterator));
+ uasort($files, function ($a, $b) {
+ preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($a), $matchA);
+ preg_match('/^Version(\d+)Date(\d+)\\.php$/', basename($b), $matchB);
+ if (!empty($matchA) && !empty($matchB)) {
+ if ($matchA[1] !== $matchB[1]) {
+ return ($matchA[1] < $matchB[1]) ? -1 : 1;
+ }
+ return ($matchA[2] < $matchB[2]) ? -1 : 1;
+ }
+ return (basename($a) < basename($b)) ? -1 : 1;
+ });
+
+ $migrations = [];
+
+ foreach ($files as $file) {
+ $className = basename($file, '.php');
+ $version = (string) substr($className, 7);
+ if ($version === '0') {
+ throw new \InvalidArgumentException(
+ "Cannot load a migrations with the name '$version' because it is a reserved number"
+ );
+ }
+ $migrations[$version] = sprintf('%s\\%s', $this->migrationsNamespace, $className);
+ }
+
+ return $migrations;
+ }
+
+ /**
+ * @param string $to
+ * @return string[]
+ */
+ private function getMigrationsToExecute($to) {
+ $knownMigrations = $this->getMigratedVersions();
+ $availableMigrations = $this->getAvailableVersions();
+
+ $toBeExecuted = [];
+ foreach ($availableMigrations as $v) {
+ if ($to !== 'latest' && $v > $to) {
+ continue;
+ }
+ if ($this->shallBeExecuted($v, $knownMigrations)) {
+ $toBeExecuted[] = $v;
+ }
+ }
+
+ return $toBeExecuted;
+ }
+
+ /**
+ * @param string $m
+ * @param string[] $knownMigrations
+ * @return bool
+ */
+ private function shallBeExecuted($m, $knownMigrations) {
+ if (in_array($m, $knownMigrations)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $version
+ */
+ private function markAsExecuted($version) {
+ $this->connection->insertIfNotExist('*PREFIX*migrations', [
+ 'app' => $this->appName,
+ 'version' => $version
+ ]);
+ }
+
+ /**
+ * Returns the name of the table which holds the already applied versions
+ *
+ * @return string
+ */
+ public function getMigrationsTableName() {
+ return $this->connection->getPrefix() . 'migrations';
+ }
+
+ /**
+ * Returns the namespace of the version classes
+ *
+ * @return string
+ */
+ public function getMigrationsNamespace() {
+ return $this->migrationsNamespace;
+ }
+
+ /**
+ * Returns the directory which holds the versions
+ *
+ * @return string
+ */
+ public function getMigrationsDirectory() {
+ return $this->migrationsPath;
+ }
+
+ /**
+ * Return the explicit version for the aliases; current, next, prev, latest
+ *
+ * @param string $alias
+ * @return mixed|null|string
+ */
+ public function getMigration($alias) {
+ switch($alias) {
+ case 'current':
+ return $this->getCurrentVersion();
+ case 'next':
+ return $this->getRelativeVersion($this->getCurrentVersion(), 1);
+ case 'prev':
+ return $this->getRelativeVersion($this->getCurrentVersion(), -1);
+ case 'latest':
+ $this->ensureMigrationsAreLoaded();
+
+ return @end($this->getAvailableVersions());
+ }
+ return '0';
+ }
+
+ /**
+ * @param string $version
+ * @param int $delta
+ * @return null|string
+ */
+ private function getRelativeVersion($version, $delta) {
+ $this->ensureMigrationsAreLoaded();
+
+ $versions = $this->getAvailableVersions();
+ array_unshift($versions, 0);
+ $offset = array_search($version, $versions, true);
+ if ($offset === false || !isset($versions[$offset + $delta])) {
+ // Unknown version or delta out of bounds.
+ return null;
+ }
+
+ return (string) $versions[$offset + $delta];
+ }
+
+ /**
+ * @return string
+ */
+ private function getCurrentVersion() {
+ $m = $this->getMigratedVersions();
+ if (count($m) === 0) {
+ return '0';
+ }
+ return @end(array_values($m));
+ }
+
+ /**
+ * @param string $version
+ * @return string
+ * @throws \InvalidArgumentException
+ */
+ private function getClass($version) {
+ $this->ensureMigrationsAreLoaded();
+
+ if (isset($this->migrations[$version])) {
+ return $this->migrations[$version];
+ }
+
+ throw new \InvalidArgumentException("Version $version is unknown.");
+ }
+
+ /**
+ * Allows to set an IOutput implementation which is used for logging progress and messages
+ *
+ * @param IOutput $output
+ */
+ public function setOutput(IOutput $output) {
+ $this->output = $output;
+ }
+
+ /**
+ * Applies all not yet applied versions up to $to
+ *
+ * @param string $to
+ * @throws \InvalidArgumentException
+ */
+ public function migrate($to = 'latest') {
+ // read known migrations
+ $toBeExecuted = $this->getMigrationsToExecute($to);
+ foreach ($toBeExecuted as $version) {
+ $this->executeStep($version);
+ }
+ }
+
+ /**
+ * @param string $version
+ * @return mixed
+ * @throws \InvalidArgumentException
+ */
+ protected function createInstance($version) {
+ $class = $this->getClass($version);
+ try {
+ $s = \OC::$server->query($class);
+ } catch (QueryException $e) {
+ if (class_exists($class)) {
+ $s = new $class();
+ } else {
+ throw new \InvalidArgumentException("Migration step '$class' is unknown");
+ }
+ }
+
+ return $s;
+ }
+
+ /**
+ * Executes one explicit version
+ *
+ * @param string $version
+ * @throws \InvalidArgumentException
+ */
+ public function executeStep($version) {
+ $instance = $this->createInstance($version);
+ if (!$instance instanceof IMigrationStep) {
+ throw new \InvalidArgumentException('Not a valid migration');
+ }
+
+ $instance->preSchemaChange($this->output, function() {
+ return $this->connection->createSchema();
+ }, ['tablePrefix' => $this->connection->getPrefix()]);
+
+ $toSchema = $instance->changeSchema($this->output, function() {
+ return new SchemaWrapper($this->connection);
+ }, ['tablePrefix' => $this->connection->getPrefix()]);
+
+ if ($toSchema instanceof SchemaWrapper) {
+ $this->connection->migrateToSchema($toSchema->getWrappedSchema());
+ $toSchema->performDropTableCalls();
+ }
+
+ $instance->postSchemaChange($this->output, function() {
+ return $this->connection->createSchema();
+ }, ['tablePrefix' => $this->connection->getPrefix()]);
+
+ $this->markAsExecuted($version);
+ }
+
+ private function ensureMigrationsAreLoaded() {
+ if (empty($this->migrations)) {
+ $this->migrations = $this->findMigrations();
+ }
+ }
+}
diff --git a/lib/private/DB/Migrator.php b/lib/private/DB/Migrator.php
index 1d00d9a1b45..da381ba0284 100644
--- a/lib/private/DB/Migrator.php
+++ b/lib/private/DB/Migrator.php
@@ -43,14 +43,10 @@ use Symfony\Component\EventDispatcher\GenericEvent;
class Migrator {
- /**
- * @var \Doctrine\DBAL\Connection $connection
- */
+ /** @var \Doctrine\DBAL\Connection */
protected $connection;
- /**
- * @var ISecureRandom
- */
+ /** @var ISecureRandom */
private $random;
/** @var IConfig */
@@ -197,6 +193,12 @@ class Migrator {
return new Table($newName, $table->getColumns(), $newIndexes, array(), 0, $table->getOptions());
}
+ public function createSchema() {
+ $filterExpression = $this->getFilterExpression();
+ $this->connection->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression);
+ return $this->connection->getSchemaManager()->createSchema();
+ }
+
/**
* @param Schema $targetSchema
* @param \Doctrine\DBAL\Connection $connection
@@ -217,8 +219,7 @@ class Migrator {
}
$filterExpression = $this->getFilterExpression();
- $this->connection->getConfiguration()->
- setFilterSchemaAssetsExpression($filterExpression);
+ $this->connection->getConfiguration()->setFilterSchemaAssetsExpression($filterExpression);
$sourceSchema = $connection->getSchemaManager()->createSchema();
// remove tables we don't know about
diff --git a/lib/private/DB/OracleConnection.php b/lib/private/DB/OracleConnection.php
index 08d71365172..51faf21970c 100644
--- a/lib/private/DB/OracleConnection.php
+++ b/lib/private/DB/OracleConnection.php
@@ -30,9 +30,14 @@ class OracleConnection extends Connection {
* Quote the keys of the array
*/
private function quoteKeys(array $data) {
- $return = array();
+ $return = [];
+ $c = $this->getDatabasePlatform()->getIdentifierQuoteCharacter();
foreach($data as $key => $value) {
- $return[$this->quoteIdentifier($key)] = $value;
+ if ($key[0] !== $c) {
+ $return[$this->quoteIdentifier($key)] = $value;
+ } else {
+ $return[$key] = $value;
+ }
}
return $return;
}
@@ -41,7 +46,9 @@ class OracleConnection extends Connection {
* {@inheritDoc}
*/
public function insert($tableName, array $data, array $types = array()) {
- $tableName = $this->quoteIdentifier($tableName);
+ if ($tableName[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) {
+ $tableName = $this->quoteIdentifier($tableName);
+ }
$data = $this->quoteKeys($data);
return parent::insert($tableName, $data, $types);
}
@@ -50,7 +57,9 @@ class OracleConnection extends Connection {
* {@inheritDoc}
*/
public function update($tableName, array $data, array $identifier, array $types = array()) {
- $tableName = $this->quoteIdentifier($tableName);
+ if ($tableName[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) {
+ $tableName = $this->quoteIdentifier($tableName);
+ }
$data = $this->quoteKeys($data);
$identifier = $this->quoteKeys($identifier);
return parent::update($tableName, $data, $identifier, $types);
@@ -60,9 +69,11 @@ class OracleConnection extends Connection {
* {@inheritDoc}
*/
public function delete($tableExpression, array $identifier, array $types = array()) {
- $tableName = $this->quoteIdentifier($tableExpression);
+ if ($tableExpression[0] !== $this->getDatabasePlatform()->getIdentifierQuoteCharacter()) {
+ $tableExpression = $this->quoteIdentifier($tableExpression);
+ }
$identifier = $this->quoteKeys($identifier);
- return parent::delete($tableName, $identifier);
+ return parent::delete($tableExpression, $identifier);
}
/**
diff --git a/lib/private/DB/OracleMigrator.php b/lib/private/DB/OracleMigrator.php
index 908b2dedf03..2735529b5e2 100644
--- a/lib/private/DB/OracleMigrator.php
+++ b/lib/private/DB/OracleMigrator.php
@@ -24,19 +24,75 @@
namespace OC\DB;
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ColumnDiff;
+use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Schema\Table;
class OracleMigrator extends NoCheckMigrator {
/**
* @param Schema $targetSchema
* @param \Doctrine\DBAL\Connection $connection
* @return \Doctrine\DBAL\Schema\SchemaDiff
+ * @throws DBALException
*/
protected function getDiff(Schema $targetSchema, \Doctrine\DBAL\Connection $connection) {
$schemaDiff = parent::getDiff($targetSchema, $connection);
// oracle forces us to quote the identifiers
+ $schemaDiff->newTables = array_map(function(Table $table) {
+ return new Table(
+ $this->connection->quoteIdentifier($table->getName()),
+ array_map(function(Column $column) {
+ $newColumn = new Column(
+ $this->connection->quoteIdentifier($column->getName()),
+ $column->getType()
+ );
+ $newColumn->setAutoincrement($column->getAutoincrement());
+ $newColumn->setColumnDefinition($column->getColumnDefinition());
+ $newColumn->setComment($column->getComment());
+ $newColumn->setDefault($column->getDefault());
+ $newColumn->setFixed($column->getFixed());
+ $newColumn->setLength($column->getLength());
+ $newColumn->setNotnull($column->getNotnull());
+ $newColumn->setPrecision($column->getPrecision());
+ $newColumn->setScale($column->getScale());
+ $newColumn->setUnsigned($column->getUnsigned());
+ $newColumn->setPlatformOptions($column->getPlatformOptions());
+ $newColumn->setCustomSchemaOptions($column->getPlatformOptions());
+ return $newColumn;
+ }, $table->getColumns()),
+ array_map(function(Index $index) {
+ return new Index(
+ $this->connection->quoteIdentifier($index->getName()),
+ array_map(function($columnName) {
+ return $this->connection->quoteIdentifier($columnName);
+ }, $index->getColumns()),
+ $index->isUnique(),
+ $index->isPrimary(),
+ $index->getFlags(),
+ $index->getOptions()
+ );
+ }, $table->getIndexes()),
+ $table->getForeignKeys(),
+ 0,
+ $table->getOptions()
+ );
+ }, $schemaDiff->newTables);
+
+ $schemaDiff->removedTables = array_map(function(Table $table) {
+ return new Table(
+ $this->connection->quoteIdentifier($table->getName()),
+ $table->getColumns(),
+ $table->getIndexes(),
+ $table->getForeignKeys(),
+ 0,
+ $table->getOptions()
+ );
+ }, $schemaDiff->removedTables);
+
foreach ($schemaDiff->changedTables as $tableDiff) {
$tableDiff->name = $this->connection->quoteIdentifier($tableDiff->name);
foreach ($tableDiff->changedColumns as $column) {
diff --git a/lib/private/DB/SchemaWrapper.php b/lib/private/DB/SchemaWrapper.php
new file mode 100644
index 00000000000..0be45d8d3f7
--- /dev/null
+++ b/lib/private/DB/SchemaWrapper.php
@@ -0,0 +1,139 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\DB;
+
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Schema\Schema;
+use OCP\IDBConnection;
+
+class SchemaWrapper {
+
+ /** @var IDBConnection|Connection */
+ protected $connection;
+
+ /** @var Schema */
+ protected $schema;
+
+ /** @var array */
+ protected $tablesToDelete;
+
+ /**
+ * @param IDBConnection $connection
+ */
+ public function __construct(IDBConnection $connection) {
+ $this->connection = $connection;
+ $this->schema = $this->connection->createSchema();
+ }
+
+ public function getWrappedSchema() {
+ return $this->schema;
+ }
+
+ public function performDropTableCalls() {
+ foreach ($this->tablesToDelete as $tableName => $true) {
+ $this->connection->dropTable($tableName);
+ unset($this->tablesToDelete[$tableName]);
+ }
+ }
+
+ /**
+ * Gets all table names
+ *
+ * @return array
+ */
+ public function getTableNamesWithoutPrefix() {
+ $tableNames = $this->schema->getTableNames();
+ return array_map(function($tableName) {
+ if (strpos($tableName, $this->connection->getPrefix()) === 0) {
+ return substr($tableName, strlen($this->connection->getPrefix()));
+ }
+
+ return $tableName;
+ }, $tableNames);
+ }
+
+ // Overwritten methods
+
+ /**
+ * @param string $tableName
+ *
+ * @return \Doctrine\DBAL\Schema\Table
+ * @throws \Doctrine\DBAL\Schema\SchemaException
+ */
+ public function getTable($tableName) {
+ return $this->schema->getTable($this->connection->getPrefix() . $tableName);
+ }
+
+ /**
+ * Does this schema have a table with the given name?
+ *
+ * @param string $tableName
+ *
+ * @return boolean
+ */
+ public function hasTable($tableName) {
+ return $this->schema->hasTable($this->connection->getPrefix() . $tableName);
+ }
+
+ /**
+ * Creates a new table.
+ *
+ * @param string $tableName
+ * @return \Doctrine\DBAL\Schema\Table
+ */
+ public function createTable($tableName) {
+ return $this->schema->createTable($this->connection->getPrefix() . $tableName);
+ }
+
+ /**
+ * Renames a table.
+ *
+ * @param string $oldTableName
+ * @param string $newTableName
+ *
+ * @return \Doctrine\DBAL\Schema\Schema
+ * @throws DBALException
+ */
+ public function renameTable($oldTableName, $newTableName) {
+ throw new DBALException('Renaming tables is not supported. Please create and drop the tables manually.');
+ }
+
+ /**
+ * Drops a table from the schema.
+ *
+ * @param string $tableName
+ * @return \Doctrine\DBAL\Schema\Schema
+ */
+ public function dropTable($tableName) {
+ $this->tablesToDelete[$tableName] = true;
+ return $this->schema->dropTable($this->connection->getPrefix() . $tableName);
+ }
+
+ /**
+ * @param string $name
+ * @param array $arguments
+ * @return mixed
+ */
+ public function __call($name, $arguments) {
+ return call_user_func_array([$this->schema, $name], $arguments);
+ }
+}
diff --git a/lib/private/Installer.php b/lib/private/Installer.php
index 35f51b19b07..60b0336fb5a 100644
--- a/lib/private/Installer.php
+++ b/lib/private/Installer.php
@@ -139,6 +139,9 @@ class Installer {
} else {
OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
}
+ } else {
+ $ms = new \OC\DB\MigrationService($info['id'], \OC::$server->getDatabaseConnection());
+ $ms->migrate();
}
\OC_App::registerAutoloading($appId, $basedir);
@@ -530,6 +533,8 @@ class Installer {
public static function installShippedApp($app) {
//install the database
$appPath = OC_App::getAppPath($app);
+ \OC_App::registerAutoloading($app, $appPath);
+
if(is_file("$appPath/appinfo/database.xml")) {
try {
OC_DB::createDbFromStructure("$appPath/appinfo/database.xml");
@@ -540,10 +545,12 @@ class Installer {
0, $e
);
}
+ } else {
+ $ms = new \OC\DB\MigrationService($app, \OC::$server->getDatabaseConnection());
+ $ms->migrate();
}
//run appinfo/install.php
- \OC_App::registerAutoloading($app, $appPath);
self::includeAppScript("$appPath/appinfo/install.php");
$info = OC_App::getAppInfo($app);
diff --git a/lib/private/Migration/SimpleOutput.php b/lib/private/Migration/SimpleOutput.php
new file mode 100644
index 00000000000..b28fcbd7628
--- /dev/null
+++ b/lib/private/Migration/SimpleOutput.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * @author Thomas Müller <thomas.mueller@tmit.eu>
+ *
+ * @copyright Copyright (c) 2017, ownCloud GmbH
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\Migration;
+
+
+use OCP\ILogger;
+use OCP\Migration\IOutput;
+
+/**
+ * Class SimpleOutput
+ *
+ * Just a simple IOutput implementation with writes messages to the log file.
+ * Alternative implementations will write to the console or to the web ui (web update case)
+ *
+ * @package OC\Migration
+ */
+class SimpleOutput implements IOutput {
+
+ /** @var ILogger */
+ private $logger;
+ private $appName;
+
+ public function __construct(ILogger $logger, $appName) {
+ $this->logger = $logger;
+ $this->appName = $appName;
+ }
+
+ /**
+ * @param string $message
+ * @since 9.1.0
+ */
+ public function info($message) {
+ $this->logger->info($message, ['app' => $this->appName]);
+ }
+
+ /**
+ * @param string $message
+ * @since 9.1.0
+ */
+ public function warning($message) {
+ $this->logger->warning($message, ['app' => $this->appName]);
+ }
+
+ /**
+ * @param int $max
+ * @since 9.1.0
+ */
+ public function startProgress($max = 0) {
+ }
+
+ /**
+ * @param int $step
+ * @param string $description
+ * @since 9.1.0
+ */
+ public function advance($step = 1, $description = '') {
+ }
+
+ /**
+ * @since 9.1.0
+ */
+ public function finishProgress() {
+ }
+}
diff --git a/lib/private/Setup.php b/lib/private/Setup.php
index b8a861fd296..5cd3c84ce92 100644
--- a/lib/private/Setup.php
+++ b/lib/private/Setup.php
@@ -332,6 +332,8 @@ class Setup {
try {
$dbSetup->initialize($options);
$dbSetup->setupDatabase($username);
+ // apply necessary migrations
+ $dbSetup->runMigrations();
} catch (\OC\DatabaseSetupException $e) {
$error[] = array(
'error' => $e->getMessage(),
diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php
index d5c34291e60..2fbec326a5d 100644
--- a/lib/private/Setup/AbstractDatabase.php
+++ b/lib/private/Setup/AbstractDatabase.php
@@ -27,6 +27,7 @@
namespace OC\Setup;
use OC\DB\ConnectionFactory;
+use OC\DB\MigrationService;
use OC\SystemConfig;
use OCP\IL10N;
use OCP\ILogger;
@@ -143,4 +144,12 @@ abstract class AbstractDatabase {
* @param string $userName
*/
abstract public function setupDatabase($userName);
+
+ public function runMigrations() {
+ if (!is_dir(\OC::$SERVERROOT."/core/Migrations")) {
+ return;
+ }
+ $ms = new MigrationService('core', \OC::$server->getDatabaseConnection());
+ $ms->migrate();
+ }
}
diff --git a/lib/private/Updater.php b/lib/private/Updater.php
index 6d08e5d4cc0..6c23a43c354 100644
--- a/lib/private/Updater.php
+++ b/lib/private/Updater.php
@@ -32,6 +32,7 @@
namespace OC;
+use OC\DB\MigrationService;
use OC\Hooks\BasicEmitter;
use OC\IntegrityCheck\Checker;
use OC_App;
@@ -300,8 +301,9 @@ class Updater extends BasicEmitter {
protected function doCoreUpgrade() {
$this->emit('\OC\Updater', 'dbUpgradeBefore');
- // do the real upgrade
- \OC_DB::updateDbFromStructure(\OC::$SERVERROOT . '/db_structure.xml');
+ // execute core migrations
+ $ms = new MigrationService('core', \OC::$server->getDatabaseConnection());
+ $ms->migrate();
$this->emit('\OC\Updater', 'dbUpgrade');
}
diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php
index 1bdbd1e2a83..463e13da915 100644
--- a/lib/private/legacy/app.php
+++ b/lib/private/legacy/app.php
@@ -50,6 +50,7 @@
use OC\App\DependencyAnalyzer;
use OC\App\InfoParser;
use OC\App\Platform;
+use OC\DB\MigrationService;
use OC\Installer;
use OC\Repair;
use OCP\App\ManagerEvent;
@@ -1041,20 +1042,27 @@ class OC_App {
if($appPath === false) {
return false;
}
+ self::registerAutoloading($appId, $appPath);
+
$appData = self::getAppInfo($appId);
self::executeRepairSteps($appId, $appData['repair-steps']['pre-migration']);
+
if (file_exists($appPath . '/appinfo/database.xml')) {
OC_DB::updateDbFromStructure($appPath . '/appinfo/database.xml');
+ } else {
+ $ms = new MigrationService($appId, \OC::$server->getDatabaseConnection());
+ $ms->migrate();
}
+
self::executeRepairSteps($appId, $appData['repair-steps']['post-migration']);
self::setupLiveMigrations($appId, $appData['repair-steps']['live-migration']);
unset(self::$appVersion[$appId]);
+
// run upgrade code
if (file_exists($appPath . '/appinfo/update.php')) {
self::loadApp($appId);
include $appPath . '/appinfo/update.php';
}
- self::registerAutoloading($appId, $appPath);
self::setupBackgroundJobs($appData['background-jobs']);
if(isset($appData['settings']) && is_array($appData['settings'])) {
\OC::$server->getSettingsManager()->setupSettings($appData['settings']);
diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php
index efd65d55f7e..56cf50c5fb3 100644
--- a/lib/public/IDBConnection.php
+++ b/lib/public/IDBConnection.php
@@ -34,6 +34,7 @@
// use OCP namespace for all classes that are considered public.
// This means that they should be used by apps instead of the internal ownCloud classes
namespace OCP;
+use Doctrine\DBAL\Schema\Schema;
use OCP\DB\QueryBuilder\IQueryBuilder;
/**
@@ -259,4 +260,20 @@ interface IDBConnection {
* @since 11.0.0
*/
public function supports4ByteText();
+
+ /**
+ * Create the schema of the connected database
+ *
+ * @return Schema
+ * @since 13.0.0
+ */
+ public function createSchema();
+
+ /**
+ * Migrate the database to the given schema
+ *
+ * @param Schema $toSchema
+ * @since 13.0.0
+ */
+ public function migrateToSchema(Schema $toSchema);
}
diff --git a/lib/public/Migration/IMigrationStep.php b/lib/public/Migration/IMigrationStep.php
new file mode 100644
index 00000000000..49bb236ab7b
--- /dev/null
+++ b/lib/public/Migration/IMigrationStep.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\Migration;
+
+use Doctrine\DBAL\Schema\Schema;
+
+/**
+ * @since 13.0.0
+ */
+interface IMigrationStep {
+
+ /**
+ * @param IOutput $output
+ * @param \Closure $schemaClosure The `\Closure` returns a `Schema`
+ * @param array $options
+ * @since 13.0.0
+ */
+ public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options);
+
+ /**
+ * @param IOutput $output
+ * @param \Closure $schemaClosure The `\Closure` returns a `Schema`
+ * @param array $options
+ * @return null|Schema
+ * @since 13.0.0
+ */
+ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options);
+
+ /**
+ * @param IOutput $output
+ * @param \Closure $schemaClosure The `\Closure` returns a `Schema`
+ * @param array $options
+ * @since 13.0.0
+ */
+ public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options);
+}
diff --git a/lib/public/Migration/SimpleMigrationStep.php b/lib/public/Migration/SimpleMigrationStep.php
new file mode 100644
index 00000000000..df4ae4e2eb6
--- /dev/null
+++ b/lib/public/Migration/SimpleMigrationStep.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\Migration;
+
+use Doctrine\DBAL\Schema\Schema;
+
+/**
+ * @since 13.0.0
+ */
+abstract class SimpleMigrationStep implements IMigrationStep {
+
+ /**
+ * @param IOutput $output
+ * @param \Closure $schemaClosure The `\Closure` returns a `Schema`
+ * @param array $options
+ * @since 13.0.0
+ */
+ public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
+ }
+
+ /**
+ * @param IOutput $output
+ * @param \Closure $schemaClosure The `\Closure` returns a `Schema`
+ * @param array $options
+ * @return null|Schema
+ * @since 13.0.0
+ */
+ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
+ return null;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param \Closure $schemaClosure The `\Closure` returns a `Schema`
+ * @param array $options
+ * @since 13.0.0
+ */
+ public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
+ }
+}