blob: 8f948fb361a78c2b0fd9a695d00ed0ae94df67ef [file] [log] [blame]
// Copyright (C) 2015 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.googlesource.gerrit.plugins.verifystatus.server.schema;
import com.google.common.collect.Lists;
import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.StatementExecutor;
import com.google.inject.Provider;
import com.googlesource.gerrit.plugins.verifystatus.server.CiDb;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
public static final Class<Schema_1> C = Schema_1.class;
public static int getBinaryVersion() {
return guessVersion(C);
}
private final Provider<? extends SchemaVersion> prior;
private final int versionNbr;
protected SchemaVersion(Provider<? extends SchemaVersion> prior) {
this.prior = prior;
this.versionNbr = guessVersion(getClass());
}
public static int guessVersion(Class<?> c) {
String n = c.getName();
n = n.substring(n.lastIndexOf('_') + 1);
while (n.startsWith("0")) {
n = n.substring(1);
}
return Integer.parseInt(n);
}
/** @return the version number this step targets. */
public final int getVersionNbr() {
return versionNbr;
}
public final void check(UpdateUI ui, CurrentSchemaVersion curr, CiDb db)
throws OrmException, SQLException {
if (curr.versionNbr == versionNbr) {
// Nothing to do, we are at the correct schema.
} else if (curr.versionNbr > versionNbr) {
throw new OrmException(
"Cannot downgrade database schema from version "
+ curr.versionNbr
+ " to "
+ versionNbr
+ ".");
} else {
upgradeFrom(ui, curr, db);
}
}
/** Runs check on the prior schema version, and then upgrades. */
private void upgradeFrom(UpdateUI ui, CurrentSchemaVersion curr, CiDb db)
throws OrmException, SQLException {
List<SchemaVersion> pending = pending(curr.versionNbr);
updateSchema(pending, ui, db);
migrateData(pending, ui, curr, db);
JdbcSchema s = (JdbcSchema) db;
final List<String> pruneList = Lists.newArrayList();
s.pruneSchema(
new StatementExecutor() {
@Override
public void execute(String sql) {
pruneList.add(sql);
}
@Override
public void close() {
// Do nothing.
}
});
try (JdbcExecutor e = new JdbcExecutor(s)) {
if (!pruneList.isEmpty()) {
ui.pruneSchema(e, pruneList);
}
}
}
private List<SchemaVersion> pending(int curr) {
List<SchemaVersion> r = Lists.newArrayListWithCapacity(versionNbr - curr);
for (SchemaVersion v = this; curr < v.getVersionNbr(); v = v.prior.get()) {
r.add(v);
}
Collections.reverse(r);
return r;
}
private void updateSchema(List<SchemaVersion> pending, UpdateUI ui, CiDb db)
throws OrmException, SQLException {
for (SchemaVersion v : pending) {
ui.message(String.format("Upgrading schema to %d ...", v.getVersionNbr()));
v.preUpdateSchema(db);
}
JdbcSchema s = (JdbcSchema) db;
try (JdbcExecutor e = new JdbcExecutor(s)) {
s.updateSchema(e);
}
}
/**
* Invoked before updateSchema adds new columns/tables.
*
* @param db open database handle.
* @throws OrmException if a Gerrit-specific exception occurred.
* @throws SQLException if an underlying SQL exception occurred.
*/
protected void preUpdateSchema(CiDb db) throws OrmException, SQLException {}
private void migrateData(
List<SchemaVersion> pending, UpdateUI ui, CurrentSchemaVersion curr, CiDb db)
throws OrmException, SQLException {
for (SchemaVersion v : pending) {
ui.message(String.format("Migrating data to schema %d ...", v.getVersionNbr()));
v.migrateData(db, ui);
v.finish(curr, db);
}
}
/**
* Invoked between updateSchema (adds new columns/tables) and pruneSchema (removes deleted
* columns/tables).
*
* @param db open database handle.
* @param ui interface for interacting with the user.
* @throws OrmException if a Gerrit-specific exception occurred.
* @throws SQLException if an underlying SQL exception occurred.
*/
protected void migrateData(CiDb db, UpdateUI ui) throws OrmException, SQLException {}
/** Mark the current schema version. */
protected void finish(CurrentSchemaVersion curr, CiDb db) throws OrmException {
curr.versionNbr = versionNbr;
db.schemaVersion().update(Collections.singleton(curr));
}
}