/*
* Copyright (c) 2009-2013, 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:
* Stefan Winkler - initial API and implementation
*/
package org.eclipse.emf.cdo.tests.db.verifier;
import org.eclipse.emf.cdo.common.model.EMFUtil;
import org.eclipse.emf.cdo.server.IRepository;
import org.eclipse.emf.cdo.server.IStore;
import org.eclipse.emf.cdo.server.db.IDBStore;
import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
import org.eclipse.emf.cdo.server.db.mapping.IClassMapping;
import org.eclipse.emf.cdo.server.db.mapping.IListMapping;
import org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalAuditClassMapping;
import org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalAuditMappingStrategy;
import org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalNonAuditClassMapping;
import org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.HorizontalNonAuditMappingStrategy;
import org.eclipse.emf.cdo.server.internal.db.mapping.horizontal.IMappingConstants;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageInfo;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageRegistry;
import org.eclipse.emf.cdo.tests.db.bundle.OM;
import org.eclipse.net4j.util.collection.Pair;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.emf.ecore.EClass;
import org.junit.Assert;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import junit.framework.AssertionFailedError;
/**
* @author Stefan Winkler
*/
public abstract class DBStoreVerifier
{
protected static final ContextTracer TRACER = new ContextTracer(OM.DEBUG, DBStoreVerifier.class);
private IRepository repository;
private IDBStoreAccessor accessor;
public DBStoreVerifier(IRepository repository)
{
this.repository = repository;
if (repository != null)
{
Assert.assertEquals(true, repository.getStore() instanceof IDBStore);
}
}
protected IRepository getRepository()
{
return repository;
}
protected IDBStore getStore()
{
return (IDBStore)repository.getStore();
}
protected Statement getStatement()
{
if (accessor == null)
{
accessor = (IDBStoreAccessor)repository.getStore().getReader(null);
}
try
{
return accessor.getDBConnection().createStatement();
}
catch (SQLException ex)
{
ex.printStackTrace();
return null;
}
}
protected Connection getConnection() throws SQLException
{
return getStatement().getConnection();
}
protected DatabaseMetaData getMetaData() throws SQLException
{
return getConnection().getMetaData();
}
protected List<IClassMapping> getClassMappings()
{
ArrayList<IClassMapping> result = new ArrayList<IClassMapping>();
InternalCDOPackageRegistry packageRegistry = (InternalCDOPackageRegistry)repository.getPackageRegistry();
for (InternalCDOPackageInfo packageInfo : packageRegistry.getPackageInfos())
{
// CDO core package is not mapped in horizontal mapping
if (!packageInfo.isCorePackage())
{
for (EClass cls : EMFUtil.getPersistentClasses(packageInfo.getEPackage()))
{
result.add(getStore().getMappingStrategy().getClassMapping(cls));
}
}
}
return result;
}
protected void cleanUp()
{
if (accessor != null)
{
accessor.release();
}
}
public void verify() throws VerificationException
{
try
{
if (TRACER.isEnabled())
{
TRACER.format("Starting {0}...", getClass().getSimpleName());
}
doVerify();
if (TRACER.isEnabled())
{
TRACER.format("{0} completed without complaints...", getClass().getSimpleName());
}
}
catch (Exception e)
{
throw new VerificationException(e);
}
finally
{
cleanUp();
}
}
protected void sqlDump(String sql)
{
ResultSet rs = null;
try
{
TRACER.format("Dumping output of {0}", sql);
rs = getStatement().executeQuery(sql);
int numCol = rs.getMetaData().getColumnCount();
StringBuilder row = new StringBuilder();
for (int c = 1; c <= numCol; c++)
{
row.append(String.format("%10s | ", rs.getMetaData().getColumnLabel(c)));
}
TRACER.trace(row.toString());
row = new StringBuilder();
for (int c = 1; c <= numCol; c++)
{
row.append("-----------+--");
}
TRACER.trace(row.toString());
while (rs.next())
{
row = new StringBuilder();
for (int c = 1; c <= numCol; c++)
{
row.append(String.format("%10s | ", rs.getString(c)));
}
TRACER.trace(row.toString());
}
row = new StringBuilder();
for (int c = 1; c <= numCol; c++)
{
row.append("-----------+-");
}
TRACER.trace(row.toString());
}
catch (SQLException ex)
{
// NOP
}
finally
{
if (rs != null)
{
try
{
rs.close();
}
catch (SQLException ex)
{
// NOP
}
}
}
}
protected abstract void doVerify() throws Exception;
/**
* @author Stefan Winkler
*/
public static class VerificationException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public VerificationException(String message)
{
super(message);
}
public VerificationException(String message, Throwable t)
{
super(message, t);
}
public VerificationException(Throwable t)
{
super(t);
}
}
/**
* @author Stefan Winkler
*/
public static class Audit extends DBStoreVerifier
{
public Audit(IRepository repo)
{
super(repo);
// this is a verifier for auditing mode
Assert.assertEquals(true, getStore().getMappingStrategy() instanceof HorizontalAuditMappingStrategy);
}
@Override
protected void doVerify() throws Exception
{
for (IClassMapping mapping : getClassMappings())
{
if (mapping != null && mapping.getDBTables() != null)
{
verifyClassMapping(mapping);
}
}
}
private void verifyClassMapping(IClassMapping mapping) throws Exception
{
verifyAtMostOneUnrevised(mapping);
verifyUniqueIdVersion(mapping);
verifyReferences(mapping);
}
private void verifyAtMostOneUnrevised(IClassMapping mapping) throws Exception
{
String tableName = mapping.getDBTables().iterator().next().getName();
TRACER.format("verifyAtMostOneUnrevised: {0} ...", tableName);
String sql = "SELECT " + IMappingConstants.ATTRIBUTES_ID + ", count(1) FROM " + tableName + " WHERE " + IMappingConstants.ATTRIBUTES_REVISED
+ "= 0 GROUP BY " + IMappingConstants.ATTRIBUTES_ID;
TRACER.format(" Executing SQL: {0} ", sql);
ResultSet resultSet = getStatement().executeQuery(sql);
try
{
while (resultSet.next())
{
Assert.assertEquals("Multiple unrevised rows for ID " + resultSet.getLong(1), true, resultSet.getInt(2) <= 1);
}
}
finally
{
resultSet.close();
}
}
/**
* Verify that the pair (id,version) is unique.
*/
private void verifyUniqueIdVersion(IClassMapping mapping) throws Exception
{
String tableName = mapping.getDBTables().iterator().next().getName();
TRACER.format("verifyUniqueIdVersion: {0} ...", tableName);
String sql = "SELECT " + IMappingConstants.ATTRIBUTES_ID + "," + IMappingConstants.ATTRIBUTES_VERSION + ", count(1) FROM " + tableName + " GROUP BY "
+ IMappingConstants.ATTRIBUTES_ID + "," + IMappingConstants.ATTRIBUTES_VERSION;
TRACER.format(" Executing SQL: {0} ", sql);
ResultSet resultSet = getStatement().executeQuery(sql);
try
{
while (resultSet.next())
{
Assert.assertEquals("Multiple rows for ID " + resultSet.getLong(1) + "v" + resultSet.getInt(2), true, resultSet.getInt(3) <= 1);
}
}
catch (AssertionFailedError e)
{
TRACER.trace(e.getMessage());
sqlDump("SELECT * FROM " + tableName + " WHERE " + IMappingConstants.ATTRIBUTES_REVISED + "=0");
throw e;
}
finally
{
resultSet.close();
}
}
private void verifyReferences(IClassMapping mapping) throws Exception
{
List<IListMapping> listMappings = ((HorizontalAuditClassMapping)mapping).getListMappings();
if (listMappings == null)
{
return;
}
String tableName = mapping.getDBTables().iterator().next().getName();
String sql = "SELECT " + IMappingConstants.ATTRIBUTES_ID + ", " + IMappingConstants.ATTRIBUTES_VERSION + " FROM " + tableName;
ArrayList<Pair<Long, Integer>> idVersions = new ArrayList<Pair<Long, Integer>>();
ResultSet resultSet = getStatement().executeQuery(sql);
try
{
while (resultSet.next())
{
idVersions.add(Pair.create(resultSet.getLong(1), resultSet.getInt(2)));
}
}
finally
{
resultSet.close();
}
for (IListMapping listMapping : listMappings)
{
for (Pair<Long, Integer> idVersion : idVersions)
{
verifyCorrectIndices(listMapping, idVersion.getElement1(), idVersion.getElement2());
}
}
}
private void verifyCorrectIndices(IListMapping refMapping, long id, int version) throws Exception
{
String tableName = refMapping.getDBTables().iterator().next().getName();
TRACER.format("verifyUniqueIdVersion: {0} for ID{1}v{2} ...", tableName, id, version);
String sql = "SELECT " + IMappingConstants.LIST_IDX + " FROM " + tableName + " WHERE " + IMappingConstants.LIST_REVISION_ID + "=" + id + " AND "
+ IMappingConstants.LIST_REVISION_VERSION + "=" + version + " ORDER BY " + IMappingConstants.LIST_IDX;
TRACER.format(" Executing SQL: {0} ", sql);
ResultSet resultSet = getStatement().executeQuery(sql);
int indexShouldBe = 0;
try
{
while (resultSet.next())
{
Assert.assertEquals("Index " + indexShouldBe + " missing for ID" + id + "v" + version, indexShouldBe++, resultSet.getInt(1));
}
}
catch (AssertionFailedError e)
{
sqlDump("SELECT * FROM " + tableName + " WHERE " + IMappingConstants.LIST_REVISION_ID + "=" + id + " AND " + IMappingConstants.LIST_REVISION_VERSION
+ "=" + version + " ORDER BY " + IMappingConstants.LIST_IDX);
throw e;
}
finally
{
resultSet.close();
}
}
}
/**
* @author Stefan Winkler
*/
public static class NonAudit extends DBStoreVerifier
{
public NonAudit(IRepository repo)
{
super(repo);
// this is a verifier for non-auditing mode
Assert.assertEquals(true, getStore().getRevisionTemporality() == IStore.RevisionTemporality.NONE);
// ... and for horizontal class mapping
Assert.assertEquals(true, getStore().getMappingStrategy() instanceof HorizontalNonAuditMappingStrategy);
}
@Override
protected void doVerify() throws Exception
{
for (IClassMapping mapping : getClassMappings())
{
if (mapping != null && mapping.getDBTables().size() > 0)
{
verifyClassMapping(mapping);
}
}
}
private void verifyClassMapping(IClassMapping mapping) throws Exception
{
verifyNoUnrevisedRevisions(mapping);
verifyUniqueId(mapping);
verifyReferences(mapping);
}
/**
* Verify that there is no row with cdo_revised == 0.
*/
private void verifyNoUnrevisedRevisions(IClassMapping mapping) throws Exception
{
String tableName = mapping.getDBTables().iterator().next().getName();
String sql = "SELECT count(1) FROM " + tableName + " WHERE " + IMappingConstants.ATTRIBUTES_REVISED + " <> 0";
ResultSet resultSet = getStatement().executeQuery(sql);
try
{
Assert.assertEquals(true, resultSet.next());
Assert.assertEquals("Revised revision in table " + tableName, 0, resultSet.getInt(1));
}
finally
{
resultSet.close();
}
}
/**
* Verify that the id is unique.
*/
private void verifyUniqueId(IClassMapping mapping) throws Exception
{
String tableName = mapping.getDBTables().iterator().next().getName();
String sql = "SELECT " + IMappingConstants.ATTRIBUTES_ID + ", count(1) FROM " + tableName + " GROUP BY " + IMappingConstants.ATTRIBUTES_ID;
ResultSet resultSet = getStatement().executeQuery(sql);
try
{
while (resultSet.next())
{
Assert.assertEquals("Multiple rows for ID " + resultSet.getLong(1), 1, resultSet.getInt(2));
}
}
finally
{
resultSet.close();
}
}
private void verifyReferences(IClassMapping mapping) throws Exception
{
List<IListMapping> referenceMappings = ((HorizontalNonAuditClassMapping)mapping).getListMappings();
if (referenceMappings == null)
{
return;
}
String tableName = mapping.getDBTables().iterator().next().getName();
String sql = "SELECT " + IMappingConstants.ATTRIBUTES_ID + ", " + IMappingConstants.ATTRIBUTES_VERSION + " FROM " + tableName;
ArrayList<Pair<Long, Integer>> idVersions = new ArrayList<Pair<Long, Integer>>();
ResultSet resultSet = getStatement().executeQuery(sql);
try
{
while (resultSet.next())
{
idVersions.add(Pair.create(resultSet.getLong(1), resultSet.getInt(2)));
}
}
finally
{
resultSet.close();
}
for (IListMapping refMapping : referenceMappings)
{
for (Pair<Long, Integer> idVersion : idVersions)
{
verifyCorrectIndices(refMapping, idVersion.getElement1());
}
}
}
private void verifyCorrectIndices(IListMapping refMapping, long id) throws Exception
{
String tableName = refMapping.getDBTables().iterator().next().getName();
String sql = "SELECT " + IMappingConstants.LIST_IDX + " FROM " + tableName + " WHERE " + IMappingConstants.LIST_REVISION_ID + "=" + id + " ORDER BY "
+ IMappingConstants.LIST_IDX;
ResultSet resultSet = getStatement().executeQuery(sql);
int indexShouldBe = 0;
try
{
while (resultSet.next())
{
Assert.assertEquals("Index " + indexShouldBe + " missing for ID" + id, indexShouldBe++, resultSet.getInt(1));
}
}
finally
{
resultSet.close();
}
}
}
}