This is an automated email from the ASF dual-hosted git repository.
skadam pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/master by this push:
new 22663ca PHOENIX-5261: Implement ALTER TABLE ADD COLUMN CASCADE (#864)
22663ca is described below
commit 22663cac7acd38757fa04da42a51bb516977c804
Author: Swaroopa Kadam <[email protected]>
AuthorDate: Mon Sep 21 14:50:36 2020 -0700
PHOENIX-5261: Implement ALTER TABLE ADD COLUMN CASCADE (#864)
Co-authored-by: Swaroopa Kadam <[email protected]>
---
.../phoenix/end2end/AlterAddCascadeIndexIT.java | 363 +++++++++++++++++++++
phoenix-core/src/main/antlr3/PhoenixSQL.g | 9 +-
.../apache/phoenix/exception/SQLExceptionCode.java | 8 +-
.../org/apache/phoenix/jdbc/PhoenixStatement.java | 8 +-
.../apache/phoenix/parse/AddColumnStatement.java | 12 +-
.../org/apache/phoenix/parse/ParseNodeFactory.java | 4 +-
.../org/apache/phoenix/schema/MetaDataClient.java | 122 ++++++-
7 files changed, 509 insertions(+), 17 deletions(-)
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterAddCascadeIndexIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterAddCascadeIndexIT.java
new file mode 100644
index 0000000..80c9f98
--- /dev/null
+++
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/AlterAddCascadeIndexIT.java
@@ -0,0 +1,363 @@
+/*
+ * 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.end2end;
+
+import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.schema.PTableType;
+import org.apache.phoenix.schema.types.PDecimal;
+import org.apache.phoenix.schema.types.PDouble;
+import org.apache.phoenix.schema.types.PFloat;
+import org.apache.phoenix.schema.types.PVarchar;
+import org.apache.phoenix.util.ColumnInfo;
+import org.apache.phoenix.util.SchemaUtil;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class AlterAddCascadeIndexIT extends ParallelStatsDisabledIT {
+
+ public static final String SYNTAX_ERROR = "Syntax error";
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+ private static Connection conn;
+ private Properties prop;
+ private boolean isViewIndex;
+ private String phoenixObjectName;
+ private String indexNames;
+ private final String tableDDLOptions;
+ String fullIndexNameOne, fullIndexNameTwo;
+
+
+ public AlterAddCascadeIndexIT(boolean isViewIndex, boolean mutable) {
+ this.isViewIndex = isViewIndex;
+ StringBuilder optionBuilder = new StringBuilder();
+ if (!mutable) {
+ optionBuilder.append(" IMMUTABLE_ROWS=true");
+ }
+ this.tableDDLOptions = optionBuilder.toString();
+ }
+
+ @Parameters(name="AlterAddCascadeIndexIT_isViewIndex={0},mutable={1}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {
+ { true, true},
+ { true, false},
+ { false, true},
+ { false, false},
+ });
+ }
+
+ @Before
+ public void setup() throws SQLException {
+ prop = new Properties();
+ conn = DriverManager.getConnection(getUrl(), prop);
+ conn.setAutoCommit(true);
+ String schemaName = "S_"+generateUniqueName();
+ String indexNameOne = "I_"+generateUniqueName();
+ String indexNameTwo = "I_"+generateUniqueName();
+ String tableName = "T_"+generateUniqueName();
+ String viewName = "V_"+generateUniqueName();
+ String fullViewName = SchemaUtil.getQualifiedTableName(schemaName,
viewName);
+ String fullTableName = SchemaUtil.getQualifiedTableName(schemaName,
tableName);
+ conn.createStatement().execute("CREATE TABLE IF NOT EXISTS " +
fullTableName + " (\n" +
+ " state CHAR(2) NOT NULL,\n" +
+ " city VARCHAR NOT NULL,\n" +
+ " population BIGINT,\n" +
+ " CONSTRAINT my_pk PRIMARY KEY (state, city)) " +
tableDDLOptions);
+
+ if(isViewIndex) {
+ conn.createStatement().execute("CREATE VIEW IF NOT EXISTS " +
fullViewName +
+ " (city_area INTEGER, avg_fam_size INTEGER) AS " +
+ "SELECT * FROM "+fullTableName+" WHERE state = 'CA'");
+
+ conn.createStatement().execute("CREATE INDEX IF NOT EXISTS " +
indexNameOne + " ON " +
+ fullViewName+" (city_area) INCLUDE (population)");
+ conn.createStatement().execute("CREATE INDEX IF NOT EXISTS " +
indexNameTwo + " ON " +
+ fullViewName+" (avg_fam_size) INCLUDE (population)");
+ phoenixObjectName = fullViewName;
+ } else {
+ conn.createStatement().execute("CREATE INDEX IF NOT EXISTS " +
indexNameOne + " ON " +
+ fullTableName+" (population)");
+ conn.createStatement().execute("CREATE INDEX IF NOT EXISTS " +
indexNameTwo + " ON " +
+ fullTableName+" (state, population)");
+ phoenixObjectName = fullTableName;
+ }
+ fullIndexNameOne = SchemaUtil.getQualifiedTableName(schemaName,
indexNameOne);
+ fullIndexNameTwo = SchemaUtil.getQualifiedTableName(schemaName,
indexNameTwo);
+ indexNames = indexNameOne +", "+indexNameTwo;
+ }
+
+
+ // Test with ALTER TABLE/VIEW CASCADE INDEX ALL with upserting into new
column
+ @Test
+ public void testAlterDBOAddCascadeIndexAllUpsert() throws Exception {
+ String query = "ALTER " +(isViewIndex? "VIEW " : "TABLE ") +
phoenixObjectName + " ADD new_column_3 VARCHAR(64) CASCADE INDEX ALL";
+ conn.createStatement().execute(query);
+ PreparedStatement ps;
+ if(isViewIndex) {
+ ps = conn.prepareStatement("UPSERT INTO " + phoenixObjectName +
+
"(state,city,population,city_area,avg_fam_size,new_column_3) " +
+ "VALUES('CA','Santa
Barbara',912332,1300,4,'test_column')");
+ } else {
+ ps = conn.prepareStatement("UPSERT INTO " + phoenixObjectName +
+ "(state,city,population,new_column_3) " +
+ "VALUES('CA','Santa Barbara',912332,'test_column')");
+ }
+ ps.executeUpdate();
+ ColumnInfo [] columnArray = {new ColumnInfo("new_column_3",
PVarchar.INSTANCE.getSqlType(), 64)};
+ ColumnInfo [] columnIndexArray = {new ColumnInfo("0:new_column_3",
PVarchar.INSTANCE.getSqlType(), 64)};
+ if(isViewIndex) {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.VIEW, 6,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 5,
columnIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 5,
columnIndexArray, false);
+ } else {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.TABLE, 4,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 4,
columnIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 4,
columnIndexArray, false);
+ }
+
+ }
+
+ // Test with CASCADE INDEX <index_name>
+ @Test
+ public void testAlterDBOAddCascadeIndex() throws Exception {
+ ColumnInfo [] columnArray = {new ColumnInfo("new_column_1",
PFloat.INSTANCE.getSqlType())};
+ ColumnInfo [] columnIndexArray = {new ColumnInfo("0:new_column_1",
PDecimal.INSTANCE.getSqlType())};
+
+ String query = "ALTER " + (isViewIndex? "VIEW " : "TABLE ")
+ + phoenixObjectName + " ADD new_column_1 FLOAT CASCADE INDEX "
+ indexNames.split(",")[0];
+ conn.createStatement().execute(query);
+ if(isViewIndex) {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.VIEW, 6,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 5,
columnIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 4,
columnIndexArray, true);
+ } else {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.TABLE, 4,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 4,
columnIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 3,
columnIndexArray, true);
+ }
+ }
+
+ // Test with CASCADE INDEX <index_name>
+ @Test
+ public void testAlterDBOAddCascadeTwoColsOneIndex() throws Exception {
+ ColumnInfo [] columnArray = {new ColumnInfo("new_column_1",
PFloat.INSTANCE.getSqlType()),
+ new ColumnInfo("new_column_2", PDouble.INSTANCE.getSqlType())};
+ ColumnInfo [] columnIndexArray = {new ColumnInfo("0:new_column_1",
PDecimal.INSTANCE.getSqlType()),
+ new ColumnInfo("0:new_column_2",
PDecimal.INSTANCE.getSqlType())};
+ String query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ") +
phoenixObjectName
+ + " ADD new_column_1 FLOAT, new_column_2 DOUBLE CASCADE INDEX
" + indexNames.split(",")[0];
+ conn.createStatement().execute(query);
+ if(isViewIndex) {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.VIEW, 7,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 6,
columnIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 4,
columnIndexArray, true);
+ } else {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.TABLE, 5,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 5,
columnIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 3,
columnIndexArray, true);
+ }
+
+ }
+
+ // Test with CASCADE INDEX <index_name>, <index_name>
+ @Test
+ public void testAlterDBOAddCascadeIndexes() throws Exception {
+ ColumnInfo [] columnArray = {new ColumnInfo("new_column_1",
PDouble.INSTANCE.getSqlType())};
+ ColumnInfo [] columnIndexArray = {new ColumnInfo("0:new_column_1",
PDecimal.INSTANCE.getSqlType())};
+ String query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ")
+ + phoenixObjectName + " ADD new_column_1 DOUBLE CASCADE INDEX
" + indexNames;
+ conn.createStatement().execute(query);
+ if(isViewIndex) {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.VIEW, 6,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 5,
columnIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 5,
columnIndexArray, false);
+ } else {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.TABLE, 4,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 4,
columnIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 4,
columnIndexArray, false);
+ }
+ }
+
+ // Test with CASCADE INDEX <index_name>, <index_name>
+ @Test
+ public void testAlterDBOAddCascadeTwoColsTwoIndexes() throws Exception {
+ ColumnInfo [] columnArray = {new ColumnInfo("new_column_1",
PFloat.INSTANCE.getSqlType()),
+ new ColumnInfo("new_column_2", PDouble.INSTANCE.getSqlType())};
+ ColumnInfo [] columIndexArray = {new ColumnInfo("0:new_column_1",
PDecimal.INSTANCE.getSqlType()),
+ new ColumnInfo("0:new_column_2",
PDecimal.INSTANCE.getSqlType())};
+
+ String query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ")
+ + phoenixObjectName + " ADD new_column_1 FLOAT, new_column_2
DOUBLE CASCADE INDEX " + indexNames;
+ conn.createStatement().execute(query);
+ if(isViewIndex) {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.VIEW, 7,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 6,
columIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 6,
columIndexArray, false);
+ } else {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.TABLE, 5,
columnArray, false);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 5,
columIndexArray, false);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 5,
columIndexArray, false);
+ }
+
+ }
+
+ // Exception for invalid grammar
+ @Test
+ public void testAlterDBOException() throws SQLException {
+
+ String query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ") +
phoenixObjectName + " ADD new_column VARCHAR ALL";
+ try {
+ conn.createStatement().execute(query);
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains(SYNTAX_ERROR));
+ }
+
+ query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ") +
phoenixObjectName
+ + " ADD new_column VARCHAR CASCADE " +
indexNames.split(",")[0];
+ try {
+ conn.createStatement().execute(query);
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains(SYNTAX_ERROR));
+ }
+
+ query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ") +
phoenixObjectName
+ + " ADD new_column VARCHAR INDEX " + indexNames.split(",")[0];
+ try {
+ conn.createStatement().execute(query);
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains(SYNTAX_ERROR));
+ }
+
+ query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ")
+ + phoenixObjectName + " ADD new_column_1 DOUBLE CASCADE INDEX
INCORRECT_NAME";
+ try {
+ conn.createStatement().execute(query);
+ } catch (Exception e) {
+
assertTrue(e.getMessage().contains(SQLExceptionCode.INCORRECT_INDEX_NAME.getMessage()));
+ }
+
+ String localIndex = generateUniqueName();
+ String createLocalIndex = "CREATE LOCAL INDEX " + localIndex + " ON "
+ + phoenixObjectName + "(avg_fam_size) INCLUDE (population)";
+ if(!isViewIndex) {
+ createLocalIndex = "CREATE LOCAL INDEX " + localIndex + " ON "
+ + phoenixObjectName + "(state, population)";
+
+ }
+ conn.createStatement().execute(createLocalIndex);
+ query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ")
+ + phoenixObjectName + " ADD new_column_1 DOUBLE CASCADE INDEX
"+localIndex;
+ try {
+ conn.createStatement().execute(query);
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains(SQLExceptionCode
+ .NOT_SUPPORTED_CASCADE_FEATURE_LOCAL_INDEX.getMessage()));
+ }
+
+ query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ")
+ + phoenixObjectName + " ADD new_column_2 DOUBLE CASCADE INDEX
"+localIndex + "," + indexNames;
+ try {
+ conn.createStatement().execute(query);
+ } catch (Exception e) {
+ assertTrue(e.getMessage().contains(SQLExceptionCode
+ .NOT_SUPPORTED_CASCADE_FEATURE_LOCAL_INDEX.getMessage()));
+
+ }
+ }
+
+ // Exception for incorrect index name
+ @Test
+ public void testAlterDBOIncorrectIndexNameCombination() throws Exception {
+ String query = "ALTER " + (isViewIndex ? "VIEW " : "TABLE ")
+ + phoenixObjectName + " ADD new_column_1 DOUBLE CASCADE INDEX
INCORRECT_NAME, "+ indexNames;
+ try {
+ conn.createStatement().execute(query);
+ } catch (Exception e) {
+
assertTrue(e.getMessage().contains(SQLExceptionCode.INCORRECT_INDEX_NAME.getMessage()));
+ }
+ ColumnInfo [] columnArray = {new ColumnInfo("new_column_1",
PFloat.INSTANCE.getSqlType())};
+ ColumnInfo [] columnIndexArray = {new ColumnInfo("0:new_column_1",
PDecimal.INSTANCE.getSqlType())};
+ if(isViewIndex) {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.VIEW, 5,
columnArray, true);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 4,
columnIndexArray, true);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 4,
columnIndexArray, true);
+ } else {
+ assertDBODefinition(conn, phoenixObjectName, PTableType.TABLE, 3,
columnArray, true);
+ assertDBODefinition(conn, fullIndexNameOne, PTableType.INDEX, 3,
columnIndexArray, true);
+ assertDBODefinition(conn, fullIndexNameTwo, PTableType.INDEX, 3,
columnIndexArray, true);
+ }
+
+ }
+
+ private void assertDBODefinition(Connection conn, String
phoenixObjectName, PTableType pTableType, int baseColumnCount, ColumnInfo []
columnInfo, boolean fail)
+ throws Exception {
+ String schemaName =
SchemaUtil.getSchemaNameFromFullName(phoenixObjectName);
+ String tableName =
SchemaUtil.getTableNameFromFullName(phoenixObjectName);
+ PreparedStatement p = conn.prepareStatement("SELECT * FROM
SYSTEM.CATALOG "
+ + "WHERE TABLE_SCHEM=? AND TABLE_NAME=? AND TABLE_TYPE=?");
+ p.setString(1, schemaName.toUpperCase());
+ p.setString(2, tableName.toUpperCase());
+ p.setString(3, pTableType.getSerializedValue());
+ ResultSet rs = p.executeQuery();
+ assertTrue(rs.next());
+ assertEquals("Mismatch in ColumnCount", baseColumnCount,
rs.getInt("COLUMN_COUNT"));
+ p = conn.prepareStatement("SELECT * FROM SYSTEM.CATALOG "
+ + "WHERE TABLE_SCHEM=? AND TABLE_NAME=? AND COLUMN_NAME=? AND
DATA_TYPE=?");
+ p.setString(1, schemaName.toUpperCase());
+ p.setString(2, tableName.toUpperCase());
+
+ int iPos = baseColumnCount - columnInfo.length + 1;
+ for(ColumnInfo column: columnInfo) {
+ p.setString(3, column.getDisplayName().toUpperCase());
+ p.setInt(4, column.getSqlType());
+ rs = p.executeQuery();
+ if(!fail) {
+ assertTrue(rs.next());
+ assertEquals(iPos, rs.getInt("ORDINAL_POSITION"));
+ iPos++;
+ } else {
+ assertFalse(rs.next());
+ }
+ }
+ rs.close();
+ p.close();
+ }
+
+}
\ No newline at end of file
diff --git a/phoenix-core/src/main/antlr3/PhoenixSQL.g
b/phoenix-core/src/main/antlr3/PhoenixSQL.g
index a526255..2c23114 100644
--- a/phoenix-core/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core/src/main/antlr3/PhoenixSQL.g
@@ -670,8 +670,8 @@ alter_session_node returns [AlterSessionStatement ret]
// Parse an alter table statement.
alter_table_node returns [AlterTableStatement ret]
: ALTER (TABLE | v=VIEW) t=from_table_name
- ( (DROP COLUMN (IF ex=EXISTS)? c=column_names) | (ADD (IF NOT
ex=EXISTS)? (d=column_defs) (p=fam_properties)?) | (SET (p=fam_properties)) )
- { PTableType tt = v==null ?
(QueryConstants.SYSTEM_SCHEMA_NAME.equals(t.getSchemaName()) ?
PTableType.SYSTEM : PTableType.TABLE) : PTableType.VIEW; ret = ( c == null ?
factory.addColumn(factory.namedTable(null,t), tt, d, ex!=null, p) :
factory.dropColumn(factory.namedTable(null,t), tt, c, ex!=null) ); }
+ ( (DROP COLUMN (IF ex=EXISTS)? c=column_names) | (ADD (IF NOT
ex=EXISTS)? (d=column_defs) (p=fam_properties)?) (cas=CASCADE INDEX
(list=indexes | all=ALL))? | (SET (p=fam_properties)) )
+ { PTableType tt = v==null ?
(QueryConstants.SYSTEM_SCHEMA_NAME.equals(t.getSchemaName()) ?
PTableType.SYSTEM : PTableType.TABLE) : PTableType.VIEW; ret = ( c == null ?
factory.addColumn(factory.namedTable(null,t), tt, d, ex!=null, p, cas!=null,
(all == null ? list : null)) : factory.dropColumn(factory.namedTable(null,t),
tt, c, ex!=null) ); }
;
update_statistics_node returns [UpdateStatisticsStatement ret]
@@ -698,6 +698,11 @@ column_defs returns [List<ColumnDef> ret]
: v = column_def {$ret.add(v);} (COMMA v = column_def {$ret.add(v);} )*
;
+indexes returns [List<NamedNode> ret]
+@init{ret = new ArrayList<NamedNode>(); }
+ : v = index_name {$ret.add(v);} (COMMA v = index_name {$ret.add(v);} )*
+;
+
column_def returns [ColumnDef ret]
: c=column_name dt=identifier (LPAREN l=NUMBER (COMMA s=NUMBER)?
RPAREN)? ar=ARRAY? (lsq=LSQUARE (a=NUMBER)? RSQUARE)? (nn=NOT? n=NULL)?
(DEFAULT df=expression)? (pk=PRIMARY KEY (order=ASC|order=DESC)?
rr=ROW_TIMESTAMP?)?
{ $ret = factory.columnDef(c, dt, ar != null || lsq != null, a == null
? null : Integer.parseInt( a.getText() ), nn!=null ? Boolean.FALSE : n!=null ?
Boolean.TRUE : null,
diff --git
a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
index ba81de2..bb4fdfd 100644
---
a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
+++
b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
@@ -523,7 +523,13 @@ public enum SQLExceptionCode {
"Cannot use a connection with SCN set to upsert data for a table
with indexes."),
CANNOT_PERFORM_DDL_WITH_PENDING_MUTATIONS(904, "43M15",
- "Cannot perform DDL with pending mutations. Commit or rollback
mutations before performing DDL");
+ "Cannot perform DDL with pending mutations. Commit or rollback
mutations before performing DDL"),
+
+ NOT_SUPPORTED_CASCADE_FEATURE_PK(905, "43M16", "CASCADE INDEX feature is
not supported to add new PK column in INDEX"),
+
+ INCORRECT_INDEX_NAME(906, "43M17", "The list contains one or more
incorrect index name(s)"),
+
+ NOT_SUPPORTED_CASCADE_FEATURE_LOCAL_INDEX(907, "43M18", "CASCADE INDEX
feature is not supported for local index");
private final int errorCode;
private final String sqlState;
diff --git
a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index bb437d1..1990148 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -1499,8 +1499,8 @@ public class PhoenixStatement implements Statement,
SQLCloseable {
private static class ExecutableAddColumnStatement extends
AddColumnStatement implements CompilableStatement {
- ExecutableAddColumnStatement(NamedTableNode table, PTableType
tableType, List<ColumnDef> columnDefs, boolean ifNotExists,
ListMultimap<String,Pair<String,Object>> props) {
- super(table, tableType, columnDefs, ifNotExists, props);
+ ExecutableAddColumnStatement(NamedTableNode table, PTableType
tableType, List<ColumnDef> columnDefs, boolean ifNotExists,
ListMultimap<String,Pair<String,Object>> props, boolean cascade,
List<NamedNode> indexes) {
+ super(table, tableType, columnDefs, ifNotExists, props, cascade,
indexes);
}
@SuppressWarnings("unchecked")
@@ -1641,8 +1641,8 @@ public class PhoenixStatement implements Statement,
SQLCloseable {
}
@Override
- public AddColumnStatement addColumn(NamedTableNode table, PTableType
tableType, List<ColumnDef> columnDefs, boolean ifNotExists,
ListMultimap<String,Pair<String,Object>> props) {
- return new ExecutableAddColumnStatement(table, tableType,
columnDefs, ifNotExists, props);
+ public AddColumnStatement addColumn(NamedTableNode table, PTableType
tableType, List<ColumnDef> columnDefs, boolean ifNotExists,
ListMultimap<String,Pair<String,Object>> props, boolean cascade,
List<NamedNode> indexes) {
+ return new ExecutableAddColumnStatement(table, tableType,
columnDefs, ifNotExists, props, cascade, indexes);
}
@Override
diff --git
a/phoenix-core/src/main/java/org/apache/phoenix/parse/AddColumnStatement.java
b/phoenix-core/src/main/java/org/apache/phoenix/parse/AddColumnStatement.java
index 9ef0b80..32b4ebe 100644
---
a/phoenix-core/src/main/java/org/apache/phoenix/parse/AddColumnStatement.java
+++
b/phoenix-core/src/main/java/org/apache/phoenix/parse/AddColumnStatement.java
@@ -29,12 +29,16 @@ public class AddColumnStatement extends AlterTableStatement
{
private final List<ColumnDef> columnDefs;
private final boolean ifNotExists;
private final ListMultimap<String,Pair<String,Object>> props;
-
- protected AddColumnStatement(NamedTableNode table, PTableType tableType,
List<ColumnDef> columnDefs, boolean ifNotExists,
ListMultimap<String,Pair<String,Object>> props) {
+ private final boolean cascade;
+ private final List<NamedNode> indexes;
+
+ protected AddColumnStatement(NamedTableNode table, PTableType tableType,
List<ColumnDef> columnDefs, boolean ifNotExists,
ListMultimap<String,Pair<String,Object>> props, boolean cascade,
List<NamedNode> indexes) {
super(table, tableType);
this.columnDefs = columnDefs;
this.props = props == null ?
ImmutableListMultimap.<String,Pair<String,Object>>of() : props;
this.ifNotExists = ifNotExists;
+ this.cascade = cascade;
+ this.indexes = indexes;
}
public List<ColumnDef> getColumnDefs() {
@@ -48,4 +52,8 @@ public class AddColumnStatement extends AlterTableStatement {
public ListMultimap<String,Pair<String,Object>> getProps() {
return props;
}
+
+ public boolean isCascade() { return cascade; }
+
+ public List<NamedNode> getIndexes() { return indexes; }
}
\ No newline at end of file
diff --git
a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
index 36a9c2d..af797da 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
@@ -357,8 +357,8 @@ public class ParseNodeFactory {
return new SequenceValueParseNode(tableName,
SequenceValueParseNode.Op.NEXT_VALUE, numToAllocateNode);
}
- public AddColumnStatement addColumn(NamedTableNode table, PTableType
tableType, List<ColumnDef> columnDefs, boolean ifNotExists,
ListMultimap<String,Pair<String,Object>> props) {
- return new AddColumnStatement(table, tableType, columnDefs,
ifNotExists, props);
+ public AddColumnStatement addColumn(NamedTableNode table, PTableType
tableType, List<ColumnDef> columnDefs, boolean ifNotExists,
ListMultimap<String,Pair<String,Object>> props, boolean cascade,
List<NamedNode>indexes) {
+ return new AddColumnStatement(table, tableType, columnDefs,
ifNotExists, props, cascade, indexes);
}
public DropColumnStatement dropColumn(NamedTableNode table, PTableType
tableType, List<ColumnName> columnNodes, boolean ifExists) {
diff --git
a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
index 68adde2..24f428b 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
@@ -158,6 +158,7 @@ import
org.apache.hadoop.hbase.security.access.AccessControlClient;
import org.apache.hadoop.hbase.security.access.Permission;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
+import org.apache.hadoop.util.StringUtils;
import org.apache.phoenix.compile.ColumnResolver;
import org.apache.phoenix.compile.FromCompiler;
import org.apache.phoenix.compile.IndexExpressionCompiler;
@@ -208,6 +209,7 @@ import org.apache.phoenix.parse.DropSchemaStatement;
import org.apache.phoenix.parse.DropSequenceStatement;
import org.apache.phoenix.parse.DropTableStatement;
import org.apache.phoenix.parse.IndexKeyConstraint;
+import org.apache.phoenix.parse.NamedNode;
import org.apache.phoenix.parse.NamedTableNode;
import org.apache.phoenix.parse.OpenStatement;
import org.apache.phoenix.parse.PFunction;
@@ -508,7 +510,6 @@ public class MetaDataClient {
public static final String EMPTY_TABLE = " ";
-
private final PhoenixConnection connection;
public MetaDataClient(PhoenixConnection connection) {
@@ -1098,7 +1099,7 @@ public class MetaDataClient {
}
// if there are new columns to add
return addColumn(table, columnDefs, statement.getProps(),
statement.ifNotExists(),
- true, NamedTableNode.create(statement.getTableName()),
statement.getTableType());
+ true, NamedTableNode.create(statement.getTableName()),
statement.getTableType(), false, null);
}
}
table = createTableInternal(statement, splits, parent, viewStatement,
viewType, viewIndexIdType, viewColumnConstants, isViewColumnReferenced, false,
null, null, tableProps, commonFamilyProps);
@@ -3632,14 +3633,25 @@ public class MetaDataClient {
public MutationState addColumn(AddColumnStatement statement) throws
SQLException {
PTable table = FromCompiler.getResolver(statement,
connection).getTables().get(0).getTable();
- return addColumn(table, statement.getColumnDefs(),
statement.getProps(), statement.ifNotExists(), false, statement.getTable(),
statement.getTableType());
+ return addColumn(table, statement.getColumnDefs(),
statement.getProps(), statement.ifNotExists(), false, statement.getTable(),
statement.getTableType(), statement.isCascade(), statement.getIndexes());
}
public MutationState addColumn(PTable table, List<ColumnDef>
origColumnDefs,
ListMultimap<String, Pair<String, Object>> stmtProperties, boolean
ifNotExists,
- boolean removeTableProps, NamedTableNode namedTableNode,
PTableType tableType)
+ boolean removeTableProps, NamedTableNode namedTableNode,
PTableType tableType, boolean cascade, List<NamedNode> indexes)
throws SQLException {
connection.rollback();
+ List<PTable> indexesPTable =
Lists.newArrayListWithExpectedSize(indexes != null ?
+ indexes.size() : table.getIndexes().size());
+ Map<PTable, Integer> indexToColumnSizeMap = new HashMap<>();
+
+ // if cascade keyword is passed and indexes are provided either
implicitly or explicitly
+ if (cascade && (indexes == null || !indexes.isEmpty())) {
+ indexesPTable = getIndexesPTableForCascade(indexes, table);
+ for(PTable index : indexesPTable) {
+ indexToColumnSizeMap.put(index, index.getColumns().size());
+ }
+ }
boolean wasAutoCommit = connection.getAutoCommit();
List<PColumn> columns =
Lists.newArrayListWithExpectedSize(origColumnDefs != null ?
origColumnDefs.size() : 0);
@@ -3763,10 +3775,10 @@ public class MetaDataClient {
if (!colDef.validateDefault(context, null)) {
colDef = new ColumnDef(colDef, null); //
Remove DEFAULT as it's not necessary
}
+ String familyName = null;
Integer encodedCQ = null;
if (!colDef.isPK()) {
String colDefFamily =
colDef.getColumnDefName().getFamilyName();
- String familyName = null;
ImmutableStorageScheme storageScheme =
table.getImmutableStorageScheme();
String defaultColumnFamily =
tableForCQCounters.getDefaultFamilyName() != null &&
!Strings.isNullOrEmpty(tableForCQCounters.getDefaultFamilyName().getString()) ?
tableForCQCounters.getDefaultFamilyName().getString() : DEFAULT_COLUMN_FAMILY;
@@ -3794,6 +3806,12 @@ public class MetaDataClient {
.setTableName(tableName).build().buildException();
}
PColumn column = newColumn(position++, colDef,
PrimaryKeyConstraint.EMPTY, table.getDefaultFamilyName() == null ? null :
table.getDefaultFamilyName().getString(), true, columnQualifierBytes,
willBeImmutableRows);
+ HashMap<PTable, PColumn> indexToIndexColumnMap =
null;
+ if (cascade) {
+ indexToIndexColumnMap =
getPTablePColumnHashMapForCascade(indexesPTable, willBeImmutableRows,
+ colDef, familyName,
indexToColumnSizeMap);
+ }
+
columns.add(column);
String pkName = null;
Short keySeq = null;
@@ -3808,6 +3826,13 @@ public class MetaDataClient {
}
colFamiliesForPColumnsToBeAdded.add(column.getFamilyName() == null ? null :
column.getFamilyName().getString());
addColumnMutation(schemaName, tableName, column,
colUpsert, null, pkName, keySeq, table.getBucketNum() != null);
+ // add new columns for given indexes one by one
+ if (cascade) {
+ for (PTable index: indexesPTable) {
+ LOGGER.info("Adding column
"+column.getName().getString()+" to "+index.getTableName().toString());
+ addColumnMutation(schemaName,
index.getTableName().getString(), indexToIndexColumnMap.get(index), colUpsert,
null, "", keySeq, index.getBucketNum() != null);
+ }
+ }
}
// Add any new PK columns to end of index PK
@@ -3873,7 +3898,18 @@ public class MetaDataClient {
tableMetaData.addAll(connection.getMutationState().toMutations(timeStamp).next().getSecond());
connection.rollback();
}
-
+
+ if (cascade) {
+ for (PTable index : indexesPTable) {
+ incrementTableSeqNum(index, index.getType(),
columnDefs.size(),
+ Boolean.FALSE,
+
metaPropertiesEvaluated.getUpdateCacheFrequency(),
+ metaPropertiesEvaluated.getPhoenixTTL());
+ }
+
tableMetaData.addAll(connection.getMutationState().toMutations(timeStamp).next().getSecond());
+ connection.rollback();
+ }
+
if (changingPhoenixTableProperty || columnDefs.size() > 0) {
incrementTableSeqNum(table, tableType, columnDefs.size(),
metaPropertiesEvaluated);
@@ -4050,6 +4086,80 @@ public class MetaDataClient {
}
}
+ private List<PTable> getIndexesPTableForCascade(List<NamedNode> indexes,
PTable table) throws SQLException {
+ boolean isView = table.getType().equals(PTableType.VIEW);
+ List<PTable> indexesPTable = new ArrayList<>();
+
+ // when indexes is null, that means ALL keyword is passed and
+ // we ll collect all global indexes for cascading
+ if(indexes == null) {
+ indexesPTable.addAll(table.getIndexes());
+ for (PTable index : table.getIndexes()) {
+ // a child view has access to its parents indexes,
+ // this if clause ensures we only get the indexes that
+ // are only created on the view itself.
+ if (index.getIndexType().equals(IndexType.LOCAL)
+ || (isView &&
index.getTableName().toString().contains("#"))) {
+ indexesPTable.remove(index);
+ }
+ }
+ } else {
+ List<String> indexesParam =
Lists.newArrayListWithExpectedSize(indexes.size());
+ for (NamedNode index : indexes) {
+ indexesParam.add(index.getName());
+ }
+ // gets the PTable for list of indexes passed in the function
+ // if all the names in parameter list are correct, indexesParam
list should be empty
+ // by end of the loop
+ for (PTable index : table.getIndexes()) {
+ if(index.getIndexType().equals(IndexType.LOCAL)) {
+ throw new SQLExceptionInfo
+
.Builder(SQLExceptionCode.NOT_SUPPORTED_CASCADE_FEATURE_LOCAL_INDEX)
+ .setTableName(index.getName().getString())
+ .build()
+ .buildException();
+ }
+ if (indexesParam.remove(index.getTableName().getString())) {
+ indexesPTable.add(index);
+ }
+ }
+ // indexesParam has index names that are not correct
+ if (!indexesParam.isEmpty()) {
+ throw new SQLExceptionInfo
+ .Builder(SQLExceptionCode.INCORRECT_INDEX_NAME)
+ .setTableName(StringUtils.join(",", indexesParam))
+ .build()
+ .buildException();
+ }
+ }
+ return indexesPTable;
+ }
+
+ private HashMap<PTable, PColumn>
getPTablePColumnHashMapForCascade(List<PTable> indexesPTable,
+ boolean willBeImmutableRows, ColumnDef colDef, String familyName,
Map<PTable, Integer> indexToColumnSizeMap) throws SQLException {
+ HashMap<PTable, PColumn> indexColumn;
+ if (colDef.isPK()) {
+ //only supported for non pk column
+ throw new SQLExceptionInfo.Builder(
+ SQLExceptionCode.NOT_SUPPORTED_CASCADE_FEATURE_PK)
+ .build()
+ .buildException();
+ }
+ indexColumn = new HashMap(indexesPTable.size());
+ PDataType indexColDataType =
IndexUtil.getIndexColumnDataType(colDef.isNull(), colDef.getDataType());
+ ColumnName
+ indexColName =
ColumnName.caseSensitiveColumnName(IndexUtil.getIndexColumnName(familyName,
colDef.getColumnDefName().getColumnName()));
+ ColumnDef indexColDef = FACTORY.columnDef(indexColName,
indexColDataType.getSqlTypeName(), colDef.isNull(), colDef.getMaxLength(),
colDef.getScale(), false, colDef.getSortOrder(), colDef.getExpression(),
colDef.isRowTimestamp());
+ // TODO: add support to specify tenant owned indexes in the DDL
statement with CASCADE executed with Global connection
+ for (PTable index : indexesPTable) {
+ int iPos = indexToColumnSizeMap.get(index);
+ PColumn iColumn = newColumn(iPos, indexColDef, null, "", false,
null, willBeImmutableRows);
+ indexColumn.put(index, iColumn);
+ indexToColumnSizeMap.put(index, iPos+1);
+ }
+ return indexColumn;
+ }
+
private void deleteMutexCells(String physicalSchemaName, String
physicalTableName, Set<String> acquiredColumnMutexSet) throws SQLException {
if (!acquiredColumnMutexSet.isEmpty()) {
for (String columnName : acquiredColumnMutexSet) {