blob: c89e9d1acf71e4ff69bfd8f5f3e138689fc20c45 [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.compile.CreateIndexNode
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to you under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.apache.derby.impl.sql.compile;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.Limits;
import org.apache.derby.iapi.reference.Property;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.iapi.services.property.PropertyUtil;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
import org.apache.derby.iapi.sql.execute.ConstantAction;
import org.apache.derby.iapi.types.DataTypeDescriptor;
/**
* A CreateIndexNode is the root of a QueryTree that represents a CREATE INDEX
* statement.
*
*/
class CreateIndexNode extends DDLStatementNode
{
private boolean unique;
private Properties properties;
private String indexType;
private TableName indexName;
private TableName tableName;
private List<String> columnNameList;
private String[] columnNames;
private boolean[] isAscending;
private int[] boundColumnIDs;
private TableDescriptor td;
/**
* Constructor for a CreateIndexNode
*
* @param unique True means it's a unique index
* @param indexType The type of index
* @param indexName The name of the index
* @param tableName The name of the table the index will be on
* @param columnNameList A list of column names, in the order they
* appear in the index.
* @param properties The optional properties list associated with the index.
* @param cm Context manager
*
* @exception StandardException Thrown on error
*/
CreateIndexNode(boolean unique,
String indexType,
TableName indexName,
TableName tableName,
List<String> columnNameList,
Properties properties,
ContextManager cm) throws StandardException
{
super(indexName, cm);
this.unique = unique;
this.indexType = indexType;
this.indexName = indexName;
this.tableName = tableName;
this.columnNameList = columnNameList;
this.properties = properties;
}
/**
* Convert this object to a String. See comments in QueryTreeNode.java
* for how this should be done for tree printing.
*
* @return This object as a String
*/
@Override
public String toString()
{
if (SanityManager.DEBUG)
{
return super.toString() +
"unique: " + unique + "\n" +
"indexType: " + indexType + "\n" +
"indexName: " + indexName + "\n" +
"tableName: " + tableName + "\n" +
"properties: " + properties + "\n";
}
else
{
return "";
}
}
String statementToString()
{
return "CREATE INDEX";
}
// We inherit the generate() method from DDLStatementNode.
/**
* Bind this CreateIndexNode. This means doing any static error
* checking that can be done before actually creating the table.
* For example, verifying that the column name list does not
* contain any duplicate column names.
*
* @exception StandardException Thrown on error
*/
@Override
public void bindStatement() throws StandardException
{
int columnCount;
getSchemaDescriptor(); // want checking side-effects only
td = getTableDescriptor(tableName);
//throw an exception if user is attempting to create an index on a temporary table
if (td.getTableType() == TableDescriptor.GLOBAL_TEMPORARY_TABLE_TYPE)
{
throw StandardException.newException(SQLState.LANG_NOT_ALLOWED_FOR_DECLARED_GLOBAL_TEMP_TABLE);
}
//If total number of indexes on the table so far is more than 32767, then we need to throw an exception
if (td.getTotalNumberOfIndexes() > Limits.DB2_MAX_INDEXES_ON_TABLE)
{
throw StandardException.newException(SQLState.LANG_TOO_MANY_INDEXES_ON_TABLE,
String.valueOf(td.getTotalNumberOfIndexes()),
tableName,
String.valueOf(Limits.DB2_MAX_INDEXES_ON_TABLE));
}
/* Validate the column name list */
verifyAndGetUniqueNames();
columnCount = columnNames.length;
boundColumnIDs = new int[ columnCount ];
// Verify that the columns exist
for (int i = 0; i < columnCount; i++)
{
ColumnDescriptor columnDescriptor;
columnDescriptor = td.getColumnDescriptor(columnNames[i]);
if (columnDescriptor == null)
{
throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE,
columnNames[i],
tableName);
}
boundColumnIDs[ i ] = columnDescriptor.getPosition();
// Don't allow a column to be created on a non-orderable type
if ( ! columnDescriptor.getType().getTypeId().
orderable(getClassFactory()))
{
throw StandardException.newException(SQLState.LANG_COLUMN_NOT_ORDERABLE_DURING_EXECUTION,
columnDescriptor.getType().getTypeId().getSQLTypeName());
}
}
/* Check for number of key columns to be less than 16 to match DB2 */
if (columnCount > 16)
throw StandardException.newException(SQLState.LANG_TOO_MANY_INDEX_KEY_COLS);
/* See if the index already exists in this schema.
* NOTE: We still need to check at execution time
* since the index name is only unique to the schema,
* not the table.
*/
// if (dd.getConglomerateDescriptor(indexName.getTableName(), sd, false) != null)
// {
// throw StandardException.newException(SQLState.LANG_OBJECT_ALREADY_EXISTS_IN_OBJECT,
// "Index",
// indexName.getTableName(),
// "schema",
// sd.getSchemaName());
// }
/* Statement is dependent on the TableDescriptor */
getCompilerContext().createDependency(td);
}
/**
* Return true if the node references SESSION schema tables (temporary or permanent)
*
* @return true if references SESSION schema tables, else false
*
* @exception StandardException Thrown on error
*/
@Override
public boolean referencesSessionSchema()
throws StandardException
{
//If create index is on a SESSION schema table, then return true.
return isSessionSchema(td.getSchemaName());
}
/**
* Create the Constant information that will drive the guts of Execution.
*
* @exception StandardException Thrown on failure
*/
@Override
public ConstantAction makeConstantAction() throws StandardException
{
SchemaDescriptor sd = getSchemaDescriptor();
int columnCount = columnNames.length;
int approxLength = 0;
// bump the page size for the index,
// if the approximate sizes of the columns in the key are
// greater than the bump threshold.
// Ideally, we would want to have atleast 2 or 3 keys fit in one page
// With fix for beetle 5728, indexes on long types is not allowed
// so we do not have to consider key columns of long types
for (int i = 0; i < columnCount; i++)
{
ColumnDescriptor columnDescriptor = td.getColumnDescriptor(columnNames[i]);
DataTypeDescriptor dts = columnDescriptor.getType();
approxLength += dts.getTypeId().getApproximateLengthInBytes(dts);
}
if (approxLength > Property.IDX_PAGE_SIZE_BUMP_THRESHOLD)
{
if (((properties == null) ||
(properties.get(Property.PAGE_SIZE_PARAMETER) == null)) &&
(PropertyUtil.getServiceProperty(
getLanguageConnectionContext().getTransactionCompile(),
Property.PAGE_SIZE_PARAMETER) == null))
{
// do not override the user's choice of page size, whether it
// is set for the whole database or just set on this statement.
if (properties == null)
properties = new Properties();
properties.put(
Property.PAGE_SIZE_PARAMETER,
Property.PAGE_SIZE_DEFAULT_LONG);
}
}
return getGenericConstantActionFactory().getCreateIndexConstantAction(
false, // not for CREATE TABLE
unique,
false, // it's not a UniqueWithDuplicateNulls Index
false, // it's not a constraint, so its checking
// is not deferrable
false, // initiallyDeferred: N/A
-1, // constraintType: N/A
indexType,
sd.getSchemaName(),
indexName.getTableName(),
tableName.getTableName(),
td.getUUID(),
columnNames,
isAscending,
false,
null,
properties);
}
/**
* Check the uniqueness of the column names within the derived column list.
*
* @exception StandardException Thrown if column list contains a
* duplicate name.
*/
private void verifyAndGetUniqueNames()
throws StandardException
{
int size = columnNameList.size();
HashSet<String> seenNames = new HashSet<String>(size + 2, 0.999f);
columnNames = new String[size];
isAscending = new boolean[size];
for (int index = 0; index < size; index++)
{
/* Verify that this column's name is unique within the list
* Having a space at the end meaning descending on the column
*/
columnNames[index] = columnNameList.get(index);
if (columnNames[index].endsWith(" "))
{
columnNames[index] = columnNames[index].substring(0, columnNames[index].length() - 1);
isAscending[index] = false;
}
else
isAscending[index] = true;
boolean alreadySeen = !seenNames.add(columnNames[index]);
if (alreadySeen)
{
throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_NAME_CREATE_INDEX, columnNames[index]);
}
}
}
@Override
void acceptChildren(Visitor v) throws StandardException {
super.acceptChildren(v);
if (indexName != null) {
indexName = (TableName) indexName.accept(v);
}
if (tableName != null) {
tableName = (TableName) tableName.accept(v);
}
}
}