blob: 6b5714894cb3f12d45c87ada260c479f78b43563 [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.end2end;
import static org.apache.phoenix.exception.SQLExceptionCode.CANNOT_MUTATE_TABLE;
import static org.apache.phoenix.util.PhoenixRuntime.TENANT_ID_ATTRIB;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import org.apache.commons.lang.ArrayUtils;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.coprocessor.PhoenixTransactionalProcessor;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.PName;
import org.apache.phoenix.schema.PNameFactory;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTableKey;
import org.apache.phoenix.schema.PTableType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class AlterTableWithViewsIT extends ParallelStatsDisabledIT {
private final boolean isMultiTenant;
private final boolean columnEncoded;
private final String TENANT_SPECIFIC_URL1 = getUrl() + ';' + TENANT_ID_ATTRIB + "=tenant1";
private final String TENANT_SPECIFIC_URL2 = getUrl() + ';' + TENANT_ID_ATTRIB + "=tenant2";
public AlterTableWithViewsIT(boolean isMultiTenant, boolean columnEncoded) {
this.isMultiTenant = isMultiTenant;
this.columnEncoded = columnEncoded;
}
@Parameters(name="AlterTableWithViewsIT_multiTenant={0}, columnEncoded={1}") // name is used by failsafe as file name in reports
public static Collection<Boolean[]> data() {
return Arrays.asList(new Boolean[][] {
{ false, false }, { false, true },
{ true, false }, { true, true } });
}
private String generateDDL(String format) {
return generateDDL("", format);
}
private String generateDDL(String options, String format) {
StringBuilder optionsBuilder = new StringBuilder(options);
if (!columnEncoded) {
if (optionsBuilder.length()!=0)
optionsBuilder.append(",");
optionsBuilder.append("COLUMN_ENCODED_BYTES=0");
}
if (isMultiTenant) {
if (optionsBuilder.length()!=0)
optionsBuilder.append(",");
optionsBuilder.append("MULTI_TENANT=true");
}
return String.format(format, isMultiTenant ? "TENANT_ID VARCHAR NOT NULL, " : "",
isMultiTenant ? "TENANT_ID, " : "", optionsBuilder.toString());
}
@Test
public void testAddNewColumnsToBaseTableWithViews() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
String tableName = generateUniqueName();
String viewOfTable = tableName + "_VIEW";
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + " ("
+ " %s ID char(1) NOT NULL,"
+ " COL1 integer NOT NULL,"
+ " COL2 bigint NOT NULL,"
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)"
+ " ) %s";
conn.createStatement().execute(generateDDL(ddlFormat));
assertTableDefinition(conn, tableName, PTableType.TABLE, null, 0, 3, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2");
viewConn.createStatement().execute("CREATE VIEW " + viewOfTable + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR ) AS SELECT * FROM " + tableName);
assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 0, 5, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
// adding a new pk column and a new regular column
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD COL3 varchar(10) PRIMARY KEY, COL4 integer");
assertTableDefinition(conn, tableName, PTableType.TABLE, null, columnEncoded ? 2 : 1, 5, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2", "COL3", "COL4");
assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 1, 7, 5, "ID", "COL1", "COL2", "COL3", "COL4", "VIEW_COL1", "VIEW_COL2");
}
}
@Test
public void testAlterPropertiesOfParentTable() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
String tableName = generateUniqueName();
String viewOfTable1 = tableName + "_VIEW1";
String viewOfTable2 = tableName + "_VIEW2";
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + " ("
+ " %s ID char(1) NOT NULL,"
+ " COL1 integer NOT NULL,"
+ " COL2 bigint NOT NULL,"
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)"
+ " ) %s ";
conn.createStatement().execute(generateDDL("UPDATE_CACHE_FREQUENCY=2", ddlFormat));
viewConn.createStatement().execute("CREATE VIEW " + viewOfTable1 + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR ) AS SELECT * FROM " + tableName);
viewConn.createStatement().execute("CREATE VIEW " + viewOfTable2 + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR ) AS SELECT * FROM " + tableName);
viewConn.createStatement().execute("ALTER VIEW " + viewOfTable2 + " SET UPDATE_CACHE_FREQUENCY = 1");
PhoenixConnection phoenixConn = conn.unwrap(PhoenixConnection.class);
PTable table = phoenixConn.getTable(new PTableKey(null, tableName));
PName tenantId = isMultiTenant ? PNameFactory.newName("tenant1") : null;
assertFalse(table.isImmutableRows());
assertEquals(2, table.getUpdateCacheFrequency());
PTable viewTable1 = viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable1));
assertFalse(viewTable1.isImmutableRows());
assertEquals(2, viewTable1.getUpdateCacheFrequency());
// query the view to force the table cache to be updated
viewConn.createStatement().execute("SELECT * FROM "+viewOfTable2);
PTable viewTable2 = viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable2));
assertFalse(viewTable2.isImmutableRows());
assertEquals(1, viewTable2.getUpdateCacheFrequency());
conn.createStatement().execute("ALTER TABLE " + tableName + " SET IMMUTABLE_ROWS=true, UPDATE_CACHE_FREQUENCY=3");
// query the views to force the table cache to be updated
viewConn.createStatement().execute("SELECT * FROM "+viewOfTable1);
viewConn.createStatement().execute("SELECT * FROM "+viewOfTable2);
phoenixConn = conn.unwrap(PhoenixConnection.class);
table = phoenixConn.getTable(new PTableKey(null, tableName));
assertTrue(table.isImmutableRows());
assertEquals(3, table.getUpdateCacheFrequency());
viewTable1 = viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable1));
assertTrue(viewTable1.isImmutableRows());
assertEquals(3, viewTable1.getUpdateCacheFrequency());
viewTable2 = viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable2));
assertTrue(viewTable2.isImmutableRows());
// update cache frequency is not propagated to the view since it was altered on the view
assertEquals(1, viewTable2.getUpdateCacheFrequency());
}
}
@Test
public void testDropColumnsFromBaseTableWithView() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
String tableName = generateUniqueName();
String viewOfTable = tableName + "_VIEW";
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + " %s ID char(1) NOT NULL,"
+ " COL1 integer NOT NULL," + " COL2 bigint NOT NULL,"
+ " COL3 varchar(10)," + " COL4 varchar(10)," + " COL5 varchar(10),"
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)" + " ) %s";
conn.createStatement().execute(generateDDL(ddlFormat));
assertTableDefinition(conn, tableName, PTableType.TABLE, null, 0, 6,
QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2", "COL3", "COL4",
"COL5");
viewConn.createStatement()
.execute(
"CREATE VIEW " + viewOfTable + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR ) AS SELECT * FROM " + tableName);
assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 0, 8, 6,
"ID", "COL1", "COL2", "COL3", "COL4", "COL5", "VIEW_COL1", "VIEW_COL2");
// drop two columns from the base table
conn.createStatement().execute("ALTER TABLE " + tableName + " DROP COLUMN COL3, COL5");
assertTableDefinition(conn, tableName, PTableType.TABLE, null, columnEncoded ? 2 : 1, 4,
QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2", "COL4");
assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 1, 6, 4,
"ID", "COL1", "COL2", "COL4", "VIEW_COL1", "VIEW_COL2");
}
}
@Test
public void testAddExistingViewColumnToBaseTableWithViews() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
conn.setAutoCommit(false);
viewConn.setAutoCommit(false);
String tableName = generateUniqueName();
String viewOfTable = tableName + "_VIEW";
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + " ("
+ " %s ID char(10) NOT NULL,"
+ " COL1 integer NOT NULL,"
+ " COL2 bigint NOT NULL,"
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)"
+ " ) %s";
conn.createStatement().execute(generateDDL(ddlFormat));
assertTableDefinition(conn, tableName, PTableType.TABLE, null, 0, 3, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2");
viewConn.createStatement().execute("CREATE VIEW " + viewOfTable + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR(256), VIEW_COL3 VARCHAR, VIEW_COL4 DECIMAL, VIEW_COL5 DECIMAL(10,2), VIEW_COL6 VARCHAR, CONSTRAINT pk PRIMARY KEY (VIEW_COL5, VIEW_COL6) ) AS SELECT * FROM " + tableName);
assertTableDefinition(conn,viewOfTable, PTableType.VIEW, tableName, 0, 9, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2", "VIEW_COL3", "VIEW_COL4", "VIEW_COL5", "VIEW_COL6");
// upsert single row into view
String dml = "UPSERT INTO " + viewOfTable + " VALUES(?,?,?,?,?, ?, ?, ?, ?)";
PreparedStatement stmt = viewConn.prepareStatement(dml);
stmt.setString(1, "view1");
stmt.setInt(2, 12);
stmt.setInt(3, 13);
stmt.setInt(4, 14);
stmt.setString(5, "view5");
stmt.setString(6, "view6");
stmt.setInt(7, 17);
stmt.setInt(8, 18);
stmt.setString(9, "view9");
stmt.execute();
viewConn.commit();
try {
// should fail because there is already a view column with same name of different type
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 char(10)");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because there is already a view column with same name with different scale
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL(10,1)");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because there is already a view column with same name with different length
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL(9,2)");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because there is already a view column with different length
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL2 VARCHAR");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
// validate that there were no columns added to the table or view, if its table is column encoded the sequence number changes when we increment the cq counter
assertTableDefinition(conn, tableName, PTableType.TABLE, null, columnEncoded ? 1 : 0, 3, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2");
assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 0, 9, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2", "VIEW_COL3", "VIEW_COL4", "VIEW_COL5", "VIEW_COL6");
if (columnEncoded) {
try {
// adding a key value column to the base table that already exists in the view is not allowed
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL4 DECIMAL, VIEW_COL2 VARCHAR(256)");
fail();
} catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
}
else {
// should succeed
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL4 DECIMAL, VIEW_COL2 VARCHAR(256)");
assertTableDefinition(conn, tableName, PTableType.TABLE, null, columnEncoded ? 2 : 1, 5, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2", "VIEW_COL4", "VIEW_COL2");
assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 1, 9, 5, "ID", "COL1", "COL2", "VIEW_COL4", "VIEW_COL2", "VIEW_COL1", "VIEW_COL3", "VIEW_COL5", "VIEW_COL6");
// query table
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName);
assertTrue(rs.next());
assertEquals("view1", rs.getString("ID"));
assertEquals(12, rs.getInt("COL1"));
assertEquals(13, rs.getInt("COL2"));
assertEquals("view5", rs.getString("VIEW_COL2"));
assertEquals(17, rs.getInt("VIEW_COL4"));
assertFalse(rs.next());
// query view
rs = stmt.executeQuery("SELECT * FROM " + viewOfTable);
assertTrue(rs.next());
assertEquals("view1", rs.getString("ID"));
assertEquals(12, rs.getInt("COL1"));
assertEquals(13, rs.getInt("COL2"));
assertEquals(14, rs.getInt("VIEW_COL1"));
assertEquals("view5", rs.getString("VIEW_COL2"));
assertEquals("view6", rs.getString("VIEW_COL3"));
assertEquals(17, rs.getInt("VIEW_COL4"));
assertEquals(18, rs.getInt("VIEW_COL5"));
assertEquals("view9", rs.getString("VIEW_COL6"));
assertFalse(rs.next());
}
}
}
@Test
public void testAddExistingViewPkColumnToBaseTableWithViews() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
conn.setAutoCommit(false);
viewConn.setAutoCommit(false);
String tableName = generateUniqueName();
String viewOfTable = tableName + "_VIEW";
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + " ("
+ " %s ID char(10) NOT NULL,"
+ " COL1 integer NOT NULL,"
+ " COL2 integer NOT NULL,"
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)"
+ " ) %s";
conn.createStatement().execute(generateDDL(ddlFormat));
assertTableDefinition(conn, tableName, PTableType.TABLE, null, 0, 3, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2");
viewConn.createStatement().execute("CREATE VIEW " + viewOfTable + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR(256) CONSTRAINT pk PRIMARY KEY (VIEW_COL1, VIEW_COL2)) AS SELECT * FROM " + tableName);
assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 0, 5, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
// upsert single row into view
String dml = "UPSERT INTO " + viewOfTable + " VALUES(?,?,?,?,?)";
PreparedStatement stmt = viewConn.prepareStatement(dml);
stmt.setString(1, "view1");
stmt.setInt(2, 12);
stmt.setInt(3, 13);
stmt.setInt(4, 14);
stmt.setString(5, "view5");
stmt.execute();
viewConn.commit();
try {
// should fail because there we have to add both VIEW_COL1 and VIEW_COL2 to the pk
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL2 VARCHAR(256) PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because there we have to add both VIEW_COL1 and VIEW_COL2 to the pk
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL(10,2) PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because there we have to add both VIEW_COL1 and VIEW_COL2 to the pk
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR(256) PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because there we have to add both VIEW_COL1 and VIEW_COL2 to the pk
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL(10,2) PRIMARY KEY, VIEW_COL2 VARCHAR(256)");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because there we have to add both VIEW_COL1 and VIEW_COL2 to the pk in the right order
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL2 VARCHAR(256) PRIMARY KEY, VIEW_COL1 DECIMAL(10,2) PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because there we have to add both VIEW_COL1 and VIEW_COL2 with the right sort order
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL(10,2) PRIMARY KEY DESC, VIEW_COL2 VARCHAR(256) PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
// add the pk column of the view to the base table
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL(10,2) PRIMARY KEY, VIEW_COL2 VARCHAR(256) PRIMARY KEY");
assertTableDefinition(conn, tableName, PTableType.TABLE, null, 1, 5, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
assertTableDefinition(conn, viewOfTable, PTableType.VIEW, tableName, 1, 5, 5, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
// query table
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName);
assertTrue(rs.next());
assertEquals("view1", rs.getString("ID"));
assertEquals(12, rs.getInt("COL1"));
assertEquals(13, rs.getInt("COL2"));
assertEquals(14, rs.getInt("VIEW_COL1"));
assertEquals("view5", rs.getString("VIEW_COL2"));
assertFalse(rs.next());
// query view
rs = stmt.executeQuery("SELECT * FROM " + viewOfTable);
assertTrue(rs.next());
assertEquals("view1", rs.getString("ID"));
assertEquals(12, rs.getInt("COL1"));
assertEquals(13, rs.getInt("COL2"));
assertEquals(14, rs.getInt("VIEW_COL1"));
assertEquals("view5", rs.getString("VIEW_COL2"));
assertFalse(rs.next());
}
}
@Test
public void testAddExistingViewPkColumnToBaseTableWithMultipleViews() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
String tableName = generateUniqueName();
String viewOfTable1 = tableName + "_VIEW1";
String viewOfTable2 = tableName + "_VIEW2";
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + "("
+ " %s ID char(10) NOT NULL,"
+ " COL1 integer NOT NULL,"
+ " COL2 integer NOT NULL,"
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)"
+ " ) %s";
conn.createStatement().execute(generateDDL(ddlFormat));
assertTableDefinition(conn, tableName, PTableType.TABLE, null, 0, 3, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2");
viewConn.createStatement().execute("CREATE VIEW " + viewOfTable1 + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR(256) CONSTRAINT pk PRIMARY KEY (VIEW_COL1, VIEW_COL2)) AS SELECT * FROM " + tableName);
assertTableDefinition(conn, viewOfTable1, PTableType.VIEW, tableName, 0, 5, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
viewConn.createStatement().execute("CREATE VIEW " + viewOfTable2 + " ( VIEW_COL3 VARCHAR(256), VIEW_COL4 DECIMAL(10,2) CONSTRAINT pk PRIMARY KEY (VIEW_COL3, VIEW_COL4)) AS SELECT * FROM " + tableName);
assertTableDefinition(conn, viewOfTable2, PTableType.VIEW, tableName, 0, 5, 3, "ID", "COL1", "COL2", "VIEW_COL3", "VIEW_COL4");
try {
// should fail because there are two view with different pk columns
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL PRIMARY KEY, VIEW_COL2 VARCHAR PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because there are two view with different pk columns
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL3 VARCHAR PRIMARY KEY, VIEW_COL4 DECIMAL PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because slot positions of pks are different
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL PRIMARY KEY, VIEW_COL2 VARCHAR PRIMARY KEY, VIEW_COL3 VARCHAR PRIMARY KEY, VIEW_COL4 DECIMAL PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because slot positions of pks are different
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL3 VARCHAR PRIMARY KEY, VIEW_COL4 DECIMAL PRIMARY KEY, VIEW_COL1 DECIMAL PRIMARY KEY, VIEW_COL2 VARCHAR PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
}
}
@Test
public void testAddExistingViewPkColumnToBaseTableWithMultipleViewsHavingSamePks() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn;
Connection viewConn2 = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn) {
conn.setAutoCommit(false);
viewConn.setAutoCommit(false);
viewConn2.setAutoCommit(false);
String tableName = generateUniqueName();
String viewOfTable1 = tableName + "_VIEW1";
String viewOfTable2 = tableName + "_VIEW2";
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + tableName + "("
+ " %s ID char(10) NOT NULL,"
+ " COL1 integer NOT NULL,"
+ " COL2 integer NOT NULL,"
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)"
+ " ) %s";
conn.createStatement().execute(generateDDL(ddlFormat));
assertTableDefinition(conn, tableName, PTableType.TABLE, null, 0, 3, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2");
viewConn.createStatement().execute("CREATE VIEW " + viewOfTable1 + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR(256) CONSTRAINT pk PRIMARY KEY (VIEW_COL1, VIEW_COL2)) AS SELECT * FROM " + tableName);
assertTableDefinition(conn, viewOfTable1, PTableType.VIEW, tableName, 0, 5, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
viewConn2.createStatement().execute("CREATE VIEW " + viewOfTable2 + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR(256) CONSTRAINT pk PRIMARY KEY (VIEW_COL1, VIEW_COL2)) AS SELECT * FROM " + tableName);
assertTableDefinition(conn, viewOfTable2, PTableType.VIEW, tableName, 0, 5, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
// upsert single row into both view
String dml = "UPSERT INTO " + viewOfTable1 + " VALUES(?,?,?,?,?)";
PreparedStatement stmt = viewConn.prepareStatement(dml);
stmt.setString(1, "view1");
stmt.setInt(2, 12);
stmt.setInt(3, 13);
stmt.setInt(4, 14);
stmt.setString(5, "view5");
stmt.execute();
viewConn.commit();
dml = "UPSERT INTO " + viewOfTable2 + " VALUES(?,?,?,?,?)";
stmt = viewConn2.prepareStatement(dml);
stmt.setString(1, "view1");
stmt.setInt(2, 12);
stmt.setInt(3, 13);
stmt.setInt(4, 14);
stmt.setString(5, "view5");
stmt.execute();
viewConn2.commit();
try {
// should fail because the view have two extra columns in their pk
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL(10,2) PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because the view have two extra columns in their pk
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL2 VARCHAR(256) PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
try {
// should fail because slot positions of pks are different
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL2 DECIMAL(10,2) PRIMARY KEY, VIEW_COL1 VARCHAR(256) PRIMARY KEY");
fail();
}
catch (SQLException e) {
assertEquals("Unexpected exception", CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode());
}
conn.createStatement().execute("ALTER TABLE " + tableName + " ADD VIEW_COL1 DECIMAL(10,2) PRIMARY KEY, VIEW_COL2 VARCHAR(256) PRIMARY KEY");
assertTableDefinition(conn, tableName, PTableType.TABLE, null, 1, 5, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
assertTableDefinition(conn, viewOfTable1, PTableType.VIEW, tableName, 1, 5, 5, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
assertTableDefinition(conn, viewOfTable2, PTableType.VIEW, tableName, 1, 5, 5, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
// query table
ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName);
assertTrue(rs.next());
assertEquals("view1", rs.getString("ID"));
assertEquals(12, rs.getInt("COL1"));
assertEquals(13, rs.getInt("COL2"));
assertEquals(14, rs.getInt("VIEW_COL1"));
assertEquals("view5", rs.getString("VIEW_COL2"));
assertFalse(rs.next());
// query both views
rs = viewConn.createStatement().executeQuery("SELECT * FROM " + viewOfTable1);
assertTrue(rs.next());
assertEquals("view1", rs.getString("ID"));
assertEquals(12, rs.getInt("COL1"));
assertEquals(13, rs.getInt("COL2"));
assertEquals(14, rs.getInt("VIEW_COL1"));
assertEquals("view5", rs.getString("VIEW_COL2"));
assertFalse(rs.next());
rs = viewConn2.createStatement().executeQuery("SELECT * FROM " + viewOfTable2);
assertTrue(rs.next());
assertEquals("view1", rs.getString("ID"));
assertEquals(12, rs.getInt("COL1"));
assertEquals(13, rs.getInt("COL2"));
assertEquals(14, rs.getInt("VIEW_COL1"));
assertEquals("view5", rs.getString("VIEW_COL2"));
assertFalse(rs.next());
}
}
public void assertTableDefinition(Connection conn, String tableName, PTableType tableType, String parentTableName, int sequenceNumber, int columnCount, int baseColumnCount, String... columnNames) throws Exception {
int delta = isMultiTenant ? 1 : 0;
String[] cols;
if (isMultiTenant) {
cols = (String[])ArrayUtils.addAll(new String[]{"TENANT_ID"}, columnNames);
}
else {
cols = columnNames;
}
AlterMultiTenantTableWithViewsIT.assertTableDefinition(conn, tableName, tableType, parentTableName, sequenceNumber, columnCount + delta,
baseColumnCount==QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT ? baseColumnCount : baseColumnCount +delta, cols);
}
public static String getSystemCatalogEntriesForTable(Connection conn, String tableName, String message) throws Exception {
StringBuilder sb = new StringBuilder(message);
sb.append("\n\n\n");
ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM \"SYSTEM\".\"CATALOG\" WHERE TABLE_NAME='"+ tableName +"'");
ResultSetMetaData metaData = rs.getMetaData();
int rowNum = 0;
while (rs.next()) {
sb.append(rowNum++).append("------\n");
for (int i = 1; i <= metaData.getColumnCount(); i++) {
sb.append("\t").append(metaData.getColumnLabel(i)).append("=").append(rs.getString(i)).append("\n");
}
sb.append("\n");
}
rs.close();
return sb.toString();
}
@Test
public void testAlteringViewThatHasChildViews() throws Exception {
String baseTable = generateUniqueName();
String childView = baseTable + "cildView";
String grandChildView = baseTable + "grandChildView";
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + baseTable + " ("
+ " %s PK2 VARCHAR NOT NULL, V1 VARCHAR, V2 VARCHAR "
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s PK2)"
+ " ) %s";
conn.createStatement().execute(generateDDL(ddlFormat));
String childViewDDL = "CREATE VIEW " + childView + " AS SELECT * FROM " + baseTable;
viewConn.createStatement().execute(childViewDDL);
String addColumnToChildViewDDL =
"ALTER VIEW " + childView + " ADD CHILD_VIEW_COL VARCHAR";
viewConn.createStatement().execute(addColumnToChildViewDDL);
String grandChildViewDDL =
"CREATE VIEW " + grandChildView + " AS SELECT * FROM " + childView;
viewConn.createStatement().execute(grandChildViewDDL);
// dropping base table column from child view should succeed
String dropColumnFromChildView = "ALTER VIEW " + childView + " DROP COLUMN V2";
viewConn.createStatement().execute(dropColumnFromChildView);
// dropping view specific column from child view should succeed
dropColumnFromChildView = "ALTER VIEW " + childView + " DROP COLUMN CHILD_VIEW_COL";
viewConn.createStatement().execute(dropColumnFromChildView);
// Adding column to view that has child views is allowed
String addColumnToChildView = "ALTER VIEW " + childView + " ADD V5 VARCHAR";
viewConn.createStatement().execute(addColumnToChildView);
// V5 column should be visible now for childView
viewConn.createStatement().execute("SELECT V5 FROM " + childView);
// However, column V5 shouldn't have propagated to grandChildView. Not till PHOENIX-2054 is fixed.
try {
viewConn.createStatement().execute("SELECT V5 FROM " + grandChildView);
} catch (SQLException e) {
assertEquals(SQLExceptionCode.COLUMN_NOT_FOUND.getErrorCode(), e.getErrorCode());
}
// dropping column from the grand child view, however, should work.
String dropColumnFromGrandChildView =
"ALTER VIEW " + grandChildView + " DROP COLUMN CHILD_VIEW_COL";
viewConn.createStatement().execute(dropColumnFromGrandChildView);
// similarly, dropping column inherited from the base table should work.
dropColumnFromGrandChildView = "ALTER VIEW " + grandChildView + " DROP COLUMN V2";
viewConn.createStatement().execute(dropColumnFromGrandChildView);
}
}
@Test
public void testDivergedViewsStayDiverged() throws Exception {
String baseTable = generateUniqueName();
String view1 = baseTable + "_VIEW1";
String view2 = baseTable + "_VIEW2";
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ;
Connection viewConn2 = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL2) : conn) {
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + baseTable + " ("
+ " %s PK1 VARCHAR NOT NULL, V1 VARCHAR, V2 VARCHAR "
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s PK1)"
+ " ) %s";
conn.createStatement().execute(generateDDL(ddlFormat));
String viewDDL = "CREATE VIEW " + view1 + " AS SELECT * FROM " + baseTable;
viewConn.createStatement().execute(viewDDL);
viewDDL = "CREATE VIEW " + view2 + " AS SELECT * FROM " + baseTable;
viewConn2.createStatement().execute(viewDDL);
// Drop the column inherited from base table to make it diverged
String dropColumn = "ALTER VIEW " + view1 + " DROP COLUMN V2";
viewConn.createStatement().execute(dropColumn);
String alterBaseTable = "ALTER TABLE " + baseTable + " ADD V3 VARCHAR";
conn.createStatement().execute(alterBaseTable);
// Column V3 shouldn't have propagated to the diverged view.
String sql = "SELECT V3 FROM " + view1;
try {
viewConn.createStatement().execute(sql);
} catch (SQLException e) {
assertEquals(SQLExceptionCode.COLUMN_NOT_FOUND.getErrorCode(), e.getErrorCode());
}
// However, column V3 should have propagated to the non-diverged view.
sql = "SELECT V3 FROM " + view2;
viewConn2.createStatement().execute(sql);
}
}
@Test
public void testMakeBaseTableTransactional() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
String baseTableName = "NONTXNTBL_" + generateUniqueName() + (isMultiTenant ? "0":"1");
String viewOfTable = baseTableName + "_VIEW";
String ddlFormat = "CREATE TABLE IF NOT EXISTS " + baseTableName + " ("
+ " %s ID char(1) NOT NULL,"
+ " COL1 integer NOT NULL,"
+ " COL2 bigint NOT NULL,"
+ " CONSTRAINT NAME_PK PRIMARY KEY (%s ID, COL1, COL2)"
+ " ) %s";
conn.createStatement().execute(generateDDL(ddlFormat));
assertTableDefinition(conn, baseTableName, PTableType.TABLE, null, 0, 3, QueryConstants.BASE_TABLE_BASE_COLUMN_COUNT, "ID", "COL1", "COL2");
viewConn.createStatement().execute("CREATE VIEW " + viewOfTable + " ( VIEW_COL1 DECIMAL(10,2), VIEW_COL2 VARCHAR ) AS SELECT * FROM "+baseTableName);
assertTableDefinition(conn, viewOfTable, PTableType.VIEW, baseTableName, 0, 5, 3, "ID", "COL1", "COL2", "VIEW_COL1", "VIEW_COL2");
PName tenantId = isMultiTenant ? PNameFactory.newName("tenant1") : null;
PhoenixConnection phoenixConn = conn.unwrap(PhoenixConnection.class);
HTableInterface htable = phoenixConn.getQueryServices().getTable(Bytes.toBytes(baseTableName));
assertFalse(htable.getTableDescriptor().getCoprocessors().contains(PhoenixTransactionalProcessor.class.getName()));
assertFalse(phoenixConn.getTable(new PTableKey(null, baseTableName)).isTransactional());
assertFalse(viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable)).isTransactional());
// make the base table transactional
conn.createStatement().execute("ALTER TABLE " + baseTableName + " SET TRANSACTIONAL=true");
// query the view to force the table cache to be updated
viewConn.createStatement().execute("SELECT * FROM " + viewOfTable);
htable = phoenixConn.getQueryServices().getTable(Bytes.toBytes(baseTableName));
assertTrue(htable.getTableDescriptor().getCoprocessors().contains(PhoenixTransactionalProcessor.class.getName()));
assertTrue(phoenixConn.getTable(new PTableKey(null, baseTableName)).isTransactional());
assertTrue(viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable)).isTransactional());
}
}
@Test
public void testAlterTablePropertyOnView() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
String baseTableName = "NONTXNTBL_" + generateUniqueName() + (isMultiTenant ? "0":"1");
String viewOfTable = baseTableName + "_VIEW";
String ddl = "CREATE TABLE " + baseTableName + " (\n"
+"%s ID VARCHAR(15) NOT NULL,\n"
+ " COL1 integer NOT NULL,"
+"CREATED_DATE DATE,\n"
+"CONSTRAINT PK PRIMARY KEY (%s ID, COL1)) %s";
conn.createStatement().execute(generateDDL(ddl));
ddl = "CREATE VIEW " + viewOfTable + " AS SELECT * FROM " + baseTableName;
viewConn.createStatement().execute(ddl);
try {
viewConn.createStatement().execute("ALTER VIEW " + viewOfTable + " SET IMMUTABLE_ROWS = true");
fail();
} catch (SQLException e) {
assertEquals(SQLExceptionCode.CANNOT_ALTER_TABLE_PROPERTY_ON_VIEW.getErrorCode(), e.getErrorCode());
}
viewConn.createStatement().execute("ALTER VIEW " + viewOfTable + " SET UPDATE_CACHE_FREQUENCY = 100");
viewConn.createStatement().execute("SELECT * FROM "+ viewOfTable);
PName tenantId = isMultiTenant ? PNameFactory.newName("tenant1") : null;
assertEquals(100, viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable)).getUpdateCacheFrequency());
try {
viewConn.createStatement().execute("ALTER VIEW " + viewOfTable + " SET APPEND_ONLY_SCHEMA = true");
fail();
} catch (SQLException e) {
assertEquals(SQLExceptionCode.CANNOT_ALTER_TABLE_PROPERTY_ON_VIEW.getErrorCode(), e.getErrorCode());
}
}
}
@Test
public void testAlterAppendOnlySchema() throws Exception {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn = isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1) : conn ) {
String baseTableName = "NONTXNTBL_" + generateUniqueName() + (isMultiTenant ? "0":"1");
String viewOfTable = baseTableName + "_VIEW";
String ddl = "CREATE TABLE " + baseTableName + " (\n"
+"%s ID VARCHAR(15) NOT NULL,\n"
+ " COL1 integer NOT NULL,"
+"CREATED_DATE DATE,\n"
+"CONSTRAINT PK PRIMARY KEY (%s ID, COL1)) %s";
conn.createStatement().execute(generateDDL(ddl));
ddl = "CREATE VIEW " + viewOfTable + " AS SELECT * FROM " + baseTableName;
viewConn.createStatement().execute(ddl);
PhoenixConnection phoenixConn = conn.unwrap(PhoenixConnection.class);
PTable table = phoenixConn.getTable(new PTableKey(null, baseTableName));
PName tenantId = isMultiTenant ? PNameFactory.newName("tenant1") : null;
assertFalse(table.isAppendOnlySchema());
PTable viewTable = viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable));
assertFalse(viewTable.isAppendOnlySchema());
try {
viewConn.createStatement().execute("ALTER VIEW " + viewOfTable + " SET APPEND_ONLY_SCHEMA = true");
fail();
}
catch(SQLException e){
assertEquals(SQLExceptionCode.CANNOT_ALTER_TABLE_PROPERTY_ON_VIEW.getErrorCode(), e.getErrorCode());
}
conn.createStatement().execute("ALTER TABLE " + baseTableName + " SET APPEND_ONLY_SCHEMA = true");
viewConn.createStatement().execute("SELECT * FROM "+viewOfTable);
phoenixConn = conn.unwrap(PhoenixConnection.class);
table = phoenixConn.getTable(new PTableKey(null, baseTableName));
assertTrue(table.isAppendOnlySchema());
viewTable = viewConn.unwrap(PhoenixConnection.class).getTable(new PTableKey(tenantId, viewOfTable));
assertTrue(viewTable.isAppendOnlySchema());
}
}
}