/*******************************************************************************
* Copyright (c) 2013, Salesforce.com, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of Salesforce.com nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package com.salesforce.phoenix.jdbc;
import java.io.IOException;
import java.io.Reader;
import java.sql.ParameterMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.hbase.util.Pair;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.salesforce.phoenix.compile.ColumnProjector;
import com.salesforce.phoenix.compile.CreateIndexCompiler;
import com.salesforce.phoenix.compile.CreateSequenceCompiler;
import com.salesforce.phoenix.compile.CreateTableCompiler;
import com.salesforce.phoenix.compile.DeleteCompiler;
import com.salesforce.phoenix.compile.DropSequenceCompiler;
import com.salesforce.phoenix.compile.ExplainPlan;
import com.salesforce.phoenix.compile.ExpressionProjector;
import com.salesforce.phoenix.compile.MutationPlan;
import com.salesforce.phoenix.compile.QueryCompiler;
import com.salesforce.phoenix.compile.QueryPlan;
import com.salesforce.phoenix.compile.RowProjector;
import com.salesforce.phoenix.compile.StatementPlan;
import com.salesforce.phoenix.compile.UpsertCompiler;
import com.salesforce.phoenix.coprocessor.MetaDataProtocol;
import com.salesforce.phoenix.exception.SQLExceptionCode;
import com.salesforce.phoenix.exception.SQLExceptionInfo;
import com.salesforce.phoenix.execute.MutationState;
import com.salesforce.phoenix.expression.RowKeyColumnExpression;
import com.salesforce.phoenix.iterate.MaterializedResultIterator;
import com.salesforce.phoenix.iterate.ResultIterator;
import com.salesforce.phoenix.parse.AddColumnStatement;
import com.salesforce.phoenix.parse.AliasedNode;
import com.salesforce.phoenix.parse.AlterIndexStatement;
import com.salesforce.phoenix.parse.BindableStatement;
import com.salesforce.phoenix.parse.ColumnDef;
import com.salesforce.phoenix.parse.ColumnName;
import com.salesforce.phoenix.parse.CreateIndexStatement;
import com.salesforce.phoenix.parse.CreateSequenceStatement;
import com.salesforce.phoenix.parse.CreateTableStatement;
import com.salesforce.phoenix.parse.DeleteStatement;
import com.salesforce.phoenix.parse.DropColumnStatement;
import com.salesforce.phoenix.parse.DropIndexStatement;
import com.salesforce.phoenix.parse.DropSequenceStatement;
import com.salesforce.phoenix.parse.DropTableStatement;
import com.salesforce.phoenix.parse.ExplainStatement;
import com.salesforce.phoenix.parse.HintNode;
import com.salesforce.phoenix.parse.LimitNode;
import com.salesforce.phoenix.parse.NamedNode;
import com.salesforce.phoenix.parse.NamedTableNode;
import com.salesforce.phoenix.parse.OrderByNode;
import com.salesforce.phoenix.parse.ParseNode;
import com.salesforce.phoenix.parse.ParseNodeFactory;
import com.salesforce.phoenix.parse.PrimaryKeyConstraint;
import com.salesforce.phoenix.parse.SQLParser;
import com.salesforce.phoenix.parse.SelectStatement;
import com.salesforce.phoenix.parse.TableName;
import com.salesforce.phoenix.parse.TableNode;
import com.salesforce.phoenix.parse.UpsertStatement;
import com.salesforce.phoenix.query.QueryConstants;
import com.salesforce.phoenix.query.QueryServices;
import com.salesforce.phoenix.query.QueryServicesOptions;
import com.salesforce.phoenix.schema.ColumnModifier;
import com.salesforce.phoenix.schema.ExecuteQueryNotApplicableException;
import com.salesforce.phoenix.schema.ExecuteUpdateNotApplicableException;
import com.salesforce.phoenix.schema.MetaDataClient;
import com.salesforce.phoenix.schema.PDataType;
import com.salesforce.phoenix.schema.PDatum;
import com.salesforce.phoenix.schema.PIndexState;
import com.salesforce.phoenix.schema.PTableType;
import com.salesforce.phoenix.schema.RowKeyValueAccessor;
import com.salesforce.phoenix.schema.tuple.SingleKeyValueTuple;
import com.salesforce.phoenix.schema.tuple.Tuple;
import com.salesforce.phoenix.util.ByteUtil;
import com.salesforce.phoenix.util.KeyValueUtil;
import com.salesforce.phoenix.util.SQLCloseable;
import com.salesforce.phoenix.util.SQLCloseables;
import com.salesforce.phoenix.util.ServerUtil;
/**
*
* JDBC Statement implementation of Phoenix.
* Currently only the following methods are supported:
* - {@link #executeQuery(String)}
* - {@link #executeUpdate(String)}
* - {@link #execute(String)}
* - {@link #getResultSet()}
* - {@link #getUpdateCount()}
* - {@link #close()}
* The Statement only supports the following options:
* - ResultSet.FETCH_FORWARD
* - ResultSet.TYPE_FORWARD_ONLY
* - ResultSet.CLOSE_CURSORS_AT_COMMIT
*
* @author jtaylor
* @since 0.1
*/
public class PhoenixStatement implements Statement, SQLCloseable, com.salesforce.phoenix.jdbc.Jdbc7Shim.Statement {
public enum UpdateOperation {
DELETED("deleted"),
UPSERTED("upserted");
private final String toString;
UpdateOperation(String toString) {
this.toString = toString;
}
@Override
public String toString() {
return toString;
}
};
protected final PhoenixConnection connection;
private static final int NO_UPDATE = -1;
private List<PhoenixResultSet> resultSets = new ArrayList<PhoenixResultSet>();
private QueryPlan lastQueryPlan;
private PhoenixResultSet lastResultSet;
private int lastUpdateCount = NO_UPDATE;
private UpdateOperation lastUpdateOperation;
private boolean isClosed = false;
private ResultSetMetaData resultSetMetaData;
private int maxRows;
public PhoenixStatement(PhoenixConnection connection) {
this.connection = connection;
}
protected List<PhoenixResultSet> getResultSets() {
return resultSets;
}
protected PhoenixResultSet newResultSet(ResultIterator iterator, RowProjector projector) throws SQLException {
return new PhoenixResultSet(iterator, projector, PhoenixStatement.this);
}
protected static interface ExecutableStatement extends BindableStatement {
public boolean execute() throws SQLException;
public int executeUpdate() throws SQLException;
public PhoenixResultSet executeQuery() throws SQLException;
public ResultSetMetaData getResultSetMetaData() throws SQLException;
public StatementPlan optimizePlan() throws SQLException;
public StatementPlan compilePlan() throws SQLException;
}
protected static interface MutatableStatement extends ExecutableStatement {
@Override
public MutationPlan optimizePlan() throws SQLException;
}
private class ExecutableSelectStatement extends SelectStatement implements ExecutableStatement {
private ExecutableSelectStatement(List<? extends TableNode> from, HintNode hint, boolean isDistinct, List<AliasedNode> select, ParseNode where,
List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit, int bindCount, boolean isAggregate) {
super(from, hint, isDistinct, select, where, groupBy, having, orderBy, limit, bindCount, isAggregate);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
QueryPlan plan = optimizePlan();
PhoenixResultSet rs = newResultSet(plan.iterator(), plan.getProjector());
resultSets.add(rs);
lastResultSet = rs;
lastUpdateCount = NO_UPDATE;
lastUpdateOperation = null;
return rs;
}
@Override
public boolean execute() throws SQLException {
executeQuery();
return true;
}
@Override
public int executeUpdate() throws SQLException {
throw new ExecuteUpdateNotApplicableException(this.toString());
}
@Override
public QueryPlan optimizePlan() throws SQLException {
return lastQueryPlan = connection.getQueryServices().getOptimizer().optimize(this, PhoenixStatement.this);
}
@Override
public StatementPlan compilePlan() throws SQLException {
return new QueryCompiler(PhoenixStatement.this).compile(this);
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
if (resultSetMetaData == null) {
// Just compile top level query without optimizing to get ResultSetMetaData
QueryPlan plan = new QueryCompiler(PhoenixStatement.this).compile(this);
resultSetMetaData = new PhoenixResultSetMetaData(connection, plan.getProjector());
}
return resultSetMetaData;
}
}
private int executeMutation(MutationPlan plan) throws SQLException {
// Note that the upsert select statements will need to commit any open transaction here,
// since they'd update data directly from coprocessors, and should thus operate on
// the latest state
MutationState state = plan.execute();
connection.getMutationState().join(state);
if (connection.getAutoCommit()) {
connection.commit();
}
lastResultSet = null;
lastQueryPlan = null;
// Unfortunately, JDBC uses an int for update count, so we
// just max out at Integer.MAX_VALUE
long updateCount = state.getUpdateCount();
lastUpdateCount = (int)Math.min(Integer.MAX_VALUE, updateCount);
return lastUpdateCount;
}
private class ExecutableUpsertStatement extends UpsertStatement implements MutatableStatement {
private ExecutableUpsertStatement(NamedTableNode table, HintNode hintNode, List<ColumnName> columns, List<ParseNode> values, SelectStatement select, int bindCount) {
super(table, hintNode, columns, values, select, bindCount);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("upsert", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
lastUpdateOperation = UpdateOperation.UPSERTED;
return executeMutation(optimizePlan());
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public MutationPlan compilePlan() throws SQLException {
UpsertCompiler compiler = new UpsertCompiler(PhoenixStatement.this);
return compiler.compile(this);
}
@Override
public MutationPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableDeleteStatement extends DeleteStatement implements MutatableStatement {
private ExecutableDeleteStatement(NamedTableNode table, HintNode hint, ParseNode whereNode, List<OrderByNode> orderBy, LimitNode limit, int bindCount) {
super(table, hint, whereNode, orderBy, limit, bindCount);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("delete", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
lastUpdateOperation = UpdateOperation.DELETED;
return executeMutation(optimizePlan());
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public MutationPlan compilePlan() throws SQLException {
DeleteCompiler compiler = new DeleteCompiler(PhoenixStatement.this);
return compiler.compile(this);
}
@Override
public MutationPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableCreateTableStatement extends CreateTableStatement implements ExecutableStatement {
ExecutableCreateTableStatement(TableName tableName, ListMultimap<String,Pair<String,Object>> props, List<ColumnDef> columnDefs,
PrimaryKeyConstraint pkConstraint, List<ParseNode> splitNodes, PTableType tableType, boolean ifNotExists,
TableName baseTableName, ParseNode tableTypeIdNode, int bindCount) {
super(tableName, props, columnDefs, pkConstraint, splitNodes, tableType, ifNotExists, baseTableName, tableTypeIdNode, bindCount);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("CREATE TABLE", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
MutationPlan plan = optimizePlan();
MutationState state = plan.execute();
lastQueryPlan = null;
lastResultSet = null;
lastUpdateCount = (int)Math.min(state.getUpdateCount(), Integer.MAX_VALUE);
lastUpdateOperation = UpdateOperation.UPSERTED;
return lastUpdateCount;
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public MutationPlan compilePlan() throws SQLException {
CreateTableCompiler compiler = new CreateTableCompiler(PhoenixStatement.this);
return compiler.compile(this);
}
@Override
public MutationPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableCreateIndexStatement extends CreateIndexStatement implements ExecutableStatement {
public ExecutableCreateIndexStatement(NamedNode indexName, NamedTableNode dataTable, PrimaryKeyConstraint pkConstraint, List<ColumnName> includeColumns, List<ParseNode> splits,
ListMultimap<String,Pair<String,Object>> props, boolean ifNotExists, int bindCount) {
super(indexName, dataTable, pkConstraint, includeColumns, splits, props, ifNotExists, bindCount);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("CREATE INDEX", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
MutationPlan plan = optimizePlan();
MutationState state = plan.execute();
lastQueryPlan = null;
lastResultSet = null;
lastUpdateCount = (int)Math.min(state.getUpdateCount(), Integer.MAX_VALUE);
lastUpdateOperation = UpdateOperation.UPSERTED;
return lastUpdateCount;
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public MutationPlan compilePlan() throws SQLException {
CreateIndexCompiler compiler = new CreateIndexCompiler(PhoenixStatement.this);
return compiler.compile(this);
}
@Override
public MutationPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableCreateSequenceStatement extends CreateSequenceStatement implements ExecutableStatement {
public ExecutableCreateSequenceStatement(TableName sequenceName, ParseNode startWith, ParseNode incrementBy, ParseNode cacheSize, boolean ifNotExists, int bindCount) {
super(sequenceName, startWith, incrementBy, cacheSize, ifNotExists, bindCount);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("CREATE SEQUENCE", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
MutationPlan plan = optimizePlan();
MutationState state = plan.execute();
lastQueryPlan = null;
lastResultSet = null;
lastUpdateCount = (int)Math.min(state.getUpdateCount(), Integer.MAX_VALUE);
lastUpdateOperation = UpdateOperation.UPSERTED;
return lastUpdateCount;
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public MutationPlan compilePlan() throws SQLException {
CreateSequenceCompiler compiler = new CreateSequenceCompiler(PhoenixStatement.this);
return compiler.compile(this);
}
@Override
public MutationPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableDropSequenceStatement extends DropSequenceStatement implements ExecutableStatement {
public ExecutableDropSequenceStatement(TableName sequenceName, boolean ifExists, int bindCount) {
super(sequenceName, ifExists, bindCount);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("DROP SEQUENCE", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
MutationPlan plan = optimizePlan();
MutationState state = plan.execute();
lastQueryPlan = null;
lastResultSet = null;
lastUpdateCount = (int)Math.min(state.getUpdateCount(), Integer.MAX_VALUE);
lastUpdateOperation = UpdateOperation.UPSERTED;
return lastUpdateCount;
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public MutationPlan compilePlan() throws SQLException {
DropSequenceCompiler compiler = new DropSequenceCompiler(PhoenixStatement.this);
return compiler.compile(this);
}
@Override
public MutationPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableDropTableStatement extends DropTableStatement implements ExecutableStatement {
ExecutableDropTableStatement(TableName tableName, PTableType tableType, boolean ifExists) {
super(tableName, tableType, ifExists);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("DROP TABLE", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
MetaDataClient client = new MetaDataClient(connection);
MutationState state = client.dropTable(this);
lastQueryPlan = null;
lastResultSet = null;
lastUpdateCount = (int)Math.min(state.getUpdateCount(), Integer.MAX_VALUE);
lastUpdateOperation = UpdateOperation.DELETED;
return lastUpdateCount;
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public StatementPlan compilePlan() throws SQLException {
return new StatementPlan() {
@Override
public ParameterMetaData getParameterMetaData() {
return PhoenixParameterMetaData.EMPTY_PARAMETER_META_DATA;
}
@Override
public ExplainPlan getExplainPlan() throws SQLException {
return new ExplainPlan(Collections.singletonList("DROP TABLE"));
}
};
}
@Override
public StatementPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableDropIndexStatement extends DropIndexStatement implements ExecutableStatement {
public ExecutableDropIndexStatement(NamedNode indexName, TableName tableName, boolean ifExists) {
super(indexName, tableName, ifExists);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("DROP INDEX", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
MetaDataClient client = new MetaDataClient(connection);
MutationState state = client.dropIndex(this);
lastQueryPlan = null;
lastResultSet = null;
lastUpdateCount = (int)Math.min(state.getUpdateCount(), Integer.MAX_VALUE);
lastUpdateOperation = UpdateOperation.DELETED;
return lastUpdateCount;
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public StatementPlan compilePlan() throws SQLException {
return new StatementPlan() {
@Override
public ParameterMetaData getParameterMetaData() {
return PhoenixParameterMetaData.EMPTY_PARAMETER_META_DATA;
}
@Override
public ExplainPlan getExplainPlan() throws SQLException {
return new ExplainPlan(Collections.singletonList("DROP INDEX"));
}
};
}
@Override
public StatementPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableAlterIndexStatement extends AlterIndexStatement implements ExecutableStatement {
public ExecutableAlterIndexStatement(NamedTableNode indexTableNode, String dataTableName, boolean ifExists, PIndexState state) {
super(indexTableNode, dataTableName, ifExists, state);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("ALTER INDEX", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
MetaDataClient client = new MetaDataClient(connection);
MutationState state = client.alterIndex(this);
lastQueryPlan = null;
lastResultSet = null;
lastUpdateCount = (int)Math.min(state.getUpdateCount(), Integer.MAX_VALUE);
lastUpdateOperation = UpdateOperation.UPSERTED;
return lastUpdateCount;
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public StatementPlan compilePlan() throws SQLException {
return new StatementPlan() {
@Override
public ParameterMetaData getParameterMetaData() {
return PhoenixParameterMetaData.EMPTY_PARAMETER_META_DATA;
}
@Override
public ExplainPlan getExplainPlan() throws SQLException {
return new ExplainPlan(Collections.singletonList("ALTER INDEX"));
}
};
}
@Override
public StatementPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableAddColumnStatement extends AddColumnStatement implements ExecutableStatement {
ExecutableAddColumnStatement(NamedTableNode table, PTableType tableType, List<ColumnDef> columnDefs, boolean ifNotExists, Map<String, Object> props) {
super(table, tableType, columnDefs, ifNotExists, props);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("ALTER TABLE", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
MetaDataClient client = new MetaDataClient(connection);
MutationState state = client.addColumn(this);
lastQueryPlan = null;
lastResultSet = null;
lastUpdateCount = (int)Math.min(state.getUpdateCount(), Integer.MAX_VALUE);
lastUpdateOperation = UpdateOperation.UPSERTED;
return lastUpdateCount;
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public StatementPlan compilePlan() throws SQLException {
return new StatementPlan() {
@Override
public ParameterMetaData getParameterMetaData() {
return PhoenixParameterMetaData.EMPTY_PARAMETER_META_DATA;
}
@Override
public ExplainPlan getExplainPlan() throws SQLException {
return new ExplainPlan(Collections.singletonList("ALTER " + getTableType() + " ADD COLUMN"));
}
};
}
@Override
public StatementPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private class ExecutableDropColumnStatement extends DropColumnStatement implements ExecutableStatement {
ExecutableDropColumnStatement(NamedTableNode table, PTableType tableType, List<ColumnName> columnRefs, boolean ifExists) {
super(table, tableType, columnRefs, ifExists);
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
throw new ExecuteQueryNotApplicableException("ALTER TABLE", this.toString());
}
@Override
public boolean execute() throws SQLException {
executeUpdate();
return false;
}
@Override
public int executeUpdate() throws SQLException {
MetaDataClient client = new MetaDataClient(connection);
MutationState state = client.dropColumn(this);
lastQueryPlan = null;
lastResultSet = null;
lastUpdateCount = (int)Math.min(state.getUpdateCount(), Integer.MAX_VALUE);
lastUpdateOperation = UpdateOperation.UPSERTED;
return lastUpdateCount;
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return null;
}
@Override
public StatementPlan compilePlan() throws SQLException {
return new StatementPlan() {
@Override
public ParameterMetaData getParameterMetaData() {
return new PhoenixParameterMetaData(0);
}
@Override
public ExplainPlan getExplainPlan() throws SQLException {
return new ExplainPlan(Collections.singletonList("ALTER " + getTableType() + " DROP COLUMN"));
}
};
}
@Override
public StatementPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
private static final byte[] EXPLAIN_PLAN_FAMILY = QueryConstants.SINGLE_COLUMN_FAMILY;
private static final byte[] EXPLAIN_PLAN_COLUMN = PDataType.VARCHAR.toBytes("Plan");
private static final String EXPLAIN_PLAN_ALIAS = "PLAN";
private static final String EXPLAIN_PLAN_TABLE_NAME = "PLAN_TABLE";
private static final PDatum EXPLAIN_PLAN_DATUM = new PDatum() {
@Override
public boolean isNullable() {
return false;
}
@Override
public PDataType getDataType() {
return PDataType.VARCHAR;
}
@Override
public Integer getByteSize() {
return null;
}
@Override
public Integer getMaxLength() {
return null;
}
@Override
public Integer getScale() {
return null;
}
@Override
public ColumnModifier getColumnModifier() {
return null;
}
};
private static final RowProjector EXPLAIN_PLAN_ROW_PROJECTOR = new RowProjector(Arrays.<ColumnProjector>asList(
new ExpressionProjector(EXPLAIN_PLAN_ALIAS, EXPLAIN_PLAN_TABLE_NAME,
new RowKeyColumnExpression(EXPLAIN_PLAN_DATUM,
new RowKeyValueAccessor(Collections.<PDatum>singletonList(EXPLAIN_PLAN_DATUM), 0)), false)
), 0, true);
private class ExecutableExplainStatement extends ExplainStatement implements ExecutableStatement {
public ExecutableExplainStatement(BindableStatement statement) {
super(statement);
}
@Override
public ExecutableStatement getStatement() {
return (ExecutableStatement) super.getStatement();
}
@Override
public int getBindCount() {
return getStatement().getBindCount();
}
@Override
public PhoenixResultSet executeQuery() throws SQLException {
StatementPlan plan = getStatement().optimizePlan();
List<String> planSteps = plan.getExplainPlan().getPlanSteps();
List<Tuple> tuples = Lists.newArrayListWithExpectedSize(planSteps.size());
for (String planStep : planSteps) {
Tuple tuple = new SingleKeyValueTuple(KeyValueUtil.newKeyValue(PDataType.VARCHAR.toBytes(planStep), EXPLAIN_PLAN_FAMILY, EXPLAIN_PLAN_COLUMN, MetaDataProtocol.MIN_TABLE_TIMESTAMP, ByteUtil.EMPTY_BYTE_ARRAY));
tuples.add(tuple);
}
PhoenixResultSet rs = new PhoenixResultSet(new MaterializedResultIterator(tuples),EXPLAIN_PLAN_ROW_PROJECTOR, new PhoenixStatement(connection));
lastResultSet = rs;
lastQueryPlan = null;
lastUpdateCount = NO_UPDATE;
return rs;
}
@Override
public boolean execute() throws SQLException {
executeQuery();
return true;
}
@Override
public int executeUpdate() throws SQLException {
throw new ExecuteUpdateNotApplicableException("ALTER TABLE", this.toString());
}
@Override
public ResultSetMetaData getResultSetMetaData() throws SQLException {
return new PhoenixResultSetMetaData(connection, EXPLAIN_PLAN_ROW_PROJECTOR);
}
@Override
public StatementPlan compilePlan() throws SQLException {
return StatementPlan.EMPTY_PLAN;
}
@Override
public StatementPlan optimizePlan() throws SQLException {
return compilePlan();
}
}
protected class ExecutableNodeFactory extends ParseNodeFactory {
@Override
public ExecutableSelectStatement select(List<? extends TableNode> from, HintNode hint, boolean isDistinct, List<AliasedNode> select,
ParseNode where, List<ParseNode> groupBy, ParseNode having,
List<OrderByNode> orderBy, LimitNode limit, int bindCount, boolean isAggregate) {
return new ExecutableSelectStatement(from, hint, isDistinct, select, where, groupBy == null ? Collections.<ParseNode>emptyList() : groupBy, having, orderBy == null ? Collections.<OrderByNode>emptyList() : orderBy, limit, bindCount, isAggregate);
}
@Override
public ExecutableUpsertStatement upsert(NamedTableNode table, HintNode hintNode, List<ColumnName> columns, List<ParseNode> values, SelectStatement select, int bindCount) {
return new ExecutableUpsertStatement(table, hintNode, columns, values, select, bindCount);
}
@Override
public ExecutableDeleteStatement delete(NamedTableNode table, HintNode hint, ParseNode whereNode, List<OrderByNode> orderBy, LimitNode limit, int bindCount) {
return new ExecutableDeleteStatement(table, hint, whereNode, orderBy, limit, bindCount);
}
@Override
public CreateTableStatement createTable(TableName tableName, ListMultimap<String,Pair<String,Object>> props, List<ColumnDef> columns, PrimaryKeyConstraint pkConstraint,
List<ParseNode> splits, PTableType tableType, boolean ifNotExists, TableName baseTableName, ParseNode tableTypeIdNode, int bindCount) {
return new ExecutableCreateTableStatement(tableName, props, columns, pkConstraint, splits, tableType, ifNotExists, baseTableName, tableTypeIdNode, bindCount);
}
@Override
public CreateSequenceStatement createSequence(TableName tableName, ParseNode startsWith, ParseNode incrementBy, ParseNode cacheSize, boolean ifNotExists, int bindCount){
return new ExecutableCreateSequenceStatement(tableName, startsWith, incrementBy, cacheSize, ifNotExists, bindCount);
}
@Override
public DropSequenceStatement dropSequence(TableName tableName, boolean ifExists, int bindCount){
return new ExecutableDropSequenceStatement(tableName, ifExists, bindCount);
}
@Override
public CreateIndexStatement createIndex(NamedNode indexName, NamedTableNode dataTable, PrimaryKeyConstraint pkConstraint, List<ColumnName> includeColumns, List<ParseNode> splits, ListMultimap<String,Pair<String,Object>> props, boolean ifNotExists, int bindCount) {
return new ExecutableCreateIndexStatement(indexName, dataTable, pkConstraint, includeColumns, splits, props, ifNotExists, bindCount);
}
@Override
public AddColumnStatement addColumn(NamedTableNode table, PTableType tableType, List<ColumnDef> columnDefs, boolean ifNotExists, Map<String,Object> props) {
return new ExecutableAddColumnStatement(table, tableType, columnDefs, ifNotExists, props);
}
@Override
public DropColumnStatement dropColumn(NamedTableNode table, PTableType tableType, List<ColumnName> columnNodes, boolean ifExists) {
return new ExecutableDropColumnStatement(table, tableType, columnNodes, ifExists);
}
@Override
public DropTableStatement dropTable(TableName tableName, PTableType tableType, boolean ifExists) {
return new ExecutableDropTableStatement(tableName, tableType, ifExists);
}
@Override
public DropIndexStatement dropIndex(NamedNode indexName, TableName tableName, boolean ifExists) {
return new ExecutableDropIndexStatement(indexName, tableName, ifExists);
}
@Override
public AlterIndexStatement alterIndex(NamedTableNode indexTableNode, String dataTableName, boolean ifExists, PIndexState state) {
return new ExecutableAlterIndexStatement(indexTableNode, dataTableName, ifExists, state);
}
@Override
public ExplainStatement explain(BindableStatement statement) {
return new ExecutableExplainStatement(statement);
}
}
static class PhoenixStatementParser extends SQLParser {
PhoenixStatementParser(String query, ParseNodeFactory nodeFactory) throws IOException {
super(query, nodeFactory);
}
PhoenixStatementParser(Reader reader) throws IOException {
super(reader);
}
@Override
public ExecutableStatement nextStatement(ParseNodeFactory nodeFactory) throws SQLException {
return (ExecutableStatement) super.nextStatement(nodeFactory);
}
@Override
public ExecutableStatement parseStatement() throws SQLException {
return (ExecutableStatement) super.parseStatement();
}
}
public Format getFormatter(PDataType type) {
return connection.getFormatter(type);
}
@Override
public void addBatch(String sql) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void cancel() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void clearBatch() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void clearWarnings() throws SQLException {
}
@Override
public void close() throws SQLException {
try {
List<PhoenixResultSet> resultSets = this.resultSets;
// Create new list so that remove of the PhoenixResultSet
// during closeAll doesn't needless do a linear search
// on this list.
this.resultSets = Lists.newArrayList();
SQLCloseables.closeAll(resultSets);
} finally {
try {
connection.removeStatement(this);
} finally {
isClosed = true;
}
}
}
public List<Object> getParameters() {
return Collections.<Object>emptyList();
}
protected ExecutableStatement parseStatement(String sql) throws SQLException {
PhoenixStatementParser parser = null;
try {
parser = new PhoenixStatementParser(sql, new ExecutableNodeFactory());
} catch (IOException e) {
throw ServerUtil.parseServerException(e);
}
ExecutableStatement statement = parser.parseStatement();
return statement;
}
@Override
public boolean execute(String sql) throws SQLException {
return parseStatement(sql).execute();
}
public QueryPlan optimizeQuery(String sql) throws SQLException {
return (QueryPlan)parseStatement(sql).optimizePlan();
}
public QueryPlan compileQuery(String sql) throws SQLException {
return (QueryPlan)parseStatement(sql).compilePlan();
}
@Override
public ResultSet executeQuery(String sql) throws SQLException {
return parseStatement(sql).executeQuery();
}
@Override
public int executeUpdate(String sql) throws SQLException {
return parseStatement(sql).executeUpdate();
}
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public boolean execute(String sql, String[] columnNames) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public int[] executeBatch() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public PhoenixConnection getConnection() {
return connection;
}
@Override
public int getFetchDirection() throws SQLException {
return ResultSet.FETCH_FORWARD;
}
@Override
public int getFetchSize() throws SQLException {
return connection.getQueryServices().getProps().getInt(QueryServices.SCAN_CACHE_SIZE_ATTRIB, QueryServicesOptions.DEFAULT_SCAN_CACHE_SIZE);
}
@Override
public ResultSet getGeneratedKeys() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public int getMaxFieldSize() throws SQLException {
return 0; // TODO: 4000?
}
@Override
public int getMaxRows() throws SQLException {
return maxRows;
}
@Override
public boolean getMoreResults() throws SQLException {
return false;
}
@Override
public boolean getMoreResults(int current) throws SQLException {
return false;
}
@Override
public int getQueryTimeout() throws SQLException {
return connection.getQueryServices().getProps().getInt(QueryServices.KEEP_ALIVE_MS_ATTRIB, 0) / 1000;
}
// For testing
public QueryPlan getQueryPlan() {
return lastQueryPlan;
}
@Override
public ResultSet getResultSet() throws SQLException {
ResultSet rs = lastResultSet;
lastResultSet = null;
return rs;
}
@Override
public int getResultSetConcurrency() throws SQLException {
return ResultSet.CONCUR_READ_ONLY;
}
@Override
public int getResultSetHoldability() throws SQLException {
// TODO: not sure this matters
return ResultSet.CLOSE_CURSORS_AT_COMMIT;
}
@Override
public int getResultSetType() throws SQLException {
return ResultSet.TYPE_FORWARD_ONLY;
}
public UpdateOperation getUpdateOperation() {
return lastUpdateOperation;
}
@Override
public int getUpdateCount() throws SQLException {
int updateCount = lastUpdateCount;
// Only first call can get the update count, otherwise
// some SQL clients get into an infinite loop when an
// update occurs.
lastUpdateCount = NO_UPDATE;
return updateCount;
}
@Override
public SQLWarning getWarnings() throws SQLException {
return null;
}
@Override
public boolean isClosed() throws SQLException {
return isClosed;
}
@Override
public boolean isPoolable() throws SQLException {
return false;
}
@Override
public void setCursorName(String name) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setEscapeProcessing(boolean enable) throws SQLException {
// TODO: any escaping we need to do?
}
@Override
public void setFetchDirection(int direction) throws SQLException {
if (direction != ResultSet.FETCH_FORWARD) {
throw new SQLFeatureNotSupportedException();
}
}
@Override
public void setFetchSize(int rows) throws SQLException {
// TODO: map to Scan.setBatch() ?
throw new SQLFeatureNotSupportedException();
}
@Override
public void setMaxFieldSize(int max) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setMaxRows(int max) throws SQLException {
this.maxRows = max;
}
@Override
public void setPoolable(boolean poolable) throws SQLException {
if (poolable) {
throw new SQLFeatureNotSupportedException();
}
}
@Override
public void setQueryTimeout(int seconds) throws SQLException {
// The Phoenix setting for this is shared across all connections currently
throw new SQLFeatureNotSupportedException();
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface.isInstance(this);
}
@SuppressWarnings("unchecked")
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
if (!iface.isInstance(this)) {
throw new SQLExceptionInfo.Builder(SQLExceptionCode.CLASS_NOT_UNWRAPPABLE)
.setMessage(this.getClass().getName() + " not unwrappable from " + iface.getName())
.build().buildException();
}
return (T)this;
}
@Override
public void closeOnCompletion() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public boolean isCloseOnCompletion() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
}