/**
* 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.sql.aisddl;
import java.util.Collections;
import java.util.List;
import com.foundationdb.ais.AISCloner;
import com.foundationdb.ais.model.*;
import com.foundationdb.ais.model.TableName;
import com.foundationdb.ais.protobuf.ProtobufWriter;
import com.foundationdb.server.error.*;
import com.foundationdb.sql.parser.*;
import com.foundationdb.sql.parser.IndexColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.foundationdb.server.api.DDLFunctions;
import com.foundationdb.server.service.session.Session;
import com.foundationdb.qp.operator.QueryContext;
import static com.foundationdb.sql.aisddl.DDLHelper.skipOrThrow;
/** DDL operations on Indices */
public class IndexDDL
{
private static final Logger logger = LoggerFactory.getLogger(IndexDDL.class);
private IndexDDL() {
}
public static void dropIndex (DDLFunctions ddlFunctions,
Session session,
String defaultSchemaName,
DropIndexNode dropIndex,
QueryContext context) {
TableName groupName = null;
TableName tableName = null;
final String indexSchemaName = dropIndex.getObjectName() != null && dropIndex.getObjectName().getSchemaName() != null ?
dropIndex.getObjectName().getSchemaName() :
null;
final String indexTableName = dropIndex.getObjectName() != null && dropIndex.getObjectName().getTableName() != null ?
dropIndex.getObjectName().getTableName() :
null;
final String indexName = dropIndex.getIndexName();
List<String> indexesToDrop = Collections.singletonList(indexName);
// if the user supplies the table for the index, look only there
if (indexTableName != null) {
tableName = TableName.create(indexSchemaName == null ? defaultSchemaName : indexSchemaName, indexTableName);
Table table = ddlFunctions.getAIS(session).getTable(tableName);
if((table == null) &&
skipOrThrow(context, dropIndex.getExistenceCheck(), table, new NoSuchTableException(tableName))) {
return;
}
// if we can't find the index, set tableName to null
// to flag not a user table index.
if (table.getIndex(indexName) == null && table.getFullTextIndex(indexName) == null) {
tableName = null;
}
// Check the group associated to the table for the
// same index name.
Group group = table.getGroup();
if (group.getIndex(indexName) != null) {
// Table and it's group share an index name, we're confused.
if (tableName != null) {
throw new IndistinguishableIndexException(indexName);
}
// else flag group index for dropping
groupName = group.getName();
}
}
// the user has not supplied a table name for the index to drop,
// scan all groups/tables for the index name
else {
for (Table table : ddlFunctions.getAIS(session).getTables().values()) {
if (indexSchemaName != null && !table.getName().getSchemaName().equalsIgnoreCase(indexSchemaName)) {
continue;
}
if (table.getIndex(indexName) != null) {
if (tableName == null) {
tableName = table.getName();
} else {
throw new IndistinguishableIndexException(indexName);
}
}
}
for (Group table : ddlFunctions.getAIS(session).getGroups().values()) {
if (table.getIndex(indexName) != null) {
if (tableName == null && groupName == null) {
groupName = table.getName();
} else {
throw new IndistinguishableIndexException(indexName);
}
}
}
}
if (groupName != null) {
ddlFunctions.dropGroupIndexes(session, groupName, indexesToDrop);
} else if (tableName != null) {
ddlFunctions.dropTableIndexes(session, tableName, indexesToDrop);
} else {
skipOrThrow(context, dropIndex.getExistenceCheck(), null, new NoSuchIndexException(indexName));
}
}
public static void renameIndex (DDLFunctions ddlFunctions,
Session session,
String defaultSchemaName,
RenameNode renameIndex) {
throw new UnsupportedSQLException (renameIndex.statementToString(), renameIndex);
}
public static void createIndex(DDLFunctions ddlFunctions,
Session session,
String defaultSchemaName,
CreateIndexNode createIndex,
QueryContext context) {
AkibanInformationSchema ais = ddlFunctions.getAIS(session);
Index index = buildIndex(ddlFunctions, ais, defaultSchemaName, createIndex, context);
if(index != null) {
ddlFunctions.createIndexes(session, Collections.singleton(index));
}
}
protected static Index buildIndex (DDLFunctions ddlFunctions,
AkibanInformationSchema ais,
String defaultSchemaName,
CreateIndexNode createIndex,
QueryContext context) {
final String schemaName = createIndex.getObjectName().getSchemaName() != null ? createIndex.getObjectName().getSchemaName() : defaultSchemaName;
final String indexName = createIndex.getObjectName().getTableName();
final TableName tableName = TableName.create(schemaName, createIndex.getIndexTableName().getTableName());
if (ais.getTable(tableName) == null) {
throw new NoSuchTableException (tableName);
}
AISBuilder builder = new AISBuilder();
clone(ddlFunctions.getAISCloner(), builder, ais);
builder.getNameGenerator().mergeAIS(ais);
Index index;
TableName constraintName = null;
if (createIndex.isUnique()) {
constraintName = builder.getNameGenerator().generateUniqueConstraintName(schemaName, indexName);
}
IndexColumnList indexColumnList = createIndex.getIndexColumnList();
String functionName = indexColumnList.functionName();
checkFunctionNameValidity(functionName);
if (isFullText(functionName)) {
logger.debug ("Building Full text index on table {}", tableName) ;
index = buildFullTextIndex(builder, tableName, indexName, createIndex, createIndex
.getExistenceCheck(), context);
} else if (checkIndexType (createIndex, tableName) == Index.IndexType.TABLE) {
logger.debug ("Building Table index on table {}", tableName) ;
index = buildTableIndex(builder, tableName, indexName, createIndex, constraintName, createIndex
.getExistenceCheck(), context);
} else {
logger.debug ("Building Group index on table {}", tableName);
index = buildGroupIndex (builder, tableName, indexName, createIndex, createIndex.getExistenceCheck(), context);
}
if(index != null) {
boolean indexIsSpatial = isSpatial(functionName);
// Can't check isSpatialCompatible before the index columns have been added.
if(indexIsSpatial && !Index.isSpatialCompatible(index)) {
throw new BadSpatialIndexException(index.getIndexName().getTableName(), createIndex);
}
builder.basicSchemaIsComplete();
if(createIndex.getStorageFormat() != null) {
TableDDL.setStorage(ddlFunctions, index, createIndex.getStorageFormat());
}
}
return index;
}
/**
* Check if the index specification is for a table index or a group index. We distinguish between
* them by checking the columns specified in the index, if they all belong to the table
* in the "ON" clause, this is a table index, else it is a group index.
* @param index AST for index to check
* @param tableName ON clause table name
* @return IndexType (GROUP or TABLE).
*/
protected static Index.IndexType checkIndexType(IndexDefinition index, TableName tableName) {
for (IndexColumn col : index.getIndexColumnList()) {
// if the column has no table name (e.g. "col1") OR
// there is a table name (e.g. "schema1.table1.col1" or "table1.col1")
// if the table name has a schema it matches, else assume it's the same as the table
// and the table name match the index table.
if (col.getTableName() == null ||
((col.getTableName().hasSchema() &&
col.getTableName().getSchemaName().equalsIgnoreCase(tableName.getSchemaName()) ||
!col.getTableName().hasSchema()) &&
col.getTableName().getTableName().equalsIgnoreCase(tableName.getTableName()))){
; // Column is in the base table, check the next
} else {
return Index.IndexType.GROUP;
}
}
return Index.IndexType.TABLE;
}
protected static Index buildTableIndex (AISBuilder builder,
TableName tableName,
String indexName,
IndexDefinition index,
TableName constraintName,
ExistenceCheck existenceCheck,
QueryContext context) {
if (index.getJoinType() != null) {
throw new TableIndexJoinTypeException();
}
Table curTable = builder.akibanInformationSchema().getTable(tableName);
Index curIndex = curTable.getIndex(indexName);
if(curIndex == null) {
curIndex = curTable.getFullTextIndex(indexName);
}
if((curIndex != null) &&
skipOrThrow(context, existenceCheck, curIndex, new DuplicateIndexException(tableName, indexName))) {
return null;
}
builder.index(tableName.getSchemaName(), tableName.getTableName(), indexName, index.isUnique(),
false, constraintName);
TableIndex tableIndex = builder.akibanInformationSchema().getTable(tableName).getIndex(indexName);
IndexColumnList indexColumns = index.getIndexColumnList();
String functionName = indexColumns.functionName();
if (isSpatial(functionName)) {
tableIndex.markSpatial(indexColumns.firstFunctionArg(),
indexColumns.lastFunctionArg() + 1 - indexColumns.firstFunctionArg(),
Index.IndexMethod.valueOf(functionName.trim().toUpperCase()));
}
int i = 0;
for (IndexColumn col : indexColumns) {
Column tableCol = builder.akibanInformationSchema().getTable(tableName).getColumn(col.getColumnName());
if (tableCol == null) {
throw new NoSuchColumnException (col.getColumnName());
}
checkColAscending(col);
builder.indexColumn(tableName.getSchemaName(),
tableName.getTableName(),
indexName,
tableCol.getName(),
i,
col.isAscending(),
null);
i++;
}
return tableIndex;
}
protected static Index buildGroupIndex (AISBuilder builder,
TableName tableName,
String indexName,
IndexDefinition index,
ExistenceCheck existenceCheck,
QueryContext context) {
final TableName groupName = builder.akibanInformationSchema().getTable(tableName).getGroup().getName();
Group curGroup = builder.akibanInformationSchema().getGroup(groupName);
if (curGroup == null) {
throw new NoSuchGroupException(groupName);
}
if (index.isUnique()) {
throw new UnsupportedUniqueGroupIndexException (indexName);
}
Index curIndex = curGroup.getIndex(indexName);
if((curIndex != null) &&
skipOrThrow(context, existenceCheck, curIndex, new DuplicateIndexException(tableName, indexName))) {
return null;
}
final Index.JoinType joinType;
if (index.getJoinType() == null) {
throw new MissingGroupIndexJoinTypeException();
}
else {
switch (index.getJoinType()) {
case LEFT_OUTER:
joinType = Index.JoinType.LEFT;
break;
case RIGHT_OUTER:
joinType = Index.JoinType.RIGHT;
break;
case INNER:
// Fall through as unsupported
default:
throw new UnsupportedGroupIndexJoinTypeException(index.getJoinType().toString());
}
}
builder.groupIndex(groupName, indexName, index.isUnique(), joinType);
GroupIndex groupIndex = builder.akibanInformationSchema().getGroup(groupName).getIndex(indexName);
IndexColumnList indexColumns = index.getIndexColumnList();
boolean indexIsSpatial = isSpatial(indexColumns.functionName());
if (indexIsSpatial) {
groupIndex.markSpatial(indexColumns.firstFunctionArg(),
indexColumns.lastFunctionArg() + 1 - indexColumns.firstFunctionArg(),
Index.IndexMethod.valueOf(indexColumns.functionName().trim().toUpperCase()));
}
int i = 0;
String schemaName;
TableName columnTable;
for (IndexColumn col : index.getIndexColumnList()) {
if (col.getTableName() != null) {
schemaName = col.getTableName().hasSchema() ? col.getTableName().getSchemaName() : tableName.getSchemaName();
columnTable = TableName.create(schemaName, col.getTableName().getTableName());
} else {
columnTable = tableName;
schemaName = tableName.getSchemaName();
}
final String columnName = col.getColumnName();
if (builder.akibanInformationSchema().getTable(columnTable) == null) {
throw new NoSuchTableException(columnTable);
}
if (builder.akibanInformationSchema().getTable(columnTable).getGroup().getName() != groupName)
throw new IndexTableNotInGroupException(indexName, columnName, columnTable.getTableName());
Column tableCol = builder.akibanInformationSchema().getTable(columnTable).getColumn(columnName);
if (tableCol == null) {
throw new NoSuchColumnException (col.getColumnName());
}
checkColAscending(col);
builder.groupIndexColumn(groupName, indexName, schemaName, columnTable.getTableName(), columnName, i);
i++;
}
return builder.akibanInformationSchema().getGroup(groupName).getIndex(indexName);
}
protected static Index buildFullTextIndex (AISBuilder builder,
TableName tableName,
String indexName,
IndexDefinition index,
ExistenceCheck existenceCheck,
QueryContext context) {
Table table = builder.akibanInformationSchema().getTable(tableName);
Index curIndex = table.getIndex(indexName);
if(curIndex == null) {
curIndex = table.getFullTextIndex(indexName);
}
if((curIndex != null) &&
skipOrThrow(context, existenceCheck, curIndex, new DuplicateIndexException(tableName, indexName))) {
return null;
}
if (index.getJoinType() != null) {
throw new TableIndexJoinTypeException();
}
builder.fullTextIndex(tableName, indexName);
int i = 0;
String schemaName;
TableName columnTable;
for (IndexColumn col : index.getIndexColumnList()) {
if (col.getTableName() != null) {
schemaName = col.getTableName().hasSchema() ? col.getTableName().getSchemaName() : tableName.getSchemaName();
columnTable = TableName.create(schemaName, col.getTableName().getTableName());
} else {
columnTable = tableName;
schemaName = tableName.getSchemaName();
}
final String columnName = col.getColumnName();
if (builder.akibanInformationSchema().getTable(columnTable) == null) {
throw new NoSuchTableException(columnTable);
}
if (builder.akibanInformationSchema().getTable(columnTable).getGroup() != table.getGroup())
throw new IndexTableNotInGroupException(indexName, columnName, columnTable.getTableName());
Column tableCol = builder.akibanInformationSchema().getTable(columnTable).getColumn(columnName);
if (tableCol == null) {
throw new NoSuchColumnException (col.getColumnName());
}
checkColAscending(col);
builder.fullTextIndexColumn(tableName, indexName, schemaName, columnTable.getTableName(), columnName, i);
i++;
}
return builder.akibanInformationSchema().getTable(tableName).getFullTextIndex(indexName);
}
static boolean isSpatial(String functionName)
{
return functionName != null &&
(functionName.equalsIgnoreCase(Index.IndexMethod.GEO_LAT_LON.name()) ||
functionName.equalsIgnoreCase(Index.IndexMethod.GEO_WKB.name()) ||
functionName.equalsIgnoreCase(Index.IndexMethod.GEO_WKT.name()));
}
static boolean isFullText(String functionName)
{
return functionName != null && functionName.equalsIgnoreCase(Index.IndexMethod.FULL_TEXT.name());
}
static void checkFunctionNameValidity(String functionName)
{
if (functionName != null && !isSpatial(functionName) && !isFullText(functionName)) {
throw new UnsupportedFunctionInIndexException(functionName);
}
}
private static void clone(AISCloner aisCloner, AISBuilder builder, AkibanInformationSchema ais) {
aisCloner.clone(builder.akibanInformationSchema(), ais, ProtobufWriter.ALL_SELECTOR);
}
private static void checkColAscending(IndexColumn indexColumn) {
if(!indexColumn.isAscending()) {
throw new UnsupportedSQLException("DESC index column " + indexColumn.getColumnName());
}
}
}