/**
* 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.it.dxl;
import com.foundationdb.ais.model.AISTableNameChanger;
import com.foundationdb.ais.model.AkibanInformationSchema;
import com.foundationdb.ais.model.Column;
import com.foundationdb.ais.model.IndexColumn;
import com.foundationdb.ais.model.Table;
import com.foundationdb.ais.model.TableIndex;
import com.foundationdb.ais.model.TableName;
import com.foundationdb.ais.util.TableChange;
import com.foundationdb.qp.operator.QueryContext;
import com.foundationdb.qp.row.Row;
import com.foundationdb.server.api.dml.scan.NewRow;
import com.foundationdb.server.test.it.ITBase;
import com.foundationdb.server.types.value.ValueSources;
import org.junit.After;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.foundationdb.ais.util.TableChangeValidator.ChangeLevel;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class AlterTableITBase extends ITBase {
protected static final String SCHEMA = "test";
protected static final String X_TABLE = "x";
protected static final String C_TABLE = "c";
protected static final String O_TABLE = "o";
protected static final String I_TABLE = "i";
protected static final TableName X_NAME = new TableName(SCHEMA, X_TABLE);
protected static final TableName C_NAME = new TableName(SCHEMA, C_TABLE);
protected static final TableName O_NAME = new TableName(SCHEMA, O_TABLE);
protected static final TableName I_NAME = new TableName(SCHEMA, I_TABLE);
protected static final List<TableChange> NO_CHANGES = Collections.emptyList();
protected Map<Integer,List<String>> checkedIndexes = new HashMap<>();
@After
public void lookForCheckedIndexes() {
for(Map.Entry<Integer, List<String>> entry : checkedIndexes.entrySet()) {
List<String> value = entry.getValue();
expectIndexes(entry.getKey(), value.toArray(new String[value.size()]));
}
checkedIndexes.clear();
}
// Added after bug1047977
@After
public void lookForDanglingStorage() throws Exception {
super.lookForDanglingStorage();
}
protected void checkIndexesInstead(TableName name, String... indexNames) {
checkedIndexes.put(tableId(name), Arrays.asList(indexNames));
}
protected QueryContext queryContext() {
return null; // Not needed
}
protected ChangeLevel getDefaultChangeLevel() {
return ChangeLevel.TABLE;
}
protected void runAlter(String sql) {
runAlter(getDefaultChangeLevel(), SCHEMA, sql);
}
protected void runAlter(ChangeLevel changeLevel, String sql) {
runAlter(changeLevel, SCHEMA, sql);
}
protected void runAlter(ChangeLevel expectedChangeLevel, TableName name, Table newDefinition,
List<TableChange> columnChanges, List<TableChange> indexChanges) {
ChangeLevel actual = ddlForAlter().alterTable(session(), name, newDefinition, columnChanges, indexChanges, queryContext());
assertEquals("ChangeLevel", expectedChangeLevel, actual);
}
protected void runRenameTable(TableName oldName, TableName newName) {
AkibanInformationSchema aisCopy = aisCloner().clone(ddl().getAIS(session()));
Table oldTable = aisCopy.getTable(oldName);
assertNotNull("Found old table " + oldName, oldTable);
AISTableNameChanger changer = new AISTableNameChanger(aisCopy.getTable(oldName), newName);
changer.doChange();
Table newTable = aisCopy.getTable(newName);
assertNotNull("Found new table " + newName, oldTable);
runAlter(ChangeLevel.METADATA, oldName, newTable, NO_CHANGES, NO_CHANGES);
}
protected void runRenameColumn(TableName tableName, String oldColName, String newColName) {
AkibanInformationSchema aisCopy = aisCloner().clone(ddl().getAIS(session()));
Table tableCopy = aisCopy.getTable(tableName);
assertNotNull("Found table " + tableName, tableCopy);
Column oldColumn = tableCopy.getColumn(oldColName);
assertNotNull("Found old column " + oldColName, oldColumn);
// Have to do this manually as parser doesn't support it, duplicates much of the work in AlterTableDDL
List<Column> columns = new ArrayList<>(tableCopy.getColumns());
tableCopy.dropColumns();
for(Column column : columns) {
Column.create(tableCopy, column, (column == oldColumn) ? newColName : null, null);
}
Column newColumn = tableCopy.getColumn(newColName);
assertNotNull("Found new column " + newColName, newColumn);
List<TableIndex> indexes = new ArrayList<>(tableCopy.getIndexes());
for(TableIndex index : indexes) {
if(index.containsTableColumn(tableName, oldColName)) {
tableCopy.removeIndexes(Collections.singleton(index));
if (index.getConstraintName() != null) {
aisCopy.removeConstraint(index.getConstraintName());
}
TableIndex indexCopy = TableIndex.create(tableCopy, index);
for(IndexColumn iCol : index.getKeyColumns()) {
IndexColumn.create(indexCopy, (iCol.getColumn() == oldColumn) ? newColumn : iCol.getColumn(), iCol, iCol.getPosition());
}
}
}
runAlter(ChangeLevel.METADATA,
tableName, tableCopy, Arrays.asList(TableChange.createModify(oldColName, newColName)), NO_CHANGES);
}
protected void createAndLoadCAOI_PK_FK(boolean cPK, boolean aPK, boolean aFK, boolean oPK, boolean oFK, boolean iPK, boolean iFK) {
throw new UnsupportedOperationException();
}
protected final void createAndLoadCAOI() {
createAndLoadCAOI_PK_FK(true, true, true, true, true, true, true);
}
protected final void createAndLoadCAOI_PK(boolean cPK, boolean aPK, boolean oPK, boolean iPK) {
createAndLoadCAOI_PK_FK(cPK, aPK, true, oPK, true, iPK, true);
}
protected final void createAndLoadCAOI_FK(boolean aFK, boolean oFK, boolean iFK) {
createAndLoadCAOI_PK_FK(true, true, aFK, true, oFK, true, iFK);
}
// Note: Does not handle null index contents, check manually in that case
private static class SingleColumnComparator implements Comparator<Row> {
private final int colPos;
SingleColumnComparator(int colPos) {
this.colPos = colPos;
}
@Override
@SuppressWarnings("unchecked")
public int compare(Row o1, Row o2) {
Object col1 = ValueSources.toObject(o1.value(colPos));
Object col2 = ValueSources.toObject(o2.value(colPos));
if(col1 == null && col2 == null) {
return 0;
}
if(col1 == null) {
return -1;
}
if(col2 == null) {
return +1;
}
return ((Comparable)col1).compareTo(col2);
}
}
private void checkIndexContents(int tableID) {
if(tableID == 0) {
return;
}
AkibanInformationSchema ais = ddl().getAIS(session());
Table table = ais.getTable(tableID);
List<Row> tableRows = new ArrayList<>(scanAll(tableID));
for(TableIndex index : table.getIndexesIncludingInternal()) {
if(index.getKeyColumns().size() == 1) {
int idxPos = 0;
int colPos = index.getKeyColumns().get(idxPos).getColumn().getPosition();
Collections.sort(tableRows, new SingleColumnComparator(colPos));
List<Row> indexRows = scanAllIndex(index);
if(tableRows.size() != indexRows.size()) {
assertEquals(index + " size does not match table size",
tableRows.toString(), indexRows.toString());
}
for(int i = 0; i < tableRows.size(); ++i) {
Object tableObj = ValueSources.toObject(tableRows.get(i).value(colPos));
Object indexObj = ValueSources.toObject(indexRows.get(i).value(idxPos));
assertEquals(index + " contents mismatch at row " + i,
tableObj, indexObj);
}
}
}
}
@After
public final void doCheckAllSingleColumnIndexes() {
for(Table table : ddl().getAIS(session()).getTables().values()) {
if(!TableName.INFORMATION_SCHEMA.equals(table.getName().getSchemaName())) {
checkIndexContents(table.getTableId());
}
}
}
}