/* * Copyright (c) 2016 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper - initial API and implementation */ package org.eclipse.emf.cdo.server.internal.db; import org.eclipse.emf.cdo.common.CDOCommonRepository.CommitInfoStorage; import org.eclipse.emf.cdo.common.branch.CDOBranch; import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; import org.eclipse.emf.cdo.common.commit.CDOCommitInfoHandler; import org.eclipse.emf.cdo.common.protocol.CDODataInput; import org.eclipse.emf.cdo.common.protocol.CDODataOutput; import org.eclipse.emf.cdo.server.db.IDBStoreAccessor; import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranch; import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranchManager; import org.eclipse.emf.cdo.spi.common.commit.CDOCommitInfoUtil; import org.eclipse.emf.cdo.spi.common.commit.InternalCDOCommitInfoManager; import org.eclipse.emf.cdo.spi.server.InternalRepository; import org.eclipse.net4j.db.DBException; import org.eclipse.net4j.db.DBType; import org.eclipse.net4j.db.DBUtil; import org.eclipse.net4j.db.IDBDatabase; import org.eclipse.net4j.db.IDBDatabase.RunnableWithSchema; import org.eclipse.net4j.db.IDBPreparedStatement; import org.eclipse.net4j.db.IDBPreparedStatement.ReuseProbability; import org.eclipse.net4j.db.ddl.IDBField; import org.eclipse.net4j.db.ddl.IDBIndex; import org.eclipse.net4j.db.ddl.IDBSchema; import org.eclipse.net4j.db.ddl.IDBTable; import org.eclipse.net4j.util.lifecycle.Lifecycle; import org.eclipse.net4j.util.om.monitor.OMMonitor; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; /** * @author Eike Stepper * @since 4.6 */ public class CommitInfoTable extends Lifecycle { private static final String COMMIT_INFOS = "cdo_commit_infos"; //$NON-NLS-1$ private static final String TIMESTAMP = "commit_time"; //$NON-NLS-1$ private static final String PREVIOUS_TIMESTAMP = "previous_time"; private static final String BRANCH = "branch_id"; //$NON-NLS-1$ private static final String USER = "user_id"; //$NON-NLS-1$ private static final String COMMENT = "commit_comment"; //$NON-NLS-1$ private static final String MERGED_BRANCH = "merged_branch"; //$NON-NLS-1$ private static final String MERGED_TIMESTAMP = "merged_time"; //$NON-NLS-1$ private DBStore store; private boolean withMergeSource; private IDBTable table; private String sqlInsert; public CommitInfoTable(DBStore store) { this.store = store; } public void writeCommitInfo(IDBStoreAccessor accessor, CDOBranch branch, long timeStamp, long previousTimeStamp, String userID, String comment, CDOBranchPoint mergeSource, OMMonitor monitor) { IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlInsert, ReuseProbability.HIGH); try { stmt.setLong(1, timeStamp); stmt.setLong(2, previousTimeStamp); stmt.setInt(3, branch.getID()); stmt.setString(4, userID); stmt.setString(5, comment); if (withMergeSource) { if (mergeSource != null) { stmt.setInt(6, mergeSource.getBranch().getID()); stmt.setLong(7, mergeSource.getTimeStamp()); } else { stmt.setNull(6, DBType.INTEGER.getCode()); stmt.setNull(7, DBType.BIGINT.getCode()); } } DBUtil.update(stmt, true); } catch (SQLException ex) { throw new DBException(ex); } finally { DBUtil.close(stmt); } } public void loadCommitInfos(IDBStoreAccessor accessor, CDOBranch branch, long startTime, long endTime, CDOCommitInfoHandler handler) { int count = CDOCommitInfoUtil.decodeCount(endTime); StringBuilder builder = new StringBuilder(); builder.append("SELECT "); //$NON-NLS-1$ builder.append(TIMESTAMP); builder.append(", "); //$NON-NLS-1$ builder.append(PREVIOUS_TIMESTAMP); builder.append(", "); //$NON-NLS-1$ builder.append(USER); builder.append(", "); //$NON-NLS-1$ builder.append(COMMENT); if (branch == null) { builder.append(", "); //$NON-NLS-1$ builder.append(BRANCH); } if (withMergeSource) { builder.append(", "); //$NON-NLS-1$ builder.append(MERGED_BRANCH); builder.append(", "); //$NON-NLS-1$ builder.append(MERGED_TIMESTAMP); } builder.append(" FROM "); //$NON-NLS-1$ builder.append(COMMIT_INFOS); boolean where = false; if (branch != null) { builder.append(where ? " AND " : " WHERE "); //$NON-NLS-1$ //$NON-NLS-2$ builder.append(BRANCH); builder.append("="); //$NON-NLS-1$ builder.append(branch.getID()); where = true; } if (startTime != CDOBranchPoint.UNSPECIFIED_DATE) { builder.append(where ? " AND " : " WHERE "); //$NON-NLS-1$ //$NON-NLS-2$ builder.append(TIMESTAMP); builder.append(count < 0 ? "<=" : ">="); //$NON-NLS-1$ builder.append(startTime); where = true; } if (endTime > CDOBranchPoint.UNSPECIFIED_DATE) { builder.append(where ? " AND " : " WHERE "); //$NON-NLS-1$ //$NON-NLS-2$ builder.append(TIMESTAMP); builder.append("<="); //$NON-NLS-1$ builder.append(endTime); where = true; } builder.append(" ORDER BY "); //$NON-NLS-1$ builder.append(TIMESTAMP); builder.append(count < 0 || CDOBranchPoint.UNSPECIFIED_DATE <= endTime && endTime <= startTime ? " DESC" : " ASC"); //$NON-NLS-1$ String sql = builder.toString(); IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sql, ReuseProbability.LOW); ResultSet resultSet = null; InternalRepository repository = store.getRepository(); InternalCDOBranchManager branchManager = repository.getBranchManager(); InternalCDOCommitInfoManager commitInfoManager = repository.getCommitInfoManager(); count = Math.abs(count); try { resultSet = stmt.executeQuery(); while (count-- > 0 && resultSet.next()) { int column = 0; long timeStamp = resultSet.getLong(++column); long previousTimeStamp = resultSet.getLong(++column); String userID = resultSet.getString(++column); String comment = resultSet.getString(++column); CDOBranch infoBranch = branch; if (infoBranch == null) { int id = resultSet.getInt(++column); infoBranch = branchManager.getBranch(id); } CDOBranchPoint mergeSource = null; if (withMergeSource) { int id = resultSet.getInt(++column); if (!resultSet.wasNull()) { InternalCDOBranch mergedBranch = branchManager.getBranch(id); long mergedTimeStamp = resultSet.getLong(++column); mergeSource = mergedBranch.getPoint(mergedTimeStamp); } } CDOCommitInfo commitInfo = commitInfoManager.createCommitInfo(infoBranch, timeStamp, previousTimeStamp, userID, comment, mergeSource, null); handler.handleCommitInfo(commitInfo); } } catch (SQLException ex) { throw new DBException(ex); } finally { DBUtil.close(resultSet); DBUtil.close(stmt); } } public void rawExport(Connection connection, CDODataOutput out, long fromCommitTime, long toCommitTime) throws IOException { out.writeBoolean(withMergeSource); String where = " WHERE " + TIMESTAMP + " BETWEEN " + fromCommitTime + " AND " + toCommitTime; DBUtil.serializeTable(out, connection, table, null, where); } public void rawImport(Connection connection, CDODataInput in, long fromCommitTime, long toCommitTime, OMMonitor monitor) throws IOException { boolean actualWithMergeSource = in.readBoolean(); if (actualWithMergeSource != withMergeSource) { throw new IllegalStateException("Commit info data mismatch. Expected: " + (withMergeSource ? "with" : "without") + " merge source. Actual: " + (actualWithMergeSource ? "with" : "without") + " merge source."); } DBUtil.deserializeTable(in, connection, table, monitor.fork()); } public void repairAfterCrash(Connection connection) { IDBField timeStampField = table.getField(TIMESTAMP); long lastCommitTime = DBUtil.selectMaximumLong(connection, timeStampField); long lastNonLocalCommitTime = DBUtil.selectMaximumLong(connection, timeStampField, CDOBranch.MAIN_BRANCH_ID + "<=" + BRANCH); if (lastNonLocalCommitTime == CDOBranchPoint.UNSPECIFIED_DATE) { lastNonLocalCommitTime = lastCommitTime; } store.setLastCommitTime(lastCommitTime); store.setLastNonLocalCommitTime(lastNonLocalCommitTime); } @Override protected void doActivate() throws Exception { super.doActivate(); InternalRepository repository = store.getRepository(); withMergeSource = repository.getCommitInfoStorage() == CommitInfoStorage.WITH_MERGE_SOURCE; IDBDatabase database = store.getDatabase(); table = database.getSchema().getTable(COMMIT_INFOS); if (table == null) { database.updateSchema(new RunnableWithSchema() { public void run(IDBSchema schema) { table = schema.addTable(COMMIT_INFOS); table.addField(TIMESTAMP, DBType.BIGINT, true); table.addField(PREVIOUS_TIMESTAMP, DBType.BIGINT); table.addField(BRANCH, DBType.INTEGER); table.addField(USER, DBType.VARCHAR); table.addField(COMMENT, DBType.VARCHAR); table.addIndex(IDBIndex.Type.PRIMARY_KEY, TIMESTAMP); table.addIndex(IDBIndex.Type.NON_UNIQUE, BRANCH); if (withMergeSource) { table.addField(MERGED_BRANCH, DBType.INTEGER); table.addField(MERGED_TIMESTAMP, DBType.BIGINT); table.addIndex(IDBIndex.Type.NON_UNIQUE, MERGED_BRANCH, MERGED_TIMESTAMP); } } }); } else { if (withMergeSource && table.getField(MERGED_BRANCH) == null) { database.updateSchema(new RunnableWithSchema() { public void run(IDBSchema schema) { IDBTable table = schema.getTable(COMMIT_INFOS); table.addField(MERGED_BRANCH, DBType.INTEGER); table.addField(MERGED_TIMESTAMP, DBType.BIGINT); table.addIndex(IDBIndex.Type.NON_UNIQUE, MERGED_BRANCH, MERGED_TIMESTAMP); } }); } } StringBuilder builder = new StringBuilder(); builder.append("INSERT INTO "); //$NON-NLS-1$ builder.append(COMMIT_INFOS); builder.append("("); //$NON-NLS-1$ builder.append(TIMESTAMP); builder.append(", "); //$NON-NLS-1$ builder.append(PREVIOUS_TIMESTAMP); builder.append(", "); //$NON-NLS-1$ builder.append(BRANCH); builder.append(", "); //$NON-NLS-1$ builder.append(USER); builder.append(", "); //$NON-NLS-1$ builder.append(COMMENT); if (withMergeSource) { builder.append(", "); //$NON-NLS-1$ builder.append(MERGED_BRANCH); builder.append(", "); //$NON-NLS-1$ builder.append(MERGED_TIMESTAMP); } builder.append(") VALUES (?, ?, ?, ?, ?"); //$NON-NLS-1$ if (withMergeSource) { builder.append(", ?, ?"); //$NON-NLS-1$ } builder.append(")"); //$NON-NLS-1$ sqlInsert = builder.toString(); } @Override protected void doDeactivate() throws Exception { sqlInsert = null; table = null; super.doDeactivate(); } }