/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.test.mt;
import com.foundationdb.ais.model.AkibanInformationSchema;
import com.foundationdb.qp.row.Row;
import com.foundationdb.server.error.ConcurrentViolationException;
import com.foundationdb.server.service.dxl.OnlineDDLMonitor;
import com.foundationdb.server.test.mt.util.*;
import com.foundationdb.server.test.mt.util.ThreadHelper.UncaughtHandler;
import com.foundationdb.sql.types.DataTypeDescriptor;
import java.util.List;
import static org.junit.Assert.assertEquals;
public abstract class OnlineMTBase extends MTBase
{
//
// Required from derived
//
protected abstract String getDDL();
protected abstract String getDDLSchema();
protected abstract List<Row> getGroupExpected();
protected abstract List<Row> getOtherExpected();
protected abstract OperatorCreator getGroupCreator();
protected abstract OperatorCreator getOtherCreator();
protected abstract void postCheckAIS(AkibanInformationSchema ais);
protected Class<? extends Exception> getFailingDMLExceptionClass() {
return store().getOnlineDMLFailureException();
}
protected Class<? extends Exception> getFailingDDLExceptionClass() {
return ConcurrentViolationException.class;
}
protected String getFailingDMLMarkString() {
return getFailingDMLExceptionClass().getSimpleName();
}
protected String getFailingDDLMarkString() {
return getFailingDDLExceptionClass().getSimpleName();
}
//
// Used by derived
//
/** As {@link #dmlPostMetaToPreFinal(OperatorCreator, List)} defaulting to expected failure. */
protected void dmlPreToPostMetadata(OperatorCreator dmlCreator) {
dmlPreToPostMetadata(dmlCreator, getGroupExpected(), true);
}
/** DML transaction starting prior to DDL METADATA and committing after DDL METADATA. */
protected void dmlPreToPostMetadata(OperatorCreator dmlCreator,
List<Row> expectedRows,
boolean isDMLFailing) {
dmlPreToPostMetadata_Check(dmlPreToPostMetadata_Build(dmlCreator, isDMLFailing, null, null, null), expectedRows, isDMLFailing);
}
protected void dmlPreToPostMetadata(OperatorCreator dmlCreator,
List<Row> expectedRows,
boolean isDMLFailing,
List<DataTypeDescriptor> descriptors,
List<String> columnNames,
OnlineCreateTableAsBase.TestSession server,
boolean skipInternalColumns) {
dmlPreToPostMetadata_Check(dmlPreToPostMetadata_Build(dmlCreator, isDMLFailing, descriptors, columnNames, server), expectedRows, isDMLFailing, skipInternalColumns);
}
/**This creates a ConcurrentTestBuidlerIMpl that each of these calls adds or modifies then the final call builds it into
* a monitor list
*/
protected List<MonitoredThread> dmlPreToPostMetadata_Build(OperatorCreator dmlCreator,
boolean isDMLFailing,
List<DataTypeDescriptor> descriptors,
List<String> columnNames,
OnlineCreateTableAsBase.TestSession server) {
return ConcurrentTestBuilderImpl
.create()
.add("DDL", getDDLSchema(), getDDL())
.sync("a", OnlineDDLMonitor.Stage.PRE_METADATA)
.sync("b", OnlineDDLMonitor.Stage.PRE_TRANSFORM)
.mark(OnlineDDLMonitor.Stage.PRE_METADATA, OnlineDDLMonitor.Stage.POST_METADATA)
.add("DML", dmlCreator)
.sync("a", ThreadMonitor.Stage.POST_BEGIN)
.sync("b", ThreadMonitor.Stage.PRE_SCAN)
.mark(ThreadMonitor.Stage.PRE_BEGIN, ThreadMonitor.Stage.PRE_COMMIT)
.rollbackRetry(!isDMLFailing)
.build(this, descriptors, columnNames, server);
}
protected void dmlPreToPostMetadata_Check(List<MonitoredThread> threads,
List<Row> expectedRows,
boolean isDMLFailing) {
dmlPreToPostMetadata_Check(threads, expectedRows, isDMLFailing, false);
}
protected void dmlPreToPostMetadata_Check(List<MonitoredThread> threads,
List<Row> expectedRows,
boolean isDMLFailing,
boolean skipInternalColumns) {
if(isDMLFailing) {
UncaughtHandler handler = ThreadHelper.startAndJoin(threads);
assertEquals("ddl failure", null, handler.thrown.get(threads.get(0)));
} else {
ThreadHelper.runAndCheck(threads);
}
new TimeMarkerComparison(threads).verify("DML:PRE_BEGIN",
"DDL:PRE_METADATA",
"DDL:POST_METADATA",
"DML:PRE_COMMIT",
isDMLFailing ? "DML:"+ getFailingDMLMarkString() : null);
assertEquals("DML row count", 1, threads.get(1).getScannedRows().size());
checkExpectedRows(expectedRows, skipInternalColumns);
}
/** As {@link #dmlPreToPostFinal(OperatorCreator, List, boolean)} with default expected pass. */
protected void dmlPostMetaToPreFinal(OperatorCreator dmlCreator,
List<Row> finalGroupRows) {
dmlPostMetaToPreFinal(dmlCreator, finalGroupRows, true, true);
}
/** As {@link #dmlPreToPostFinal(OperatorCreator, List, boolean)} with default expected DML pass, DDL fail. */
protected void dmlViolationPostMetaToPreFinal(OperatorCreator dmlCreator,
List<Row> finalGroupRows) {
dmlPostMetaToPreFinal(dmlCreator, finalGroupRows, true, false);
}
/** DML transaction starting after DDL METADATA and committing prior DDL FINAL. */
protected void dmlPostMetaToPreFinal(OperatorCreator dmlCreator,
List<Row> finalGroupRows,
boolean isDMLPassing,
boolean isDDLPassing) {
dmlPostMetaToPreFinal(dmlCreator, finalGroupRows, isDMLPassing, isDDLPassing, null, null, null, false);
}
/** DML transaction starting after DDL METADATA and committing prior DDL FINAL. */
protected void dmlPostMetaToPreFinal(OperatorCreator dmlCreator,
List<Row> finalGroupRows,
boolean isDMLPassing,
boolean isDDLPassing,
List<DataTypeDescriptor> descriptors,
List<String> columnNames,
OnlineCreateTableAsBase.TestSession server,
boolean skipInternalColumns){
// In the interest of determinism, DDL transform runs completely *before* DML starts.
// The opposite ordering would fail the DDL directly instead (e.g. NotNullViolation vs ConcurrentViolation).
ConcurrentTestBuilder builder = ConcurrentTestBuilderImpl
.create()
.add("DDL", getDDLSchema(), getDDL())
.sync("a", OnlineDDLMonitor.Stage.POST_METADATA)
.sync("b", OnlineDDLMonitor.Stage.POST_TRANSFORM)
.sync("c", OnlineDDLMonitor.Stage.PRE_FINAL)
.mark(OnlineDDLMonitor.Stage.POST_METADATA, OnlineDDLMonitor.Stage.PRE_FINAL)
.add("DML", dmlCreator)
.sync("a", ThreadMonitor.Stage.START)
.sync("b", ThreadMonitor.Stage.PRE_BEGIN)
.sync("c", ThreadMonitor.Stage.FINISH)
.mark(ThreadMonitor.Stage.PRE_BEGIN, ThreadMonitor.Stage.POST_COMMIT)
.rollbackRetry(isDMLPassing);
final List<MonitoredThread> threads = builder.build(this, descriptors, columnNames, server);
ThreadHelper.startAndJoin(threads);
new TimeMarkerComparison(threads).verify("DDL:POST_METADATA",
"DML:PRE_BEGIN",
"DML:" + (isDMLPassing ? "POST_COMMIT" : getFailingDMLMarkString()),
"DDL:PRE_FINAL",
isDDLPassing ? null : "DDL:" + getFailingDDLMarkString());
if(isDMLPassing) {
assertEquals("DML row count", 1, threads.get(1).getScannedRows().size());
}
checkExpectedRows(finalGroupRows, skipInternalColumns);
}
/** As {@link #dmlPreToPostFinal(OperatorCreator, List, boolean)} with default expected failure. */
protected void dmlPreToPostFinal(OperatorCreator dmlCreator) {
dmlPreToPostFinal(dmlCreator, getGroupExpected(), true);
}
protected void dmlPreToPostFinal(OperatorCreator dmlCreator,
List<Row> expectedRows,
boolean isDMLFailing){
dmlPreToPostFinal(dmlCreator, expectedRows, isDMLFailing, null, null, null, false);
}
/** DML transaction starting prior to DDL FINAL and committing after DDL FINAL. */
protected void dmlPreToPostFinal(OperatorCreator dmlCreator,
List<Row> expectedRows,
boolean isDMLFailing,
List<DataTypeDescriptor> descriptors,
List<String> columnNames,
OnlineCreateTableAsBase.TestSession server,
boolean skipInternalColumns) {
List<MonitoredThread> threads = ConcurrentTestBuilderImpl
.create()
.add("DDL", getDDLSchema(), getDDL())
.sync("a", OnlineDDLMonitor.Stage.POST_TRANSFORM)
.sync("b", ThreadMonitor.Stage.FINISH)
.mark(OnlineDDLMonitor.Stage.PRE_FINAL, OnlineDDLMonitor.Stage.POST_FINAL)
.add("DML", dmlCreator)
.sync("a", ThreadMonitor.Stage.PRE_SCAN)
.sync("b", ThreadMonitor.Stage.POST_SCAN)
.mark(ThreadMonitor.Stage.POST_BEGIN, ThreadMonitor.Stage.PRE_COMMIT)
.rollbackRetry(!isDMLFailing)
.build(this, descriptors, columnNames, server);
if(isDMLFailing) {
UncaughtHandler handler = ThreadHelper.startAndJoin(threads);
assertEquals("ddl failure", null, handler.thrown.get(threads.get(0)));
} else {
ThreadHelper.runAndCheck(threads);
}
new TimeMarkerComparison(threads).verify("DML:POST_BEGIN",
"DDL:PRE_FINAL",
"DDL:POST_FINAL",
"DML:PRE_COMMIT",
isDMLFailing ? "DML:"+getFailingDMLMarkString() : null);
assertEquals("DML row count", 1, threads.get(1).getScannedRows().size());
checkExpectedRows(expectedRows, skipInternalColumns);
}
protected void checkExpectedRows(List<Row> expectedRows,
boolean skipInternalColumns) {
checkExpectedRows(expectedRows, getGroupCreator(), skipInternalColumns);
}
protected void checkExpectedRows(List<Row> expectedRows,
OperatorCreator groupCreator) {
checkExpectedRows(expectedRows, groupCreator, false);
}
protected void checkExpectedRows(List<Row> expectedRows,
OperatorCreator groupCreator,
boolean skipInternalColumns) {
compareRows(expectedRows, runPlanTxn(groupCreator));
postCheckAIS(ais());
List<Row> otherExpected = getOtherExpected();
if(otherExpected != null) {
compareRows(otherExpected, runPlanTxn(getOtherCreator()), skipInternalColumns);
}
}
}