package com.opower.updater.operation; import com.opower.updater.LayoutUpdate; import com.opower.updater.admin.LayoutUpdateTable; import com.opower.updater.admin.Update; import com.opower.updater.ddl.DDLRunner; import org.kiji.schema.KijiMetaTable; import java.io.IOException; import java.util.SortedSet; /** * Class responsible for updating a table by sequentially applying the updates. * * @author felix.trepanier */ public class TableUpdater extends TableOperator { private final TableDDLGenerator tableDDLGenerator; /** * Constructor the {@link com.opower.updater.operation.TableUpdater}. * * @param ddlRunner The DDLRunner used to execute DDL statements. * @param layoutUpdateTable The layout_update table. * @param metaTable Kiji MetaTable */ public TableUpdater(DDLRunner ddlRunner, LayoutUpdateTable layoutUpdateTable, KijiMetaTable metaTable, TableDDLGenerator tableDDLGenerator) { super(ddlRunner, layoutUpdateTable, metaTable); this.tableDDLGenerator = tableDDLGenerator; } /** * Update a given table. * * @param tableName The name of the table to load updates for. * @param updates The sorted set of updates. * @param bootstrap If the table exists in the instance but not in layout_update, bootstrap it in layout_update. * @throws IOException */ public UpdateResult updateTable(String tableName, SortedSet<Update> updates, boolean bootstrap) throws IOException { boolean wasBootstrapped = false; checkTableExistsInInstance(tableName); if (!bootstrap) { // If we are not bootstrapping an existing instance with the updater tool, then the table needs to // exist in the layout_update table. checkTableExistsInLayoutUpdateTable(tableName); } else if (layoutUpdateTable.getLastUpdateIdForTable(tableName) == null) { // If we are bootstrapping an existing instance to use the updater tool and the table is not found // in the layout_update table, then insert the initial update based on the table current layout. bootstrapExistingTableInLayoutUpdateTable(tableName); wasBootstrapped = true; } Integer id = layoutUpdateTable.getLastUpdateIdForTable(tableName); Integer nextUpdate = id + 1; SortedSet<Update> updatesToApply = updates.tailSet(Update.fromUpdate(nextUpdate)); Integer expectedUpdateId = nextUpdate; for (Update update : updatesToApply) { if (update.getId() != expectedUpdateId) { throw new MissingUpdateException(expectedUpdateId, update.getId()); } try { LayoutUpdate layoutUpdate = applyUpdate(update); layoutUpdateTable.insertLayoutUpdate(tableName, layoutUpdate); expectedUpdateId += 1; } catch (Exception ex) { throw new UpdateException(tableName, update, ex); } } return new UpdateResult(tableName, wasBootstrapped, updatesToApply.size()); } private void bootstrapExistingTableInLayoutUpdateTable(String tableName) throws IOException { String currentTableDDL = tableDDLGenerator.generateTableLayoutDDL(tableName); LayoutUpdate initialLayoutUpdate = LayoutUpdate.newBuilder() .setUpdateId(0) .setAppliedDDL(currentTableDDL) .setChecksum("") // TODO check with the expected checksum from the create DDL .setValidationFunctionVersion(0) .build(); layoutUpdateTable.insertLayoutUpdate(tableName, initialLayoutUpdate); } /** * Class representing the update result. */ public static class UpdateResult { private final String tableName; private final boolean wasBootstrapped; private final Integer numberOfUpdatesApplied; public UpdateResult(String tableName, boolean wasBootstrapped, Integer numberOfUpdatesApplied) { this.tableName = tableName; this.wasBootstrapped = wasBootstrapped; this.numberOfUpdatesApplied = numberOfUpdatesApplied; } /** * @return The updated table name. */ public String getTableName() { return tableName; } /** * @return true if the table was bootstrapped in the layout_update table, false otherwise */ public boolean wasBootstrapped() { return wasBootstrapped; } /** * @return The number of applied updates. */ public Integer getNumberOfUpdatesApplied() { return numberOfUpdatesApplied; } } }