blob: d640d1ac817bdf3456d926664c231343db82af9e [file] [log] [blame]
/*
* 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.phoenix.compile;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellComparator;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.exception.SQLExceptionInfo;
import org.apache.phoenix.execute.MutationState;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.LiteralExpression;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.jdbc.PhoenixStatement;
import org.apache.phoenix.jdbc.PhoenixStatement.Operation;
import org.apache.phoenix.parse.CreateIndexStatement;
import org.apache.phoenix.parse.ParseNode;
import org.apache.phoenix.parse.StatelessTraverseAllParseNodeVisitor;
import org.apache.phoenix.parse.SubqueryParseNode;
import org.apache.phoenix.parse.TableName;
import org.apache.phoenix.schema.MetaDataClient;
import org.apache.phoenix.schema.PColumn;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTable.IndexType;
import org.apache.phoenix.schema.tuple.MultiKeyValueTuple;
import org.apache.phoenix.schema.types.PArrayDataType;
import org.apache.phoenix.schema.types.PBoolean;
import org.apache.phoenix.schema.types.PChar;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.schema.types.PDate;
import org.apache.phoenix.schema.types.PDateArray;
import org.apache.phoenix.schema.types.PJson;
import org.apache.phoenix.schema.types.PNumericType;
import org.apache.phoenix.schema.types.PTime;
import org.apache.phoenix.schema.types.PTimeArray;
import org.apache.phoenix.schema.types.PTimestamp;
import org.apache.phoenix.schema.types.PTimestampArray;
import org.apache.phoenix.schema.types.PUnsignedDate;
import org.apache.phoenix.schema.types.PUnsignedDateArray;
import org.apache.phoenix.schema.types.PUnsignedTime;
import org.apache.phoenix.schema.types.PUnsignedTimeArray;
import org.apache.phoenix.schema.types.PUnsignedTimestamp;
import org.apache.phoenix.schema.types.PUnsignedTimestampArray;
import org.apache.phoenix.schema.types.PVarchar;
import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.EnvironmentEdgeManager;
import org.apache.phoenix.util.PhoenixRuntime;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class CreateIndexCompiler {
private final PhoenixStatement statement;
private final Operation operation;
public CreateIndexCompiler(PhoenixStatement statement, Operation operation) {
this.statement = statement;
this.operation = operation;
}
/**
* This is to check if the index where clause has a subquery
*/
private static class IndexWhereParseNodeVisitor extends StatelessTraverseAllParseNodeVisitor {
private boolean hasSubquery = false;
@Override
public Void visit(SubqueryParseNode node) throws SQLException {
hasSubquery = true;
return null;
}
}
private String getValue(PDataType type) {
if (type instanceof PNumericType) {
return "0";
} else if (type instanceof PChar || type instanceof PVarchar) {
return "'a'";
} else if (type instanceof PDate || type instanceof PUnsignedDate || type instanceof PTime
|| type instanceof PUnsignedTime || type instanceof PTimestamp
|| type instanceof PUnsignedTimestamp) {
Timestamp now = new Timestamp(EnvironmentEdgeManager.currentTimeMillis());
return "TO_DATE('" + now + "','yyyy-MM-dd HH:mm:ss.SSS', 'PST')";
} else if (type instanceof PBoolean) {
return "TRUE";
} else if (type instanceof PDateArray || type instanceof PUnsignedDateArray
|| type instanceof PTimeArray || type instanceof PUnsignedTimeArray
|| type instanceof PTimestampArray || type instanceof PUnsignedTimestampArray) {
return "ARRAY[" + getValue(PDate.INSTANCE) + "]";
} else if (type instanceof PArrayDataType) {
return "ARRAY" + type.getSampleValue().toString();
} else if (type instanceof PJson) {
return "'{a:1}'";
} else {
return "0123";
}
}
/**
* Verifies that index WHERE clause does not include a subquery and it can
* be evaluated on a single data table row.
*
*/
private void verifyIndexWhere(ParseNode indexWhere, StatementContext context,
TableName dataTableName) throws SQLException {
if (indexWhere == null) {
return;
}
// Verify that index WHERE clause does not include a subquery
PhoenixConnection connection = context.getConnection();
IndexWhereParseNodeVisitor indexWhereParseNodeVisitor = new IndexWhereParseNodeVisitor();
indexWhere.accept(indexWhereParseNodeVisitor);
if (indexWhereParseNodeVisitor.hasSubquery) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.INVALID_INDEX_WHERE_WITH_SUBQUERY).
build().buildException();
}
// Verify that index WHERE clause can be evaluated on a single data table row
// Convert the index WHERE ParseNode to an Expression
ExpressionCompiler expressionCompiler = new ExpressionCompiler(context);
Expression indexWhereExpression = indexWhere.accept(expressionCompiler);
PTable dataTable = connection.getTable(dataTableName.toString());
// Create a full data table row. Skip generating values for view constants as they
// will be generated by the Upsert compiler
boolean autoCommit = connection.getAutoCommit();
connection.setAutoCommit(false);
StringBuilder stringBuilder = new StringBuilder("UPSERT INTO ");
stringBuilder.append(dataTableName);
int startingColumnIndex = dataTable.getBucketNum() != null ? 1 : 0;
startingColumnIndex += dataTable.getTenantId() != null ? 1 : 0;
stringBuilder.append(" (");
PColumn column;
byte[] value;
int i = startingColumnIndex;
for (; i < dataTable.getColumns().size() - 1; i++) {
column = dataTable.getColumns().get(i);
value = column.getViewConstant();
if (value == null) {
stringBuilder.append(column.getName().getString() + ",");
}
}
column = dataTable.getColumns().get(i);
value = column.getViewConstant();
if (value == null) {
stringBuilder.append(column.getName().getString() + ")");
} else {
stringBuilder.append(")");
}
stringBuilder.append(" Values(");
i = startingColumnIndex;
for (; i < dataTable.getColumns().size() - 1; i++) {
column = dataTable.getColumns().get(i);
value = column.getViewConstant();
if (value == null) {
PDataType dataType = column.getDataType();
stringBuilder.append(getValue(dataType) + ",");
}
}
column = dataTable.getColumns().get(i);
value = column.getViewConstant();
if (value == null) {
PDataType dataType = column.getDataType();
stringBuilder.append(getValue(dataType) + ")");
} else {
stringBuilder.append(")");
}
try (PreparedStatement ps =
context.getConnection().prepareStatement(stringBuilder.toString())) {
ps.execute();
Iterator<Pair<byte[], List<Cell>>> dataTableNameAndMutationIterator =
PhoenixRuntime.getUncommittedDataIterator(connection);
Pair<byte[], List<Cell>> dataTableNameAndMutation = null;
while (dataTableNameAndMutationIterator.hasNext()) {
dataTableNameAndMutation = dataTableNameAndMutationIterator.next();
if (java.util.Arrays.equals(dataTableNameAndMutation.getFirst(),
dataTable.getPhysicalName().getBytes())) {
break;
} else {
dataTableNameAndMutation = null;
}
}
if (dataTableNameAndMutation == null) {
throw new RuntimeException(
"Unexpected result from " + "PhoenixRuntime#getUncommittedDataIterator for "
+ dataTableName);
}
// Evaluate the WHERE expression on the data table row
ImmutableBytesWritable ptr = new ImmutableBytesWritable();
ptr.set(ByteUtil.EMPTY_BYTE_ARRAY);
List<Cell> cols = dataTableNameAndMutation.getSecond();
Collections.sort(cols, CellComparator.getInstance());
MultiKeyValueTuple tuple = new MultiKeyValueTuple(cols);
if (!indexWhereExpression.evaluate(tuple, ptr)) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_EVALUATE_INDEX_WHERE).
build().buildException();
}
} finally {
connection.setAutoCommit(autoCommit);
}
}
public MutationPlan compile(final CreateIndexStatement create) throws SQLException {
final PhoenixConnection connection = statement.getConnection();
final ColumnResolver resolver = FromCompiler.getResolver(create, connection, create.getUdfParseNodes());
Scan scan = new Scan();
final StatementContext context = new StatementContext(statement, resolver, scan, new SequenceManager(statement));
verifyIndexWhere(create.getWhere(), context, create.getTable().getName());
ExpressionCompiler expressionCompiler = new ExpressionCompiler(context);
List<ParseNode> splitNodes = create.getSplitNodes();
if (create.getIndexType() == IndexType.LOCAL) {
if (!splitNodes.isEmpty()) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_SPLIT_LOCAL_INDEX)
.build().buildException();
}
List<Pair<String, Object>> list = create.getProps() != null ? create.getProps().get("") : null;
if (list != null) {
for (Pair<String, Object> pair : list) {
if (pair.getFirst().equals(PhoenixDatabaseMetaData.SALT_BUCKETS)) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_SALT_LOCAL_INDEX)
.build().buildException();
}
}
}
}
final byte[][] splits = new byte[splitNodes.size()][];
for (int i = 0; i < splits.length; i++) {
ParseNode node = splitNodes.get(i);
if (!node.isStateless()) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.SPLIT_POINT_NOT_CONSTANT)
.setMessage("Node: " + node).build().buildException();
}
LiteralExpression expression = (LiteralExpression)node.accept(expressionCompiler);
splits[i] = expression.getBytes();
}
final MetaDataClient client = new MetaDataClient(connection);
return new BaseMutationPlan(context, operation) {
@Override
public MutationState execute() throws SQLException {
return client.createIndex(create, splits);
}
@Override
public ExplainPlan getExplainPlan() throws SQLException {
return new ExplainPlan(Collections.singletonList("CREATE INDEX"));
}
};
}
}