diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -45,6 +45,7 @@ 'AphrontPageView' => 'view/page/base', 'AphrontPagerView' => 'view/control/pager', 'AphrontPanelView' => 'view/layout/panel', + 'AphrontQueryAccessDeniedException' => 'storage/exception/accessdenied', 'AphrontQueryConnectionException' => 'storage/exception/connection', 'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost', 'AphrontQueryCountException' => 'storage/exception/count', @@ -509,6 +510,7 @@ 'AphrontPageView' => 'AphrontView', 'AphrontPagerView' => 'AphrontView', 'AphrontPanelView' => 'AphrontView', + 'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', diff --git a/scripts/sql/upgrade_schema.php b/scripts/sql/upgrade_schema.php --- a/scripts/sql/upgrade_schema.php +++ b/scripts/sql/upgrade_schema.php @@ -25,14 +25,14 @@ define('SCHEMA_VERSION_TABLE_NAME', 'schema_version'); -if (isset($argv[1]) && !is_numeric($argv[1])) { - print - "USAGE: ./update_schema.php [first_patch_version]\n\n". - "run './update_schema.php 12' to apply all patches starting from ". - "version 12.\n". - "run './update_schema.php' to apply all patches that are new since\n". - "the last time this script was run\n\n"; - exit(0); +$options = getopt('v:u:p:') + array( + 'v' => null, + 'u' => null, + 'p' => null, +); + +if ($options['v'] && !is_numeric($options['v'])) { + usage(); } echo phutil_console_wrap( @@ -45,113 +45,141 @@ } // Use always the version from the commandline if it is defined -$next_version = isset($argv[1]) ? (int)$argv[1] : null; +$next_version = isset($options['v']) ? (int)$options['v'] : null; -// Dummy class needed for creating our database -class DummyUser extends PhabricatorLiskDAO { - public function getApplicationName() { - return 'user'; - } +if ($options['u']) { + $conn_user = $options['u']; + $conn_pass = $options['p']; +} else { + $conn_user = PhabricatorEnv::getEnvConfig('mysql.user'); + $conn_pass = PhabricatorEnv::getEnvConfig('mysql.pass'); } +$conn_host = PhabricatorEnv::getEnvConfig('mysql.host'); -// Class needed for setting up the actual SQL connection -class PhabricatorSchemaVersion extends PhabricatorLiskDAO { - public function getApplicationName() { - return 'meta_data'; - } -} +$conn = new AphrontMySQLDatabaseConnection( + array( + 'user' => $conn_user, + 'pass' => $conn_pass, + 'host' => $conn_host, + 'database' => null, + )); -// Connect to 'phabricator_user' db first to create our db -$conn = id(new DummyUser())->establishConnection('w'); -$create_sql = <<establishConnection('w'); -$create_sql = <<withSuffix('sql'); + $results = $finder->find(); -// Find the patch files -$patches_dir = $root.'/resources/sql/patches/'; -$finder = id(new FileFinder($patches_dir)) - ->withSuffix('sql'); -$results = $finder->find(); - -$patches = array(); -foreach ($results as $r) { - $matches = array(); - if (preg_match('/(\d+)\..*\.sql$/', $r, $matches)) { - $patches[] = array('version' => (int)$matches[1], - 'file' => $r); - } else { - print - "*** WARNING : File {$r} does not follow the normal naming ". - "convention. ***\n"; - } -} - -// Files are in some 'random' order returned by the operating system -// We need to apply them in proper order -$patches = isort($patches, 'version'); - -$patch_applied = false; -foreach ($patches as $patch) { - if ($patch['version'] < $next_version) { - continue; + $patches = array(); + foreach ($results as $r) { + $matches = array(); + if (preg_match('/(\d+)\..*\.sql$/', $r, $matches)) { + $patches[] = array('version' => (int)$matches[1], + 'file' => $r); + } else { + print + "*** WARNING : File {$r} does not follow the normal naming ". + "convention. ***\n"; + } } - print "Applying patch {$patch['file']}\n"; + // Files are in some 'random' order returned by the operating system + // We need to apply them in proper order + $patches = isort($patches, 'version'); - $path = Filesystem::resolvePath($patches_dir.$patch['file']); + $patch_applied = false; + foreach ($patches as $patch) { + if ($patch['version'] < $next_version) { + continue; + } - $user = PhabricatorEnv::getEnvConfig('mysql.user'); - $pass = PhabricatorEnv::getEnvConfig('mysql.pass'); - $host = PhabricatorEnv::getEnvConfig('mysql.host'); + print "Applying patch {$patch['file']}\n"; - list($stdout, $stderr) = execx( - "mysql --user=%s --password=%s --host=%s < %s", - $user, $pass, $host, $path); + $path = Filesystem::resolvePath($patches_dir.$patch['file']); - if ($stderr) { - print $stderr; - exit(-1); + list($stdout, $stderr) = execx( + "mysql --user=%s --password=%s --host=%s < %s", + $conn_user, + $conn_pass, + $conn_host, + $path); + + if ($stderr) { + print $stderr; + exit(-1); + } + + // Patch was successful, update the db with the latest applied patch version + // 'DELETE' and 'INSERT' instead of update, because the table might be empty + queryfx( + $conn, + 'DELETE FROM phabricator_meta_data.%T', + SCHEMA_VERSION_TABLE_NAME); + queryfx( + $conn, + 'INSERT INTO phabricator_meta_data.%T VALUES (%d)', + SCHEMA_VERSION_TABLE_NAME, + $patch['version']); + + $patch_applied = true; } - // Patch was successful, update the db with the latest applied patch version - // 'DELETE' and 'INSERT' instead of update, because the table might be empty - queryfx($conn, 'DELETE FROM %T', SCHEMA_VERSION_TABLE_NAME); - queryfx( - $conn, - 'INSERT INTO %T values (%d)', - SCHEMA_VERSION_TABLE_NAME, - $patch['version']); + if (!$patch_applied) { + print "Your database is already up-to-date.\n"; + } - $patch_applied = true; +} catch (AphrontQueryAccessDeniedException $ex) { + echo + "ACCESS DENIED\n". + "The user '{$conn_user}' does not have sufficient MySQL privileges to\n". + "execute the schema upgrade. Use the -u and -p flags to run as a user\n". + "with more privileges (e.g., root).". + "\n\n". + "EXCEPTION:\n". + $ex->getMessage(). + "\n\n"; + exit(1); } -if (!$patch_applied) { - print "Your database is already up-to-date.\n"; +function usage() { + echo + "usage: upgrade_schema.php [-v version] [-u user -p pass]". + "\n\n". + "Run 'upgrade_schema.php -v 12' to apply all patches starting from ". + "version 12.\n". + "Run 'upgrade_schema.php -u root -p hunter2' to override the configured ". + "default user.\n"; + exit(1); } + diff --git a/src/docs/configuration_guide.diviner b/src/docs/configuration_guide.diviner --- a/src/docs/configuration_guide.diviner +++ b/src/docs/configuration_guide.diviner @@ -119,4 +119,5 @@ = Upgrading Schema = After you have configured Phabricator, you need to upgrade the database -schema, see @{article:Upgrading Schema} +schema, see @{article:Upgrading Schema}. You'll also need to do this after you +update the code in the future. diff --git a/src/docs/upgrade_schema.diviner b/src/docs/upgrade_schema.diviner --- a/src/docs/upgrade_schema.diviner +++ b/src/docs/upgrade_schema.diviner @@ -14,19 +14,25 @@ If you are doing this for the first time to a freshly installed MySQL database, run the following command: - PHABRICATOR_ENV= php path/to/phabricator/scripts/sql/upgrade_schema.php 0 + PHABRICATOR_ENV= path/to/phabricator/scripts/sql/upgrade_schema.php -v 0 This will install all the patches starting from 0. Running this script will store the information of the latest installed patch in the Phabricator database. Next time you want to upgrade your schema, just run: - PHABRICATOR_ENV= php path/to/phabricator/scripts/sql/upgrade_schema.php + PHABRICATOR_ENV= path/to/phabricator/scripts/sql/upgrade_schema.php This will install all the patches that are new since the last time you ran this script. +If your configuration uses an unprivileged user to connect to the database, you +may have to override the default user so the schema changes can be applied with +root or some other admin user: + + PHABRICATOR_ENV= path/to/phabricator/scripts/sql/upgrade_schema.php -u -p + If you need to upgrade the schema starting from a specific patch, just run: - PHABRICATOR_ENV= php path/to/phabricator/scripts/sql/upgrade_schema.php + PHABRICATOR_ENV= path/to/phabricator/scripts/sql/upgrade_schema.php -v However, this isn't usually needed. diff --git a/resources/sql/patches/033.privtest.sql b/resources/sql/patches/033.privtest.sql new file mode 100644 --- /dev/null +++ b/resources/sql/patches/033.privtest.sql @@ -0,0 +1,2 @@ +# This is a no-op, just testing the upgrade_schema.php script. +SELECT 1; diff --git a/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php b/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php --- a/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php +++ b/src/storage/connection/mysql/AphrontMySQLDatabaseConnection.php @@ -144,9 +144,11 @@ "{$error}."); } - $ret = @mysql_select_db($database, $conn); - if (!$ret) { - $this->throwQueryException($conn); + if ($database !== null) { + $ret = @mysql_select_db($database, $conn); + if (!$ret) { + $this->throwQueryException($conn); + } } $end = microtime(true); @@ -249,6 +251,11 @@ // portable to parse the key out of the error and attach it to the // exception. throw new AphrontQueryDuplicateKeyException("{$errno}: {$error}"); + case 1044: // Access denied to database + case 1045: // Access denied (auth) + case 1142: // Access denied to table + case 1143: // Access denied to column + throw new AphrontQueryAccessDeniedException("#{$errno}: {$error}"); default: // TODO: 1064 is syntax error, and quite terrible in production. throw new AphrontQueryException("#{$errno}: {$error}"); diff --git a/src/storage/connection/mysql/__init__.php b/src/storage/connection/mysql/__init__.php --- a/src/storage/connection/mysql/__init__.php +++ b/src/storage/connection/mysql/__init__.php @@ -8,6 +8,7 @@ phutil_require_module('phabricator', 'aphront/console/plugin/services/api'); phutil_require_module('phabricator', 'storage/connection/base'); +phutil_require_module('phabricator', 'storage/exception/accessdenied'); phutil_require_module('phabricator', 'storage/exception/base'); phutil_require_module('phabricator', 'storage/exception/connection'); phutil_require_module('phabricator', 'storage/exception/connectionlost'); diff --git a/src/storage/exception/accessdenied/AphrontQueryAccessDeniedException.php b/src/storage/exception/accessdenied/AphrontQueryAccessDeniedException.php new file mode 100644 --- /dev/null +++ b/src/storage/exception/accessdenied/AphrontQueryAccessDeniedException.php @@ -0,0 +1,23 @@ +