From 2fdaabd50eda549130eb7d6d22478d175e4aefc0 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 16 Dec 2009 20:30:00 -0800 Subject: [PATCH] init: Create database indexes during schema creation We now automatically create the database indexes during schema creation by parsing our own index creation script(s) and doing their raw SQL commands one at a time on the underlying database. To simplify setup we now only perform this work inside of init, and no longer do it implicitly when the WAR is loaded inside of a servlet container. Bug: issue 343 Change-Id: I0b37d7e75d75402a3854bb3751c642dec7897940 Signed-off-by: Shawn O. Pearce --- .../main/java/com/google/gerrit/pgm/Init.java | 25 +- .../google/gerrit/reviewdb}/index_generic.sql | 0 .../gerrit/reviewdb}/index_postgres.sql | 4 + .../google/gerrit/reviewdb}/mysql_nextval.sql | 0 .../server/config/AuthConfigModule.java | 2 + .../gerrit/server/config/DatabaseModule.java | 4 - .../server/config/SystemConfigProvider.java | 272 +--------------- .../config/WildProjectNameProvider.java | 4 +- .../gerrit/server/schema/SchemaCreator.java | 300 ++++++++++++++++++ .../gerrit/server/schema/SchemaUpdater.java | 79 +++++ .../gerrit/server/schema/ScriptRunner.java | 123 +++++++ .../SchemaCreatorTest.java} | 16 +- .../google/gerrit/testutil/TestDatabase.java | 16 +- 13 files changed, 547 insertions(+), 298 deletions(-) rename {gerrit-war/src/main/webapp/WEB-INF/sql => gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb}/index_generic.sql (100%) rename {gerrit-war/src/main/webapp/WEB-INF/sql => gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb}/index_postgres.sql (99%) rename {gerrit-war/src/main/webapp/WEB-INF/sql => gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb}/mysql_nextval.sql (100%) create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java rename gerrit-server/src/test/java/com/google/gerrit/server/{config/SystemConfigProviderTest.java => schema/SchemaCreatorTest.java} (96%) diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java index 4740694a05..64a4f68438 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java @@ -28,11 +28,11 @@ import com.google.gerrit.pgm.util.SiteProgram; import com.google.gerrit.reviewdb.AuthType; import com.google.gerrit.reviewdb.Project; import com.google.gerrit.reviewdb.ReviewDb; -import com.google.gerrit.reviewdb.SystemConfig; import com.google.gerrit.reviewdb.Project.SubmitType; import com.google.gerrit.server.config.ConfigUtil; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.mail.SmtpEmailSender.Encryption; +import com.google.gerrit.server.schema.SchemaUpdater; import com.google.gwtjsonrpc.server.SignedToken; import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.SchemaFactory; @@ -82,6 +82,9 @@ public class Init extends SiteProgram { @Option(name = "--no-auto-start", usage = "Don't automatically start daemon after init") private boolean noAutoStart; + @Inject + private SchemaUpdater schemaUpdater; + @Inject private GitRepositoryManager repositoryManager; @@ -137,7 +140,7 @@ public class Init extends SiteProgram { deleteOnFailure = false; inject(); - updateSystemConfig(); + schemaUpdater.update(); initGit(); } catch (Exception failure) { if (deleteOnFailure) { @@ -268,24 +271,6 @@ public class Init extends SiteProgram { chmod(0755, gerrit_sh); } - private void updateSystemConfig() throws OrmException { - final ReviewDb db = schema.open(); - try { - final SystemConfig sc = db.systemConfig().get(new SystemConfig.Key()); - if (sc == null) { - throw die("No record in system_config table"); - } - try { - sc.sitePath = getSitePath().getCanonicalPath(); - } catch (IOException e) { - sc.sitePath = getSitePath().getAbsolutePath(); - } - db.systemConfig().update(Collections.singleton(sc)); - } finally { - db.close(); - } - } - private void initGit() throws OrmException, IOException { final File root = repositoryManager.getBasePath(); if (root != null && importProjects) { diff --git a/gerrit-war/src/main/webapp/WEB-INF/sql/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql similarity index 100% rename from gerrit-war/src/main/webapp/WEB-INF/sql/index_generic.sql rename to gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql diff --git a/gerrit-war/src/main/webapp/WEB-INF/sql/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql similarity index 99% rename from gerrit-war/src/main/webapp/WEB-INF/sql/index_postgres.sql rename to gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql index b007a3b246..455d96018d 100644 --- a/gerrit-war/src/main/webapp/WEB-INF/sql/index_postgres.sql +++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql @@ -17,6 +17,8 @@ CLUSTER; -- CREATE LANGUAGE plpgsql; +delimiter // + CREATE OR REPLACE FUNCTION check_schema_version (exp INT) RETURNS VARCHAR(255) @@ -34,7 +36,9 @@ BEGIN RETURN 'OK'; END; $$ LANGUAGE plpgsql; +// +delimiter ; -- Indexes to support @Query -- diff --git a/gerrit-war/src/main/webapp/WEB-INF/sql/mysql_nextval.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/mysql_nextval.sql similarity index 100% rename from gerrit-war/src/main/webapp/WEB-INF/sql/mysql_nextval.sql rename to gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/mysql_nextval.sql diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfigModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfigModule.java index e5d4ba8c31..33ea3fb663 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfigModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfigModule.java @@ -16,12 +16,14 @@ package com.google.gerrit.server.config; import static com.google.inject.Scopes.SINGLETON; +import com.google.gerrit.reviewdb.SystemConfig; import com.google.inject.AbstractModule; /** Creates {@link AuthConfig} from {@link GerritServerConfig}. */ public class AuthConfigModule extends AbstractModule { @Override protected void configure() { + bind(SystemConfig.class).toProvider(SystemConfigProvider.class).in(SINGLETON); bind(AuthConfig.class).in(SINGLETON); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/DatabaseModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/DatabaseModule.java index 21ab1b8283..71142b9191 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/DatabaseModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/DatabaseModule.java @@ -17,7 +17,6 @@ package com.google.gerrit.server.config; import static com.google.inject.Scopes.SINGLETON; import com.google.gerrit.reviewdb.ReviewDb; -import com.google.gerrit.reviewdb.SystemConfig; import com.google.gwtorm.client.SchemaFactory; import com.google.gwtorm.jdbc.Database; import com.google.inject.TypeLiteral; @@ -30,8 +29,5 @@ public class DatabaseModule extends FactoryModule { new TypeLiteral>() {}).in(SINGLETON); bind(new TypeLiteral>() {}).toProvider( ReviewDbDatabaseProvider.class).in(SINGLETON); - - bind(SystemConfig.class).toProvider(SystemConfigProvider.class).in( - SINGLETON); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java index 5621b4fb09..d3348fab94 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SystemConfigProvider.java @@ -14,33 +14,19 @@ package com.google.gerrit.server.config; -import com.google.gerrit.reviewdb.AccountGroup; -import com.google.gerrit.reviewdb.ApprovalCategory; -import com.google.gerrit.reviewdb.ApprovalCategoryValue; -import com.google.gerrit.reviewdb.Project; -import com.google.gerrit.reviewdb.ProjectRight; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.SchemaVersion; import com.google.gerrit.reviewdb.SystemConfig; -import com.google.gerrit.server.workflow.NoOpFunction; -import com.google.gerrit.server.workflow.SubmitFunction; -import com.google.gwtjsonrpc.server.SignedToken; import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.SchemaFactory; -import com.google.gwtorm.client.Transaction; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.ProvisionException; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** Loads the {@link SystemConfig} from the database. */ public class SystemConfigProvider implements Provider { - private static final Project.NameKey DEFAULT_WILD_NAME = - new Project.NameKey("-- All Projects --"); private final SchemaFactory schema; @Inject @@ -56,30 +42,12 @@ public class SystemConfigProvider implements Provider { SchemaVersion sVer = getSchemaVersion(db); if (sVer == null) { - // Assume the schema is empty and try to populate it. - // - sVer = createSchema(db); + throw new OrmException("Schema not yet initialized." + + " Run init to initialize the schema."); } - - switch (sVer.versionNbr) { - case 2: - initPushTagCategory(db); - initPushUpdateBranchCategory(db); - - sVer.versionNbr = 3; - db.schemaVersion().update(Collections.singleton(sVer)); - break; - - case 15: - sVer.versionNbr = 16; - db.schemaVersion().update(Collections.singleton(sVer)); - break; - } - if (sVer.versionNbr != ReviewDb.VERSION) { - throw new OrmException("Unsupported schema version " - + sVer.versionNbr + "; expected schema version " - + ReviewDb.VERSION); + throw new OrmException("Unsupported schema version " + sVer.versionNbr + + "; expected schema version " + ReviewDb.VERSION + ". Run init to upgrade."); } final List all = db.systemConfig().all().toList(); @@ -89,8 +57,8 @@ public class SystemConfigProvider implements Provider { case 0: throw new OrmException("system_config table is empty"); default: - throw new OrmException("system_config must have exactly 1 row;" - + " found " + all.size() + " rows instead"); + throw new OrmException("system_config must have exactly 1 row;" + " found " + + all.size() + " rows instead"); } } finally { db.close(); @@ -100,26 +68,6 @@ public class SystemConfigProvider implements Provider { } } - private SchemaVersion createSchema(final ReviewDb db) throws OrmException { - db.createSchema(); - - final SchemaVersion sVer = SchemaVersion.create(); - sVer.versionNbr = ReviewDb.VERSION; - db.schemaVersion().insert(Collections.singleton(sVer)); - - final SystemConfig sConfig = initSystemConfig(db); - initOwnerCategory(db); - initReadCategory(db, sConfig); - initVerifiedCategory(db); - initCodeReviewCategory(db, sConfig); - initSubmitCategory(db); - initPushTagCategory(db); - initPushUpdateBranchCategory(db); - initWildCardProject(db); - - return sVer; - } - private SchemaVersion getSchemaVersion(final ReviewDb db) { try { return db.schemaVersion().get(new SchemaVersion.Key()); @@ -127,212 +75,4 @@ public class SystemConfigProvider implements Provider { return null; } } - - private SystemConfig initSystemConfig(final ReviewDb c) throws OrmException { - final AccountGroup admin = - new AccountGroup(new AccountGroup.NameKey("Administrators"), - new AccountGroup.Id(c.nextAccountGroupId())); - admin.setDescription("Gerrit Site Administrators"); - admin.setType(AccountGroup.Type.INTERNAL); - c.accountGroups().insert(Collections.singleton(admin)); - - final AccountGroup anonymous = - new AccountGroup(new AccountGroup.NameKey("Anonymous Users"), - new AccountGroup.Id(c.nextAccountGroupId())); - anonymous.setDescription("Any user, signed-in or not"); - anonymous.setOwnerGroupId(admin.getId()); - anonymous.setType(AccountGroup.Type.SYSTEM); - c.accountGroups().insert(Collections.singleton(anonymous)); - - final AccountGroup registered = - new AccountGroup(new AccountGroup.NameKey("Registered Users"), - new AccountGroup.Id(c.nextAccountGroupId())); - registered.setDescription("Any signed-in user"); - registered.setOwnerGroupId(admin.getId()); - registered.setType(AccountGroup.Type.SYSTEM); - c.accountGroups().insert(Collections.singleton(registered)); - - File sitePath = new File(".").getAbsoluteFile(); - if (".".equals(sitePath.getName())) { - sitePath = sitePath.getParentFile(); - } - - final SystemConfig s = SystemConfig.create(); - s.registerEmailPrivateKey = SignedToken.generateRandomKey(); - s.adminGroupId = admin.getId(); - s.anonymousGroupId = anonymous.getId(); - s.registeredGroupId = registered.getId(); - s.sitePath = sitePath.getAbsolutePath(); - c.systemConfig().insert(Collections.singleton(s)); - return s; - } - - private void initWildCardProject(final ReviewDb c) throws OrmException { - final Project p; - - p = new Project(DEFAULT_WILD_NAME, WildProjectNameProvider.WILD_PROJECT_ID); - p.setDescription("Rights inherited by all other projects"); - p.setUseContributorAgreements(false); - c.projects().insert(Collections.singleton(p)); - } - - private void initVerifiedCategory(final ReviewDb c) throws OrmException { - final Transaction txn = c.beginTransaction(); - final ApprovalCategory cat; - final ArrayList vals; - - cat = new ApprovalCategory(new ApprovalCategory.Id("VRIF"), "Verified"); - cat.setPosition((short) 0); - cat.setAbbreviatedName("V"); - vals = new ArrayList(); - vals.add(value(cat, 1, "Verified")); - vals.add(value(cat, 0, "No score")); - vals.add(value(cat, -1, "Fails")); - c.approvalCategories().insert(Collections.singleton(cat), txn); - c.approvalCategoryValues().insert(vals, txn); - txn.commit(); - } - - private void initCodeReviewCategory(final ReviewDb c, - final SystemConfig sConfig) throws OrmException { - final Transaction txn = c.beginTransaction(); - final ApprovalCategory cat; - final ArrayList vals; - - cat = new ApprovalCategory(new ApprovalCategory.Id("CRVW"), "Code Review"); - cat.setPosition((short) 1); - cat.setAbbreviatedName("R"); - cat.setCopyMinScore(true); - vals = new ArrayList(); - vals.add(value(cat, 2, "Looks good to me, approved")); - vals.add(value(cat, 1, "Looks good to me, but someone else must approve")); - vals.add(value(cat, 0, "No score")); - vals.add(value(cat, -1, "I would prefer that you didn't submit this")); - vals.add(value(cat, -2, "Do not submit")); - c.approvalCategories().insert(Collections.singleton(cat), txn); - c.approvalCategoryValues().insert(vals, txn); - txn.commit(); - - final ProjectRight approve = - new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), - sConfig.registeredGroupId)); - approve.setMaxValue((short) 1); - approve.setMinValue((short) -1); - c.projectRights().insert(Collections.singleton(approve)); - } - - private void initOwnerCategory(final ReviewDb c) throws OrmException { - final Transaction txn = c.beginTransaction(); - final ApprovalCategory cat; - final ArrayList vals; - - cat = new ApprovalCategory(ApprovalCategory.OWN, "Owner"); - cat.setPosition((short) -1); - cat.setFunctionName(NoOpFunction.NAME); - vals = new ArrayList(); - vals.add(value(cat, 1, "Administer All Settings")); - c.approvalCategories().insert(Collections.singleton(cat), txn); - c.approvalCategoryValues().insert(vals, txn); - txn.commit(); - } - - private void initReadCategory(final ReviewDb c, final SystemConfig sConfig) - throws OrmException { - final Transaction txn = c.beginTransaction(); - final ApprovalCategory cat; - final ArrayList vals; - - cat = new ApprovalCategory(ApprovalCategory.READ, "Read Access"); - cat.setPosition((short) -1); - cat.setFunctionName(NoOpFunction.NAME); - vals = new ArrayList(); - vals.add(value(cat, 2, "Upload permission")); - vals.add(value(cat, 1, "Read access")); - vals.add(value(cat, -1, "No access")); - c.approvalCategories().insert(Collections.singleton(cat), txn); - c.approvalCategoryValues().insert(vals, txn); - txn.commit(); - { - final ProjectRight read = - new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), - sConfig.anonymousGroupId)); - read.setMaxValue((short) 1); - read.setMinValue((short) 1); - c.projectRights().insert(Collections.singleton(read)); - } - { - final ProjectRight read = - new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), - sConfig.registeredGroupId)); - read.setMaxValue((short) 2); - read.setMinValue((short) 1); - c.projectRights().insert(Collections.singleton(read)); - } - { - final ProjectRight read = - new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), - sConfig.adminGroupId)); - read.setMaxValue((short) 1); - read.setMinValue((short) 1); - c.projectRights().insert(Collections.singleton(read)); - } - } - - private void initSubmitCategory(final ReviewDb c) throws OrmException { - final Transaction txn = c.beginTransaction(); - final ApprovalCategory cat; - final ArrayList vals; - - cat = new ApprovalCategory(ApprovalCategory.SUBMIT, "Submit"); - cat.setPosition((short) -1); - cat.setFunctionName(SubmitFunction.NAME); - vals = new ArrayList(); - vals.add(value(cat, 1, "Submit")); - c.approvalCategories().insert(Collections.singleton(cat), txn); - c.approvalCategoryValues().insert(vals, txn); - txn.commit(); - } - - private void initPushTagCategory(final ReviewDb c) throws OrmException { - final Transaction txn = c.beginTransaction(); - final ApprovalCategory cat; - final ArrayList vals; - - cat = new ApprovalCategory(ApprovalCategory.PUSH_TAG, "Push Annotated Tag"); - cat.setPosition((short) -1); - cat.setFunctionName(NoOpFunction.NAME); - vals = new ArrayList(); - vals.add(value(cat, ApprovalCategory.PUSH_TAG_SIGNED, "Create Signed Tag")); - vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED, - "Create Annotated Tag")); - vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANY, "Create Any Tag")); - c.approvalCategories().insert(Collections.singleton(cat), txn); - c.approvalCategoryValues().insert(vals, txn); - txn.commit(); - } - - private void initPushUpdateBranchCategory(final ReviewDb c) - throws OrmException { - final Transaction txn = c.beginTransaction(); - final ApprovalCategory cat; - final ArrayList vals; - - cat = new ApprovalCategory(ApprovalCategory.PUSH_HEAD, "Push Branch"); - cat.setPosition((short) -1); - cat.setFunctionName(NoOpFunction.NAME); - vals = new ArrayList(); - vals.add(value(cat, ApprovalCategory.PUSH_HEAD_UPDATE, "Update Branch")); - vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch")); - vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE, - "Force Push Branch; Delete Branch")); - c.approvalCategories().insert(Collections.singleton(cat), txn); - c.approvalCategoryValues().insert(vals, txn); - txn.commit(); - } - - private static ApprovalCategoryValue value(final ApprovalCategory cat, - final int value, final String name) { - return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(), - (short) value), name); - } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectNameProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectNameProvider.java index bf35ca8beb..ffb4cdeb65 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectNameProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/WildProjectNameProvider.java @@ -9,9 +9,9 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.ProvisionException; -class WildProjectNameProvider implements Provider { +public class WildProjectNameProvider implements Provider { /** Project.Id meaning "any and all projects on this server". */ - static final Project.Id WILD_PROJECT_ID = new Project.Id(0); + public static final Project.Id WILD_PROJECT_ID = new Project.Id(0); private final SchemaFactory schema; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java new file mode 100644 index 0000000000..c11132241d --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java @@ -0,0 +1,300 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.schema; + +import com.google.gerrit.reviewdb.AccountGroup; +import com.google.gerrit.reviewdb.ApprovalCategory; +import com.google.gerrit.reviewdb.ApprovalCategoryValue; +import com.google.gerrit.reviewdb.Project; +import com.google.gerrit.reviewdb.ProjectRight; +import com.google.gerrit.reviewdb.ReviewDb; +import com.google.gerrit.reviewdb.SchemaVersion; +import com.google.gerrit.reviewdb.SystemConfig; +import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.WildProjectNameProvider; +import com.google.gerrit.server.workflow.NoOpFunction; +import com.google.gerrit.server.workflow.SubmitFunction; +import com.google.gwtjsonrpc.server.SignedToken; +import com.google.gwtorm.client.OrmException; +import com.google.gwtorm.client.Transaction; +import com.google.gwtorm.jdbc.JdbcSchema; +import com.google.gwtorm.schema.sql.DialectH2; +import com.google.gwtorm.schema.sql.DialectMySQL; +import com.google.gwtorm.schema.sql.DialectPostgreSQL; +import com.google.gwtorm.schema.sql.SqlDialect; +import com.google.inject.Inject; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; + +/** Creates the current database schema and populates initial code rows. */ +public class SchemaCreator { + private static final Project.NameKey DEFAULT_WILD_NAME = + new Project.NameKey("-- All Projects --"); + + private final File sitePath; + + private final ScriptRunner index_generic; + private final ScriptRunner index_postgres; + private final ScriptRunner mysql_nextval; + + @Inject + public SchemaCreator(final @SitePath File sitePath) { + this.sitePath = sitePath; + + index_generic = new ScriptRunner("index_generic.sql"); + index_postgres = new ScriptRunner("index_postgres.sql"); + mysql_nextval = new ScriptRunner("mysql_nextval.sql"); + } + + public void create(final ReviewDb db) throws OrmException { + final JdbcSchema jdbc = (JdbcSchema) db; + + jdbc.createSchema(); + + final SchemaVersion sVer = SchemaVersion.create(); + sVer.versionNbr = ReviewDb.VERSION; + db.schemaVersion().insert(Collections.singleton(sVer)); + + final SystemConfig sConfig = initSystemConfig(db); + initOwnerCategory(db); + initReadCategory(db, sConfig); + initVerifiedCategory(db); + initCodeReviewCategory(db, sConfig); + initSubmitCategory(db); + initPushTagCategory(db); + initPushUpdateBranchCategory(db); + initWildCardProject(db); + + final SqlDialect d = jdbc.getDialect(); + if (d instanceof DialectH2) { + index_generic.run(db); + + } else if (d instanceof DialectMySQL) { + index_generic.run(db); + mysql_nextval.run(db); + + } else if (d instanceof DialectPostgreSQL) { + index_postgres.run(db); + + } else { + throw new OrmException("Unsupported database " + d.getClass().getName()); + } + } + + private SystemConfig initSystemConfig(final ReviewDb c) throws OrmException { + final AccountGroup admin = + new AccountGroup(new AccountGroup.NameKey("Administrators"), new AccountGroup.Id(c + .nextAccountGroupId())); + admin.setDescription("Gerrit Site Administrators"); + admin.setType(AccountGroup.Type.INTERNAL); + c.accountGroups().insert(Collections.singleton(admin)); + + final AccountGroup anonymous = + new AccountGroup(new AccountGroup.NameKey("Anonymous Users"), new AccountGroup.Id(c + .nextAccountGroupId())); + anonymous.setDescription("Any user, signed-in or not"); + anonymous.setOwnerGroupId(admin.getId()); + anonymous.setType(AccountGroup.Type.SYSTEM); + c.accountGroups().insert(Collections.singleton(anonymous)); + + final AccountGroup registered = + new AccountGroup(new AccountGroup.NameKey("Registered Users"), new AccountGroup.Id(c + .nextAccountGroupId())); + registered.setDescription("Any signed-in user"); + registered.setOwnerGroupId(admin.getId()); + registered.setType(AccountGroup.Type.SYSTEM); + c.accountGroups().insert(Collections.singleton(registered)); + + final SystemConfig s = SystemConfig.create(); + s.registerEmailPrivateKey = SignedToken.generateRandomKey(); + s.adminGroupId = admin.getId(); + s.anonymousGroupId = anonymous.getId(); + s.registeredGroupId = registered.getId(); + try { + s.sitePath = sitePath.getCanonicalPath(); + } catch (IOException e) { + s.sitePath = sitePath.getAbsolutePath(); + } + c.systemConfig().insert(Collections.singleton(s)); + return s; + } + + private void initWildCardProject(final ReviewDb c) throws OrmException { + final Project p; + + p = new Project(DEFAULT_WILD_NAME, WildProjectNameProvider.WILD_PROJECT_ID); + p.setDescription("Rights inherited by all other projects"); + p.setUseContributorAgreements(false); + c.projects().insert(Collections.singleton(p)); + } + + private void initVerifiedCategory(final ReviewDb c) throws OrmException { + final Transaction txn = c.beginTransaction(); + final ApprovalCategory cat; + final ArrayList vals; + + cat = new ApprovalCategory(new ApprovalCategory.Id("VRIF"), "Verified"); + cat.setPosition((short) 0); + cat.setAbbreviatedName("V"); + vals = new ArrayList(); + vals.add(value(cat, 1, "Verified")); + vals.add(value(cat, 0, "No score")); + vals.add(value(cat, -1, "Fails")); + c.approvalCategories().insert(Collections.singleton(cat), txn); + c.approvalCategoryValues().insert(vals, txn); + txn.commit(); + } + + private void initCodeReviewCategory(final ReviewDb c, final SystemConfig sConfig) + throws OrmException { + final Transaction txn = c.beginTransaction(); + final ApprovalCategory cat; + final ArrayList vals; + + cat = new ApprovalCategory(new ApprovalCategory.Id("CRVW"), "Code Review"); + cat.setPosition((short) 1); + cat.setAbbreviatedName("R"); + cat.setCopyMinScore(true); + vals = new ArrayList(); + vals.add(value(cat, 2, "Looks good to me, approved")); + vals.add(value(cat, 1, "Looks good to me, but someone else must approve")); + vals.add(value(cat, 0, "No score")); + vals.add(value(cat, -1, "I would prefer that you didn't submit this")); + vals.add(value(cat, -2, "Do not submit")); + c.approvalCategories().insert(Collections.singleton(cat), txn); + c.approvalCategoryValues().insert(vals, txn); + txn.commit(); + + final ProjectRight approve = + new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), + sConfig.registeredGroupId)); + approve.setMaxValue((short) 1); + approve.setMinValue((short) -1); + c.projectRights().insert(Collections.singleton(approve)); + } + + private void initOwnerCategory(final ReviewDb c) throws OrmException { + final Transaction txn = c.beginTransaction(); + final ApprovalCategory cat; + final ArrayList vals; + + cat = new ApprovalCategory(ApprovalCategory.OWN, "Owner"); + cat.setPosition((short) -1); + cat.setFunctionName(NoOpFunction.NAME); + vals = new ArrayList(); + vals.add(value(cat, 1, "Administer All Settings")); + c.approvalCategories().insert(Collections.singleton(cat), txn); + c.approvalCategoryValues().insert(vals, txn); + txn.commit(); + } + + private void initReadCategory(final ReviewDb c, final SystemConfig sConfig) throws OrmException { + final Transaction txn = c.beginTransaction(); + final ApprovalCategory cat; + final ArrayList vals; + + cat = new ApprovalCategory(ApprovalCategory.READ, "Read Access"); + cat.setPosition((short) -1); + cat.setFunctionName(NoOpFunction.NAME); + vals = new ArrayList(); + vals.add(value(cat, 2, "Upload permission")); + vals.add(value(cat, 1, "Read access")); + vals.add(value(cat, -1, "No access")); + c.approvalCategories().insert(Collections.singleton(cat), txn); + c.approvalCategoryValues().insert(vals, txn); + txn.commit(); + { + final ProjectRight read = + new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), + sConfig.anonymousGroupId)); + read.setMaxValue((short) 1); + read.setMinValue((short) 1); + c.projectRights().insert(Collections.singleton(read)); + } + { + final ProjectRight read = + new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), + sConfig.registeredGroupId)); + read.setMaxValue((short) 2); + read.setMinValue((short) 1); + c.projectRights().insert(Collections.singleton(read)); + } + { + final ProjectRight read = + new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(), + sConfig.adminGroupId)); + read.setMaxValue((short) 1); + read.setMinValue((short) 1); + c.projectRights().insert(Collections.singleton(read)); + } + } + + private void initSubmitCategory(final ReviewDb c) throws OrmException { + final Transaction txn = c.beginTransaction(); + final ApprovalCategory cat; + final ArrayList vals; + + cat = new ApprovalCategory(ApprovalCategory.SUBMIT, "Submit"); + cat.setPosition((short) -1); + cat.setFunctionName(SubmitFunction.NAME); + vals = new ArrayList(); + vals.add(value(cat, 1, "Submit")); + c.approvalCategories().insert(Collections.singleton(cat), txn); + c.approvalCategoryValues().insert(vals, txn); + txn.commit(); + } + + private void initPushTagCategory(final ReviewDb c) throws OrmException { + final Transaction txn = c.beginTransaction(); + final ApprovalCategory cat; + final ArrayList vals; + + cat = new ApprovalCategory(ApprovalCategory.PUSH_TAG, "Push Annotated Tag"); + cat.setPosition((short) -1); + cat.setFunctionName(NoOpFunction.NAME); + vals = new ArrayList(); + vals.add(value(cat, ApprovalCategory.PUSH_TAG_SIGNED, "Create Signed Tag")); + vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED, "Create Annotated Tag")); + vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANY, "Create Any Tag")); + c.approvalCategories().insert(Collections.singleton(cat), txn); + c.approvalCategoryValues().insert(vals, txn); + txn.commit(); + } + + private void initPushUpdateBranchCategory(final ReviewDb c) throws OrmException { + final Transaction txn = c.beginTransaction(); + final ApprovalCategory cat; + final ArrayList vals; + + cat = new ApprovalCategory(ApprovalCategory.PUSH_HEAD, "Push Branch"); + cat.setPosition((short) -1); + cat.setFunctionName(NoOpFunction.NAME); + vals = new ArrayList(); + vals.add(value(cat, ApprovalCategory.PUSH_HEAD_UPDATE, "Update Branch")); + vals.add(value(cat, ApprovalCategory.PUSH_HEAD_CREATE, "Create Branch")); + vals.add(value(cat, ApprovalCategory.PUSH_HEAD_REPLACE, "Force Push Branch; Delete Branch")); + c.approvalCategories().insert(Collections.singleton(cat), txn); + c.approvalCategoryValues().insert(vals, txn); + txn.commit(); + } + + private static ApprovalCategoryValue value(final ApprovalCategory cat, final int value, + final String name) { + return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(), (short) value), name); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java new file mode 100644 index 0000000000..5ef73c3d59 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaUpdater.java @@ -0,0 +1,79 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.schema; + +import com.google.gerrit.reviewdb.ReviewDb; +import com.google.gerrit.reviewdb.SchemaVersion; +import com.google.gerrit.reviewdb.SystemConfig; +import com.google.gerrit.server.config.SitePath; +import com.google.gwtorm.client.OrmException; +import com.google.gwtorm.client.SchemaFactory; +import com.google.inject.Inject; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; + +/** Creates or updates the current database schema. */ +public class SchemaUpdater { + private final SchemaFactory schema; + private final File sitePath; + private final SchemaCreator creator; + + @Inject + SchemaUpdater(final SchemaFactory schema, + final @SitePath File sitePath, + final SchemaCreator creator) { + this.schema = schema; + this.sitePath = sitePath; + this.creator = creator; + } + + public void update() throws OrmException { + final ReviewDb db = schema.open(); + try { + final SchemaVersion version = getSchemaVersion(db); + if (version == null) { + creator.create(db); + + } else { + updateSystemConfig(db); + } + } finally { + db.close(); + } + } + + private SchemaVersion getSchemaVersion(final ReviewDb db) { + try { + return db.schemaVersion().get(new SchemaVersion.Key()); + } catch (OrmException e) { + return null; + } + } + + private void updateSystemConfig(final ReviewDb db) throws OrmException { + final SystemConfig sc = db.systemConfig().get(new SystemConfig.Key()); + if (sc == null) { + throw new OrmException("No record in system_config table"); + } + try { + sc.sitePath = sitePath.getCanonicalPath(); + } catch (IOException e) { + sc.sitePath = sitePath.getAbsolutePath(); + } + db.systemConfig().update(Collections.singleton(sc)); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java new file mode 100644 index 0000000000..34c47a80a9 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ScriptRunner.java @@ -0,0 +1,123 @@ +// Copyright (C) 2009 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.schema; + +import com.google.gerrit.reviewdb.ReviewDb; +import com.google.gwtorm.client.OrmException; +import com.google.gwtorm.jdbc.JdbcSchema; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +/** Parses a SQL script from a resource file and later runs it. */ +class ScriptRunner { + private final String name; + private final List commands; + + ScriptRunner(final String name) { + try { + this.name = name; + this.commands = parse(name); + } catch (IOException e) { + throw new IllegalStateException("Cannot parse " + name, e); + } + } + + void run(final ReviewDb db) throws OrmException { + try { + final Connection c = ((JdbcSchema) db).getConnection(); + final Statement stmt = c.createStatement(); + try { + for (String sql : commands) { + try { + stmt.execute(sql); + } catch (SQLException e) { + throw new OrmException("Error in " + name + ":\n" + sql, e); + } + } + } finally { + stmt.close(); + } + } catch (SQLException e) { + throw new OrmException("Cannot run statements for " + name, e); + } + } + + private List parse(final String name) throws IOException { + InputStream in = ReviewDb.class.getResourceAsStream(name); + if (in == null) { + throw new FileNotFoundException("SQL script " + name + " not found"); + } + + BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + try { + String delimiter = ";"; + List commands = new ArrayList(); + StringBuilder buffer = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + if (line.isEmpty()) { + continue; + } + if (line.startsWith("--")) { + continue; + } + + if (buffer.length() == 0 && line.toLowerCase().startsWith("delimiter ")) { + delimiter = line.substring("delimiter ".length()).trim(); + continue; + } + + if (buffer.length() > 0) { + buffer.append('\n'); + } + buffer.append(line); + + if (isDone(delimiter, line, buffer)) { + String cmd = buffer.toString(); + commands.add(cmd); + buffer = new StringBuilder(); + } + } + if (buffer.length() > 0) { + commands.add(buffer.toString()); + } + return commands; + } finally { + br.close(); + } + } + + private boolean isDone(String delimiter, String line, StringBuilder buffer) { + if (";".equals(delimiter)) { + return buffer.charAt(buffer.length() - 1) == ';'; + + } else if (line.equals(delimiter)) { + buffer.setLength(buffer.length() - delimiter.length()); + return true; + + } else { + return false; + } + } +} diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/SystemConfigProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java similarity index 96% rename from gerrit-server/src/test/java/com/google/gerrit/server/config/SystemConfigProviderTest.java rename to gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java index 77a3ee2156..00fa2c2785 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/config/SystemConfigProviderTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.server.config; +package com.google.gerrit.server.schema; import com.google.gerrit.reviewdb.AccountGroup; import com.google.gerrit.reviewdb.ApprovalCategory; @@ -22,6 +22,8 @@ import com.google.gerrit.reviewdb.ProjectRight; import com.google.gerrit.reviewdb.ReviewDb; import com.google.gerrit.reviewdb.SchemaVersion; import com.google.gerrit.reviewdb.SystemConfig; +import com.google.gerrit.server.config.SystemConfigProvider; +import com.google.gerrit.server.config.WildProjectNameProvider; import com.google.gerrit.server.workflow.NoOpFunction; import com.google.gerrit.server.workflow.SubmitFunction; import com.google.gerrit.testutil.TestDatabase; @@ -33,7 +35,7 @@ import java.io.File; import java.sql.SQLException; import java.util.HashSet; -public class SystemConfigProviderTest extends TestCase { +public class SchemaCreatorTest extends TestCase { private ApprovalCategory.Id codeReview = new ApprovalCategory.Id("CRVW"); private TestDatabase db; @@ -65,6 +67,7 @@ public class SystemConfigProviderTest extends TestCase { // Create the schema using the current schema version. // + db.create(); final SystemConfig config = getSystemConfig(); final SchemaVersion version = getSchemaVersion(); assertNotNull(version); @@ -89,7 +92,8 @@ public class SystemConfigProviderTest extends TestCase { assertTrue(20 < config.registerEmailPrivateKey.length()); } - public void testSubsequentGetReads() { + public void testSubsequentGetReads() throws OrmException { + db.create(); final SystemConfig exp = getSystemConfig(); final SystemConfig act = getSystemConfig(); @@ -102,6 +106,7 @@ public class SystemConfigProviderTest extends TestCase { } public void testCreateSchema_Group_Administrators() throws OrmException { + db.create(); final SystemConfig config = getSystemConfig(); final ReviewDb c = db.open(); try { @@ -116,6 +121,7 @@ public class SystemConfigProviderTest extends TestCase { } public void testCreateSchema_Group_AnonymousUsers() throws OrmException { + db.create(); final SystemConfig config = getSystemConfig(); final ReviewDb c = db.open(); try { @@ -130,6 +136,7 @@ public class SystemConfigProviderTest extends TestCase { } public void testCreateSchema_Group_RegisteredUsers() throws OrmException { + db.create(); final SystemConfig config = getSystemConfig(); final ReviewDb c = db.open(); try { @@ -308,12 +315,14 @@ public class SystemConfigProviderTest extends TestCase { public void testCreateSchema_DefaultAccess_AnonymousUsers() throws OrmException { + db.create(); final SystemConfig config = getSystemConfig(); assertDefaultRight(config.anonymousGroupId, ApprovalCategory.READ, 1, 1); } public void testCreateSchema_DefaultAccess_RegisteredUsers() throws OrmException { + db.create(); final SystemConfig config = getSystemConfig(); assertDefaultRight(config.registeredGroupId, ApprovalCategory.READ, 1, 2); assertDefaultRight(config.registeredGroupId, codeReview, -1, 1); @@ -321,6 +330,7 @@ public class SystemConfigProviderTest extends TestCase { public void testCreateSchema_DefaultAccess_Administrators() throws OrmException { + db.create(); final SystemConfig config = getSystemConfig(); assertDefaultRight(config.adminGroupId, ApprovalCategory.READ, 1, 1); } diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestDatabase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestDatabase.java index 7dc504fc5f..51c006ff08 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestDatabase.java +++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestDatabase.java @@ -15,12 +15,13 @@ package com.google.gerrit.testutil; import com.google.gerrit.reviewdb.ReviewDb; -import com.google.gerrit.server.config.SystemConfigProvider; +import com.google.gerrit.server.schema.SchemaCreator; import com.google.gwtorm.client.OrmException; import com.google.gwtorm.client.SchemaFactory; import com.google.gwtorm.jdbc.Database; import com.google.gwtorm.jdbc.SimpleDataSource; +import java.io.File; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; @@ -55,6 +56,7 @@ public class TestDatabase implements SchemaFactory { private Connection openHandle; private Database database; + private boolean created; public TestDatabase() throws OrmException { try { @@ -84,8 +86,16 @@ public class TestDatabase implements SchemaFactory { } /** Ensure the database schema has been created and initialized. */ - public TestDatabase create() { - new SystemConfigProvider(this).get(); + public TestDatabase create() throws OrmException { + if (!created) { + created = true; + final ReviewDb c = open(); + try { + new SchemaCreator(new File(".")).create(c); + } finally { + c.close(); + } + } return this; }