/* * 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.jdbc.PhoenixDatabaseMetaData.COLUMN_FAMILY; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_NAME; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_QUALIFIER; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_QUALIFIER_COUNTER; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_NAME; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_SCHEM; import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.TABLE_SEQ_NUM; import static org.apache.phoenix.query.QueryConstants.DEFAULT_COLUMN_FAMILY; import static org.apache.phoenix.query.QueryConstants.ENCODED_CQ_COUNTER_INITIAL_VALUE; import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES; import static org.apache.phoenix.util.TestUtil.closeConnection; import static org.apache.phoenix.util.TestUtil.closeStatement; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; 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.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.Collection; import java.util.Properties; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; import org.apache.hadoop.hbase.client.TableDescriptor; import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.exception.PhoenixParserException; import org.apache.phoenix.exception.SQLExceptionCode; import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; import org.apache.phoenix.query.BaseTest; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.schema.PTable; import org.apache.phoenix.schema.PTable.EncodedCQCounter; import org.apache.phoenix.schema.PTable.QualifierEncodingScheme; import org.apache.phoenix.schema.PTableKey; import org.apache.phoenix.schema.TableNotFoundException; import org.apache.phoenix.transaction.TransactionFactory; import org.apache.phoenix.util.IndexUtil; import org.apache.phoenix.util.PropertiesUtil; import org.apache.phoenix.util.SchemaUtil; import org.apache.phoenix.util.TestUtil; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; /** * * A lot of tests in this class test HBase level properties. As a result, * tests need to have non-overlapping table names. The option of * disabling and dropping underlying HBase tables at the end of each test * to avoid the overlap makes the test class really slow. By having the * test class run in its own cluster and having non overlapping table names * we don't need to worry about dropping the tables between each test * or at the end of test class. * */ @RunWith(Parameterized.class) public class AlterTableIT extends ParallelStatsDisabledIT { private String schemaName; private String dataTableName; private String indexTableName; private String dataTableFullName; private String indexTableFullName; private String tableDDLOptions; private final boolean columnEncoded; public AlterTableIT(boolean columnEncoded) { this.columnEncoded = columnEncoded; this.tableDDLOptions = columnEncoded ? "" : "COLUMN_ENCODED_BYTES=0"; } @Parameters(name="AlterTableIT_columnEncoded={0}") // name is used by failsafe as file name in reports public static synchronized Collection<Boolean> data() { return Arrays.asList( false, true); } @Before public void setupTableNames() throws Exception { schemaName = ""; dataTableName = generateUniqueName(); indexTableName = "I_" + generateUniqueName(); dataTableFullName = SchemaUtil.getTableName(schemaName, dataTableName); indexTableFullName = SchemaUtil.getTableName(schemaName, indexTableName); } @Test public void testAlterTableWithVarBinaryKey() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); conn.setAutoCommit(false); String ddl = "CREATE TABLE " + dataTableFullName + " (a_string varchar not null, a_binary varbinary not null, col1 integer" + " CONSTRAINT pk PRIMARY KEY (a_string, a_binary)) " + tableDDLOptions; createTestTable(getUrl(), ddl); conn.createStatement().execute("ALTER TABLE " + dataTableFullName + " SET DISABLE_WAL = true"); try { ddl = "ALTER TABLE " + dataTableFullName + " ADD b_string VARCHAR NULL PRIMARY KEY"; PreparedStatement stmt = conn.prepareStatement(ddl); stmt.execute(); fail("Should have caught bad alter."); } catch (SQLException e) { assertEquals(SQLExceptionCode.VARBINARY_LAST_PK.getErrorCode(), e.getErrorCode()); } finally { conn.close(); } } @Test public void testDropSystemTable() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); try { try { conn.createStatement().executeUpdate( "DROP TABLE " + PhoenixDatabaseMetaData.SYSTEM_CATALOG); fail("Should not be allowed to drop a system table"); } catch (SQLException e) { assertEquals(SQLExceptionCode.CANNOT_MUTATE_TABLE.getErrorCode(), e.getErrorCode()); } } finally { conn.close(); } } @Test public void testAddVarCharColToPK() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); conn.setAutoCommit(false); try { String ddl = "CREATE TABLE " + dataTableFullName + " (a_string varchar not null, col1 integer" + " CONSTRAINT pk PRIMARY KEY (a_string)) " + tableDDLOptions; conn.createStatement().execute(ddl); String dml = "UPSERT INTO " + dataTableFullName + " VALUES(?)"; PreparedStatement stmt = conn.prepareStatement(dml); stmt.setString(1, "b"); stmt.execute(); stmt.setString(1, "a"); stmt.execute(); conn.commit(); String query = "SELECT * FROM " + dataTableFullName; ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("a",rs.getString(1)); assertTrue(rs.next()); assertEquals("b",rs.getString(1)); assertFalse(rs.next()); ddl = "ALTER TABLE " + dataTableFullName + " ADD b_string VARCHAR NULL PRIMARY KEY "; conn.createStatement().execute(ddl); query = "SELECT * FROM " + dataTableFullName + " WHERE a_string = 'a' AND b_string IS NULL"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("a",rs.getString(1)); assertFalse(rs.next()); dml = "UPSERT INTO " + dataTableFullName + " VALUES(?)"; stmt = conn.prepareStatement(dml); stmt.setString(1, "c"); stmt.execute(); conn.commit(); query = "SELECT * FROM " + dataTableFullName + " WHERE a_string = 'c' AND b_string IS NULL"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("c",rs.getString(1)); assertFalse(rs.next()); dml = "UPSERT INTO " + dataTableFullName + "(a_string,col1) VALUES(?,?)"; stmt = conn.prepareStatement(dml); stmt.setString(1, "a"); stmt.setInt(2, 5); stmt.execute(); conn.commit(); query = "SELECT a_string,col1 FROM " + dataTableFullName + " WHERE a_string = 'a' AND b_string IS NULL"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("a",rs.getString(1)); assertEquals(5,rs.getInt(2)); // TODO: figure out why this flaps assertFalse(rs.next()); } finally { conn.close(); } } @Test public void testSetPropertyAndAddColumnForNewColumnFamily() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); String ddl = "CREATE TABLE " + dataTableFullName + " (a_string varchar not null, col1 integer" + " CONSTRAINT pk PRIMARY KEY (a_string)) " + tableDDLOptions; try { conn.createStatement().execute(ddl); conn.createStatement().execute("ALTER TABLE " + dataTableFullName + " ADD CF.col2 integer CF.IN_MEMORY=true"); try (Admin admin = conn.unwrap(PhoenixConnection.class).getQueryServices().getAdmin()) { ColumnFamilyDescriptor[] columnFamilies = admin.getDescriptor(TableName.valueOf(dataTableFullName)).getColumnFamilies(); assertEquals(2, columnFamilies.length); assertEquals("0", columnFamilies[0].getNameAsString()); assertFalse(columnFamilies[0].isInMemory()); assertEquals("CF", columnFamilies[1].getNameAsString()); assertTrue(columnFamilies[1].isInMemory()); } } finally { conn.close(); } } private String generateDDLOptions(String options) { StringBuilder sb = new StringBuilder(); if (!options.isEmpty()) { sb.append(options); } if (!tableDDLOptions.isEmpty()) { if (sb.length()!=0) sb.append(","); sb.append(tableDDLOptions); } return sb.toString(); } @Test public void testSetSaltedTableAsImmutable() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); conn.setAutoCommit(false); try { String ddl = "CREATE TABLE " + dataTableFullName + "(\n" + " SENDER_ID UNSIGNED_LONG NOT NULL,\n" + " RECIPIENT_ID UNSIGNED_LONG NOT NULL,\n" + " M_TIMESTAMP DATE NOT NULL,\n" + " ROW_ID UNSIGNED_LONG NOT NULL,\n" + " IS_READ TINYINT,\n" + " IS_DELETED TINYINT,\n" + " VISIBILITY TINYINT,\n" + " B.SENDER_IP VARCHAR,\n" + " B.JSON VARCHAR,\n" + " B.M_TEXT VARCHAR\n" + " CONSTRAINT ROWKEY PRIMARY KEY\n" + "(SENDER_ID,RECIPIENT_ID,M_TIMESTAMP DESC,ROW_ID))\n" + generateDDLOptions("SALT_BUCKETS=4"); conn.createStatement().execute(ddl); ddl = "ALTER TABLE " + dataTableFullName + " SET IMMUTABLE_ROWS=true"; conn.createStatement().execute(ddl); conn.createStatement().executeQuery("select count(*) from " + dataTableFullName).next(); } finally { conn.close(); } } @Test public void testDropColumnFromSaltedTable() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); conn.setAutoCommit(false); try { String ddl = "CREATE TABLE " + dataTableFullName + " (\n" + " SENDER_ID UNSIGNED_LONG NOT NULL,\n" + " RECIPIENT_ID UNSIGNED_LONG NOT NULL,\n" + " M_TIMESTAMP DATE NOT NULL,\n" + " ROW_ID UNSIGNED_LONG NOT NULL,\n" + " IS_READ TINYINT,\n" + " IS_DELETED TINYINT,\n" + " VISIBILITY TINYINT,\n" + " B.SENDER_IP VARCHAR,\n" + " B.JSON VARCHAR,\n" + " B.M_TEXT VARCHAR\n" + " CONSTRAINT ROWKEY PRIMARY KEY\n" + "(SENDER_ID,RECIPIENT_ID,M_TIMESTAMP DESC,ROW_ID))\n" + generateDDLOptions("SALT_BUCKETS=4"); conn.createStatement().execute(ddl); ddl = "ALTER TABLE " + dataTableFullName + " DROP COLUMN B.JSON"; conn.createStatement().execute(ddl); conn.createStatement().executeQuery("select count(*) from " + dataTableFullName).next(); } finally { conn.close(); } } @Test public void testAddVarCols() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { conn.setAutoCommit(false); String ddl = "CREATE TABLE " + dataTableFullName + " (a_string varchar not null, col1 integer" + " CONSTRAINT pk PRIMARY KEY (a_string)) " + tableDDLOptions; conn.createStatement().execute(ddl); String dml = "UPSERT INTO " + dataTableFullName + " VALUES(?)"; PreparedStatement stmt = conn.prepareStatement(dml); stmt.setString(1, "b"); stmt.execute(); stmt.setString(1, "a"); stmt.execute(); conn.commit(); String query = "SELECT * FROM " + dataTableFullName; ResultSet rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("a",rs.getString(1)); assertTrue(rs.next()); assertEquals("b",rs.getString(1)); assertFalse(rs.next()); query = "SELECT * FROM " + dataTableFullName + " WHERE a_string = 'a' "; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("a",rs.getString(1)); ddl = "ALTER TABLE " + dataTableFullName + " ADD c1.col2 VARCHAR, c1.col3 integer, " + "c2.col4 integer"; conn.createStatement().execute(ddl); // If we are adding two columns but one of them already exists, the other one should // not be added ddl = "ALTER TABLE " + dataTableFullName + " ADD col5 integer, c1.col2 VARCHAR"; try { conn.createStatement().execute(ddl); fail(); } catch (SQLException e) { assertEquals(SQLExceptionCode.COLUMN_EXIST_IN_DEF.getErrorCode(), e.getErrorCode()); } query = "SELECT col5 FROM " + dataTableFullName; try { conn.createStatement().executeQuery(query); fail(); } catch(SQLException e) { assertTrue(e.getMessage(), e.getMessage().contains("ERROR 504 (42703): Undefined column.")); } dml = "UPSERT INTO " + dataTableFullName + " VALUES(?, ?, ?, ?, ?)"; stmt = conn.prepareStatement(dml); stmt.setString(1, "c"); stmt.setInt(2, 100); stmt.setString(3, "d"); stmt.setInt(4, 101); stmt.setInt(5, 102); stmt.execute(); conn.commit(); query = "SELECT * FROM " + dataTableFullName + " WHERE a_string = 'c' "; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("c",rs.getString(1)); assertEquals(100,rs.getInt(2)); assertEquals("d",rs.getString(3)); assertEquals(101,rs.getInt(4)); assertEquals(102,rs.getInt(5)); assertFalse(rs.next()); query = "SELECT c1.* FROM " + dataTableFullName + " WHERE a_string = 'c' "; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("d",rs.getString(1)); assertEquals(101,rs.getInt(2)); assertFalse(rs.next()); // If we are adding two columns with "IF NOT EXISTS" and one of them already exists, // the other one should be added ddl = "ALTER TABLE " + dataTableFullName + " ADD IF NOT EXISTS col5 integer, " + "c1.col2 VARCHAR"; conn.createStatement().execute(ddl); query = "SELECT col5 FROM " + dataTableFullName; conn.createStatement().executeQuery(query); dml = "UPSERT INTO " + dataTableFullName + "(a_string, col1, col5) VALUES(?, ?, ?)"; stmt = conn.prepareStatement(dml); stmt.setString(1, "e"); stmt.setInt(2, 200); stmt.setInt(3, 201); stmt.execute(); conn.commit(); query = "SELECT a_string, col1, col5 FROM " + dataTableFullName + " WHERE a_string = 'e' "; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("e",rs.getString(1)); assertEquals(200,rs.getInt(2)); assertEquals(201,rs.getInt(3)); assertFalse(rs.next()); } } @Test public void testDropVarCols() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); conn.setAutoCommit(false); try { String ddl = "CREATE TABLE " + dataTableFullName + " " + " (a_string varchar not null, col1 integer, cf1.col2 integer" + " CONSTRAINT pk PRIMARY KEY (a_string)) " + tableDDLOptions; conn.createStatement().execute(ddl); ddl = "ALTER TABLE " + dataTableFullName + " DROP COLUMN col1"; conn.createStatement().execute(ddl); ddl = "ALTER TABLE " + dataTableFullName + " DROP COLUMN cf1.col2"; conn.createStatement().execute(ddl); } finally { conn.close(); } } @Test public void testDisallowAddingNotNullableColumnNotPartOfPkForExistingTable() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = null; PreparedStatement stmt = null; try { conn = DriverManager.getConnection(getUrl(), props); conn.setAutoCommit(false); try { String ddl = "CREATE TABLE " + dataTableFullName + " " + " (a_string varchar not null, col1 integer, cf1.col2 integer" + " CONSTRAINT pk PRIMARY KEY (a_string)) " + tableDDLOptions; conn.createStatement().execute(ddl); } finally { closeStatement(stmt); } try { stmt = conn.prepareStatement("ALTER TABLE " + dataTableFullName + " ADD b_string VARCHAR NOT NULL"); stmt.execute(); fail("Should have failed since altering a table by adding a non-nullable column is not allowed."); } catch (SQLException e) { assertEquals(SQLExceptionCode.KEY_VALUE_NOT_NULL.getErrorCode(), e.getErrorCode()); } finally { closeStatement(stmt); } } finally { closeConnection(conn); } } private void asssertIsWALDisabled(Connection conn, String fullTableName, boolean expectedValue) throws SQLException { PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class); assertEquals(expectedValue, pconn.getTable(new PTableKey(pconn.getTenantId(), fullTableName)).isWALDisabled()); } @Test public void testDisableWAL1() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); try { conn.createStatement() .execute( "CREATE TABLE " + dataTableFullName + " (a_string varchar not null, col1 integer, cf1.col2 integer, col3 integer , cf2.col4 integer " + " CONSTRAINT pk PRIMARY KEY (a_string)) " + generateDDLOptions("immutable_rows=true, disable_wal=true" + (!columnEncoded ? ",IMMUTABLE_STORAGE_SCHEME=" + PTable.ImmutableStorageScheme.ONE_CELL_PER_COLUMN : ""))); Connection conn2 = DriverManager.getConnection(getUrl(), props); String query = "SELECT * FROM " + dataTableFullName; ResultSet rs = conn2.createStatement().executeQuery(query); assertFalse(rs.next()); asssertIsWALDisabled(conn2,dataTableFullName, true); conn2.close(); asssertIsWALDisabled(conn,dataTableFullName, true); conn.createStatement().execute("CREATE INDEX " + indexTableName + " ON " + dataTableFullName + " (col1) include (cf1.col2) SALT_BUCKETS=4"); conn2 = DriverManager.getConnection(getUrl(), props); query = "SELECT * FROM " + indexTableFullName; rs = conn2.createStatement().executeQuery(query); asssertIsWALDisabled(conn2,indexTableFullName, false); assertFalse(rs.next()); conn2.close(); asssertIsWALDisabled(conn,indexTableFullName, false); } finally { conn.close(); } } @Test public void testDisableWAL2() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); try { conn.createStatement() .execute( "CREATE TABLE " + dataTableFullName + " (a_string varchar not null, col1 integer, cf1.col2 integer, col3 integer , cf2.col4 integer " + " CONSTRAINT pk PRIMARY KEY (a_string))" + generateDDLOptions("immutable_rows=true" + (!columnEncoded ? ",IMMUTABLE_STORAGE_SCHEME=" + PTable.ImmutableStorageScheme.ONE_CELL_PER_COLUMN : ""))); Connection conn2 = DriverManager.getConnection(getUrl(), props); String query = "SELECT * FROM " + dataTableFullName; ResultSet rs = conn2.createStatement().executeQuery(query); assertFalse(rs.next()); asssertIsWALDisabled(conn,dataTableFullName, false); conn2.close(); asssertIsWALDisabled(conn,dataTableFullName, false); conn.createStatement().execute("CREATE INDEX " + indexTableName + " ON " + dataTableFullName + " (col1) include (cf1.col2) SALT_BUCKETS=4"); conn2 = DriverManager.getConnection(getUrl(), props); query = "SELECT * FROM " + indexTableFullName; rs = conn2.createStatement().executeQuery(query); asssertIsWALDisabled(conn2,indexTableFullName, false); assertFalse(rs.next()); conn2.close(); asssertIsWALDisabled(conn,indexTableFullName, false); } finally { conn.close(); } } @Test public void testDisableWAL3() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); try { conn.createStatement() .execute( "CREATE TABLE " + dataTableFullName + " (a_string varchar not null, col1 integer, cf1.col2 integer, col3 integer , cf2.col4 integer " + " CONSTRAINT pk PRIMARY KEY (a_string)) " + tableDDLOptions); Connection conn2 = DriverManager.getConnection(getUrl(), props); String query = "SELECT * FROM " + dataTableFullName; ResultSet rs = conn2.createStatement().executeQuery(query); assertFalse(rs.next()); asssertIsWALDisabled(conn2,dataTableFullName, false); conn2.close(); asssertIsWALDisabled(conn,dataTableFullName, false); conn.createStatement().execute( "CREATE INDEX " + indexTableName + " ON " + dataTableFullName + " (col1) include (cf1.col2) SALT_BUCKETS=4"); conn2 = DriverManager.getConnection(getUrl(), props); query = "SELECT * FROM " + indexTableFullName; rs = conn2.createStatement().executeQuery(query); asssertIsWALDisabled(conn2,indexTableFullName, false); assertFalse(rs.next()); conn2.close(); asssertIsWALDisabled(conn,indexTableFullName, false); } finally { conn.close(); } } @Test public void testDropColumnsWithImutability() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); conn.setAutoCommit(false); try { conn.createStatement() .execute( "CREATE TABLE " + dataTableFullName + " (a_string varchar not null, col1 integer, cf1.col2 integer, col3 integer , cf2.col4 integer " + " CONSTRAINT pk PRIMARY KEY (a_string)) " + generateDDLOptions("immutable_rows=true , SALT_BUCKETS=3 " + (!columnEncoded ? ",IMMUTABLE_STORAGE_SCHEME=" + PTable.ImmutableStorageScheme.ONE_CELL_PER_COLUMN : ""))); String query = "SELECT * FROM " + dataTableFullName; ResultSet rs = conn.createStatement().executeQuery(query); assertFalse(rs.next()); conn.createStatement().execute("CREATE INDEX " + indexTableName + " ON " + dataTableFullName + " (col1) include (cf1.col2) SALT_BUCKETS=4"); query = "SELECT * FROM " + indexTableFullName; rs = conn.createStatement().executeQuery(query); assertFalse(rs.next()); String dml = "UPSERT INTO " + dataTableFullName + " VALUES(?,?,?,?,?)"; PreparedStatement stmt = conn.prepareStatement(dml); stmt.setString(1, "b"); stmt.setInt(2, 10); stmt.setInt(3, 20); stmt.setInt(4, 30); stmt.setInt(5, 40); stmt.execute(); stmt.setString(1, "a"); stmt.setInt(2, 101); stmt.setInt(3, 201); stmt.setInt(4, 301); stmt.setInt(5, 401); stmt.execute(); conn.commit(); query = "SELECT * FROM " + dataTableFullName + " order by col1"; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertEquals("b", rs.getString(1)); assertTrue(rs.next()); assertEquals("a", rs.getString(1)); assertFalse(rs.next()); String ddl = "ALTER TABLE " + dataTableFullName + " DROP COLUMN IF EXISTS col2,col3"; conn.createStatement().execute(ddl); ddl = "ALTER TABLE " + dataTableFullName + " DROP COLUMN a_string,col1"; try{ conn.createStatement().execute(ddl); fail(); } catch (SQLException e) { assertEquals(SQLExceptionCode.CANNOT_DROP_PK.getErrorCode(), e.getErrorCode()); } ddl = "ALTER TABLE " + dataTableFullName + " DROP COLUMN col4,col5"; try { conn.createStatement().execute(ddl); fail(); } catch (SQLException e) { assertEquals(SQLExceptionCode.COLUMN_NOT_FOUND.getErrorCode(), e.getErrorCode()); assertTrue(e.getMessage(), e.getMessage().contains("ERROR 504 (42703): Undefined column. columnName="+dataTableFullName+".COL5")); } ddl = "ALTER TABLE " + dataTableFullName + " DROP COLUMN IF EXISTS col1"; conn.createStatement().execute(ddl); query = "SELECT * FROM " + indexTableFullName; try { rs = conn.createStatement().executeQuery(query); fail(); } catch (TableNotFoundException e) {} query = "select col4 FROM " + dataTableFullName; rs = conn.createStatement().executeQuery(query); assertTrue(rs.next()); assertTrue(rs.next()); query = "select col2,col3 FROM " + dataTableFullName; try { rs = conn.createStatement().executeQuery(query); fail(); } catch (SQLException e) { assertEquals(SQLExceptionCode.COLUMN_NOT_FOUND.getErrorCode(), e.getErrorCode()); } } finally { conn.close(); } } @Test public void alterTableFromDifferentClient() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn3 = DriverManager.getConnection(getUrl(), props); // here we insert into the orig schema with one column Connection conn1 = DriverManager.getConnection(getUrl(), props); conn1.createStatement().execute("create table " + dataTableFullName + "(id VARCHAR PRIMARY KEY, field1 BIGINT) " + tableDDLOptions); PreparedStatement stmtInsert1 = conn1.prepareStatement("upsert into " + dataTableFullName + " (id, field1) values ( ?, ?)"); stmtInsert1.setString(1, "key1"); stmtInsert1.setLong(2, 1L); stmtInsert1.execute(); conn1.commit(); stmtInsert1.close(); // Do the alter through a separate client. conn3.createStatement().execute("alter table " + dataTableFullName + " add field2 BIGINT"); //Connection conn1 = DriverManager.getConnection(getUrl(), props); PreparedStatement pstmt2 = conn1.prepareStatement("upsert into " + dataTableFullName + " (id, field1, field2) values ( ?, ?, ?)"); pstmt2.setString(1, "key2"); pstmt2.setLong(2, 2L); pstmt2.setLong(3, 2L); pstmt2.execute(); conn1.commit(); pstmt2.close(); conn1.close(); } @Test public void testAddColumnsUsingNewConnection() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); String ddl = "CREATE TABLE " + dataTableFullName + " (\n" +"ID1 VARCHAR(15) NOT NULL,\n" +"ID2 VARCHAR(15) NOT NULL,\n" +"CREATED_DATE DATE,\n" +"CREATION_TIME BIGINT,\n" +"LAST_USED DATE,\n" +"CONSTRAINT PK PRIMARY KEY (ID1, ID2)) " + tableDDLOptions; Connection conn1 = DriverManager.getConnection(getUrl(), props); conn1.createStatement().execute(ddl); ddl = "ALTER TABLE " + dataTableFullName + " ADD STRING VARCHAR, STRING_DATA_TYPES VARCHAR"; conn1.createStatement().execute(ddl); ddl = "ALTER TABLE " + dataTableFullName + " DROP COLUMN STRING, STRING_DATA_TYPES"; conn1.createStatement().execute(ddl); ddl = "ALTER TABLE " + dataTableFullName + " ADD STRING_ARRAY1 VARCHAR[]"; conn1.createStatement().execute(ddl); conn1.close(); } @Test public void testAddMultipleColumns() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); String ddl = "CREATE TABLE " + dataTableFullName + " (\n" +"ID VARCHAR(15) PRIMARY KEY,\n" +"COL1 BIGINT) " + tableDDLOptions; Connection conn1 = DriverManager.getConnection(getUrl(), props); conn1.createStatement().execute(ddl); conn1.createStatement().execute("CREATE INDEX " + indexTableName + " ON " + dataTableFullName + "(COL1)"); ddl = "ALTER TABLE " + dataTableFullName + " ADD COL2 VARCHAR PRIMARY KEY, COL3 VARCHAR PRIMARY KEY"; conn1.createStatement().execute(ddl); ResultSet rs = conn1.getMetaData().getColumns("", "", dataTableFullName, null); assertTrue(rs.next()); assertEquals("ID",rs.getString(4)); assertTrue(rs.next()); assertEquals("COL1",rs.getString(4)); assertTrue(rs.next()); assertEquals("COL2",rs.getString(4)); assertTrue(rs.next()); assertEquals("COL3",rs.getString(4)); assertFalse(rs.next()); rs = conn1.createStatement().executeQuery("SELECT COLUMN_COUNT FROM \"SYSTEM\".\"CATALOG\"\n" + "WHERE TENANT_ID IS NULL AND\n" + "(TABLE_SCHEM, TABLE_NAME) = ('" + schemaName + "','"+ dataTableName + "') AND\n" + "COLUMN_FAMILY IS NULL AND COLUMN_NAME IS NULL"); assertTrue(rs.next()); assertEquals(4,rs.getInt(1)); assertFalse(rs.next()); rs = conn1.createStatement().executeQuery("SELECT COLUMN_COUNT FROM \"SYSTEM\".\"CATALOG\"\n" + "WHERE TENANT_ID IS NULL AND\n" + "(TABLE_SCHEM, TABLE_NAME) = ('" + schemaName + "','"+ indexTableName + "') AND\n" + "COLUMN_FAMILY IS NULL AND COLUMN_NAME IS NULL"); assertTrue(rs.next()); assertEquals(4,rs.getInt(1)); assertFalse(rs.next()); conn1.createStatement().execute("UPSERT INTO " + dataTableFullName + " VALUES ('a',2,'a','b')"); conn1.createStatement().execute("UPSERT INTO " + dataTableFullName + " VALUES ('b',3,'b','c')"); conn1.createStatement().execute("UPSERT INTO " + dataTableFullName + " VALUES ('c',4,'c','c')"); conn1.commit(); rs = conn1.createStatement().executeQuery("SELECT ID,COL1 FROM " + dataTableFullName + " WHERE COL1=3"); assertTrue(rs.next()); assertEquals("b",rs.getString(1)); assertEquals(3,rs.getLong(2)); assertFalse(rs.next()); conn1.close(); } @Test public void testDropMultipleColumns() throws Exception { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); String ddl = "CREATE TABLE " + dataTableFullName + " (\n" + "ID VARCHAR(15) PRIMARY KEY,\n" + "COL1 BIGINT," + "COL2 BIGINT," + "COL3 BIGINT," + "COL4 BIGINT) " + tableDDLOptions; Connection conn1 = DriverManager.getConnection(getUrl(), props); conn1.createStatement().execute(ddl); conn1.createStatement().execute("CREATE INDEX " + indexTableName + " ON " + dataTableFullName + "(COL1) INCLUDE (COL2,COL3,COL4)"); ddl = "ALTER TABLE " + dataTableFullName + " DROP COLUMN COL2, COL3"; conn1.createStatement().execute(ddl); ResultSet rs = conn1.getMetaData().getColumns("", "", dataTableFullName, null); assertTrue(rs.next()); assertEquals("ID",rs.getString(4)); assertTrue(rs.next()); assertEquals("COL1",rs.getString(4)); assertTrue(rs.next()); assertEquals("COL4",rs.getString(4)); assertFalse(rs.next()); rs = conn1.createStatement().executeQuery("SELECT COLUMN_COUNT FROM \"SYSTEM\".\"CATALOG\"\n" + "WHERE TENANT_ID IS NULL AND\n" + "(TABLE_SCHEM, TABLE_NAME) = ('" + schemaName + "','"+ dataTableName + "') AND\n" + "COLUMN_FAMILY IS NULL AND COLUMN_NAME IS NULL"); assertTrue(rs.next()); assertEquals(3,rs.getInt(1)); assertFalse(rs.next()); rs = conn1.createStatement().executeQuery("SELECT COLUMN_COUNT FROM \"SYSTEM\".\"CATALOG\"\n" + "WHERE TENANT_ID IS NULL AND\n" + "(TABLE_SCHEM, TABLE_NAME) = ('" + schemaName + "','"+ indexTableName + "') AND\n" + "COLUMN_FAMILY IS NULL AND COLUMN_NAME IS NULL"); assertTrue(rs.next()); assertEquals(3,rs.getInt(1)); assertFalse(rs.next()); conn1.createStatement().execute("UPSERT INTO " + dataTableFullName + " VALUES ('a',2, 20)"); conn1.createStatement().execute("UPSERT INTO " + dataTableFullName + " VALUES ('b',3, 30)"); conn1.createStatement().execute("UPSERT INTO " + dataTableFullName + " VALUES ('c',4, 40)"); conn1.commit(); rs = conn1.createStatement().executeQuery("SELECT ID,COL1,COL4 FROM " + dataTableFullName + " WHERE COL1=3"); assertTrue(rs.next()); assertEquals("b",rs.getString(1)); assertEquals(3,rs.getLong(2)); assertEquals(30,rs.getLong(3)); assertFalse(rs.next()); conn1.close(); } @Test public void testAlterTableOnGlobalIndex() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl()); Statement stmt = conn.createStatement()) { conn.setAutoCommit(false); Admin admin = conn.unwrap(PhoenixConnection.class).getQueryServices().getAdmin(); String tableName = generateUniqueName(); String globalIndexTableName = generateUniqueName(); stmt.execute("CREATE TABLE " + tableName + " (ID INTEGER PRIMARY KEY, COL1 VARCHAR(10), COL2 BOOLEAN)"); stmt.execute("CREATE INDEX " + globalIndexTableName + " on " + tableName + " (COL2)"); TableDescriptor originalDesc = admin.getDescriptor(TableName.valueOf(globalIndexTableName)); int expectedErrorCode = 0; try { stmt.execute("ALTER TABLE " + globalIndexTableName + " ADD CF1.AGE INTEGER "); conn.commit(); fail("The alter table did not fail as expected"); } catch (SQLException e) { assertEquals(e.getErrorCode(), CANNOT_MUTATE_TABLE.getErrorCode()); } TableDescriptor finalDesc = admin.getDescriptor(TableName.valueOf(globalIndexTableName)); assertTrue(finalDesc.equals(originalDesc)); // drop the table stmt.execute("DROP TABLE " + tableName); } } @Test public void testAlterStoreNulls() throws SQLException { Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); Statement stmt = conn.createStatement(); stmt.execute("CREATE TABLE " + dataTableFullName + " (id SMALLINT PRIMARY KEY, name VARCHAR) "+tableDDLOptions); ResultSet rs = stmt.executeQuery("SELECT STORE_NULLS FROM \"SYSTEM\".\"CATALOG\" " + "WHERE table_name = '" + dataTableFullName + "' AND STORE_NULLS IS NOT NULL"); assertTrue(rs.next()); assertFalse(rs.getBoolean(1)); assertFalse(rs.next()); rs.close(); stmt.execute("ALTER TABLE " + dataTableFullName + " SET STORE_NULLS = true"); rs = stmt.executeQuery("SELECT STORE_NULLS FROM \"SYSTEM\".\"CATALOG\" " + "WHERE table_name = '" + dataTableFullName + "' AND STORE_NULLS IS NOT NULL"); assertTrue(rs.next()); assertTrue(rs.getBoolean(1)); assertFalse(rs.next()); rs.close(); stmt.close(); } @Test public void testAddingPkColAndSettingProperties() throws Exception { Connection conn = DriverManager.getConnection(getUrl()); try { String ddl = "create table " + dataTableFullName + " (" + " k1 char(1) NOT NULL," + " k2 integer NOT NULL," + " col1 bigint," + " CONSTRAINT NAME_PK PRIMARY KEY (k1, k2)" + " ) "+tableDDLOptions; conn.createStatement().execute(ddl); // set HTableProperty when adding a pk column should fail ddl = "ALTER TABLE " + dataTableFullName + " ADD k3 DECIMAL PRIMARY KEY COMPACTION_ENABLED = false"; try { conn.createStatement().execute(ddl); fail(); } catch (SQLException e) { assertEquals(SQLExceptionCode.CANNOT_SET_TABLE_PROPERTY_ADD_COLUMN.getErrorCode(), e.getErrorCode()); } // set HColumnProperty when adding only a pk column should fail ddl = "ALTER TABLE " + dataTableFullName + " ADD k3 DECIMAL PRIMARY KEY REPLICATION_SCOPE = 0"; try { conn.createStatement().execute(ddl); fail(); } catch (SQLException e) { assertEquals(SQLExceptionCode.SET_UNSUPPORTED_PROP_ON_ALTER_TABLE.getErrorCode(), e.getErrorCode()); } // set phoenix table property when adding a pk column should fail ddl = "ALTER TABLE " + dataTableFullName + " ADD k3 DECIMAL PRIMARY KEY DISABLE_WAL = true"; try { conn.createStatement().execute(ddl); fail(); } catch (SQLException e) { assertEquals(SQLExceptionCode.CANNOT_SET_TABLE_PROPERTY_ADD_COLUMN.getErrorCode(), e.getErrorCode()); } // set HColumnProperty property when adding a pk column and other key value columns should work ddl = "ALTER TABLE " + dataTableFullName + " ADD k3 DECIMAL PRIMARY KEY, col2 bigint, CF.col3 bigint IN_MEMORY = true, CF.IN_MEMORY=false, REPLICATION_SCOPE = 1"; conn.createStatement().execute(ddl); // assert that k3 was added as new pk ResultSet rs = conn.getMetaData().getPrimaryKeys("", schemaName, dataTableName); assertTrue(rs.next()); assertEquals("K1",rs.getString("COLUMN_NAME")); assertEquals(1, rs.getShort("KEY_SEQ")); assertTrue(rs.next()); assertEquals("K2",rs.getString("COLUMN_NAME")); assertEquals(2, rs.getShort("KEY_SEQ")); assertTrue(rs.next()); assertEquals("K3",rs.getString("COLUMN_NAME")); assertEquals(3, rs.getShort("KEY_SEQ")); assertFalse(rs.next()); try (Admin admin = conn.unwrap(PhoenixConnection.class).getQueryServices().getAdmin()) { TableDescriptor tableDesc = admin.getDescriptor(TableName.valueOf(dataTableFullName)); ColumnFamilyDescriptor[] columnFamilies = tableDesc.getColumnFamilies(); assertEquals(2, columnFamilies.length); assertEquals("0", columnFamilies[0].getNameAsString()); assertEquals(true, columnFamilies[0].isInMemory()); assertEquals(1, columnFamilies[0].getScope()); assertEquals("CF", columnFamilies[1].getNameAsString()); assertEquals(false, columnFamilies[1].isInMemory()); assertEquals(1, columnFamilies[1].getScope()); } } finally { conn.close(); } } @Test public void testClientCacheUpdatedOnChangingPhoenixTableProperties() throws Exception { Connection conn = DriverManager.getConnection(getUrl()); try { String ddl = "create table " + dataTableFullName + " (" + " id char(1) NOT NULL," + " col1 integer NOT NULL," + " col2 bigint NOT NULL," + " CONSTRAINT NAME_PK PRIMARY KEY (id, col1, col2)" + " ) "+tableDDLOptions; conn.createStatement().execute(ddl); asssertIsWALDisabled(conn, dataTableFullName, false); ddl = "ALTER TABLE " + dataTableFullName + " SET DISABLE_WAL = true"; conn.createStatement().execute(ddl); // check metadata cache is updated with DISABLE_WAL = true asssertIsWALDisabled(conn, dataTableFullName, true); ddl = "ALTER TABLE " + dataTableFullName + " SET DISABLE_WAL = false"; conn.createStatement().execute(ddl); // check metadata cache is updated with DISABLE_WAL = false asssertIsWALDisabled(conn, dataTableFullName, false); ddl = "ALTER TABLE " + dataTableFullName + " SET MULTI_TENANT = true"; conn.createStatement().execute(ddl); // check metadata cache is updated with MULTI_TENANT = true PTable t = conn.unwrap(PhoenixConnection.class).getTable(new PTableKey(null, dataTableFullName)); assertTrue(t.isMultiTenant()); // check table metadata updated server side ResultSet rs = conn.createStatement().executeQuery("SELECT DISABLE_WAL, MULTI_TENANT FROM \"SYSTEM\".\"CATALOG\"" + "WHERE table_name = '" + dataTableFullName + "' AND DISABLE_WAL IS NOT NULL AND MULTI_TENANT IS NOT NULL"); assertTrue(rs.next()); assertFalse(rs.getBoolean(1)); assertTrue(rs.getBoolean(2)); assertFalse(rs.next()); rs.close(); } finally { conn.close(); } } @Test public void testDeclaringColumnAsRowTimestamp() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl())) { conn.createStatement().execute("CREATE TABLE " + dataTableFullName + " (PK1 DATE NOT NULL, PK2 VARCHAR NOT NULL, KV1 VARCHAR CONSTRAINT PK PRIMARY KEY(PK1 ROW_TIMESTAMP, PK2)) " + tableDDLOptions); PhoenixConnection phxConn = conn.unwrap(PhoenixConnection.class); PTable table = phxConn.getTable(new PTableKey(phxConn.getTenantId(), dataTableFullName)); // Assert that the column shows up as row time stamp in the cache. assertTrue(table.getColumnForColumnName("PK1").isRowTimestamp()); assertFalse(table.getColumnForColumnName("PK2").isRowTimestamp()); assertIsRowTimestampSet(schemaName, dataTableName, "PK1"); String dataTableName2 = BaseTest.generateUniqueName(); String dataTableFullName2 = SchemaUtil.getTableName(schemaName, dataTableName2); conn.createStatement().execute("CREATE IMMUTABLE TABLE " + dataTableFullName2 + " (PK1 VARCHAR, PK2 DATE PRIMARY KEY ROW_TIMESTAMP, KV1 VARCHAR, KV2 INTEGER)"); table = phxConn.getTable(new PTableKey(phxConn.getTenantId(), dataTableFullName2)); // Assert that the column shows up as row time stamp in the cache. assertFalse(table.getColumnForColumnName("PK1").isRowTimestamp()); assertTrue(table.getColumnForColumnName("PK2").isRowTimestamp()); assertIsRowTimestampSet(schemaName, dataTableName2, "PK2"); // Create an index on a table has a row time stamp pk column. The column should show up as a row time stamp column for the index too. conn.createStatement().execute("CREATE INDEX " + indexTableName + " ON " + dataTableFullName2 + " (KV1) include (KV2)"); PTable indexTable = phxConn.getTable(new PTableKey(phxConn.getTenantId(), indexTableFullName)); String indexColName = IndexUtil.getIndexColumnName(table.getColumnForColumnName("PK2")); // Assert that the column shows up as row time stamp in the cache. assertTrue(indexTable.getColumnForColumnName(indexColName).isRowTimestamp()); assertIsRowTimestampSet(schemaName, indexTableName, indexColName); String viewTableName2 = dataTableName2 + "_VIEW"; String viewTableFullName2 = SchemaUtil.getTableName(schemaName, viewTableName2); // Creating a view with a row_timestamp column in its pk constraint is not allowed try { conn.createStatement().execute("CREATE VIEW " + viewTableFullName2 + " (KV3 VARCHAR, KV4 DATE, KV5 INTEGER, CONSTRAINT PK PRIMARY KEY (KV3, KV4 ROW_TIMESTAMP) ) AS SELECT * FROM " + dataTableFullName2); fail("Creating a view with a row_timestamp column in its pk constraint is not allowed"); } catch (SQLException e) { assertEquals(SQLExceptionCode.ROWTIMESTAMP_NOT_ALLOWED_ON_VIEW.getErrorCode(), e.getErrorCode()); } // Make sure that the base table column declared as row_timestamp is also row_timestamp for view conn.createStatement().execute("CREATE VIEW " + viewTableFullName2 + " (KV3 VARCHAR, KV4 VARCHAR, KV5 INTEGER, CONSTRAINT PK PRIMARY KEY (KV3, KV4) ) AS SELECT * FROM " + dataTableFullName2); PTable view = phxConn.getTable(new PTableKey(phxConn.getTenantId(), viewTableFullName2)); assertNotNull(view.getPKColumn("PK2")); assertTrue(view.getPKColumn("PK2").isRowTimestamp()); } } private void assertIsRowTimestampSet(String schemaName, String tableName, String columnName) throws SQLException { String sql = "SELECT IS_ROW_TIMESTAMP FROM \"SYSTEM\".\"CATALOG\" WHERE " + "(TABLE_SCHEM, TABLE_NAME) = ('" + schemaName + "','"+ tableName + "') AND\n" + "COLUMN_FAMILY IS NULL AND COLUMN_NAME = ?"; try(Connection conn = DriverManager.getConnection(getUrl())) { PreparedStatement stmt = conn.prepareStatement(sql); stmt.setString(1, columnName); ResultSet rs = stmt.executeQuery(); assertTrue(rs.next()); assertEquals(true, rs.getBoolean(1)); } } @Test public void testAddingRowTimestampColumnNotAllowedViaAlterTable() throws Exception { try (Connection conn = DriverManager.getConnection(getUrl())) { conn.createStatement().execute("CREATE TABLE " + dataTableFullName + " (PK1 VARCHAR NOT NULL, PK2 VARCHAR NOT NULL, KV1 VARCHAR CONSTRAINT PK PRIMARY KEY(PK1, PK2)) " + tableDDLOptions); // adding a new pk column that is also row_timestamp is not allowed try { conn.createStatement().execute("ALTER TABLE " + dataTableFullName + " ADD PK3 DATE PRIMARY KEY ROW_TIMESTAMP"); fail("Altering table to add a PK column as row_timestamp column should fail"); } catch (SQLException e) { assertEquals(SQLExceptionCode.ROWTIMESTAMP_CREATE_ONLY.getErrorCode(), e.getErrorCode()); } } } @Test public void testCreatingTxnTableFailsIfTxnsDisabled() throws Exception { if (!TransactionFactory.Provider.getDefault().runTests()) { return; } Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES); props.setProperty(QueryServices.TRANSACTIONS_ENABLED, Boolean.toString(false)); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { // creating a transactional table should fail if transactions are disabled try { conn.createStatement().execute("CREATE TABLE " + dataTableFullName + "(k INTEGER PRIMARY KEY, v VARCHAR) " + generateDDLOptions("TRANSACTIONAL=true")); fail(); } catch (SQLException e) { assertEquals(SQLExceptionCode.CANNOT_CREATE_TXN_TABLE_IF_TXNS_DISABLED.getErrorCode(), e.getErrorCode()); } // altering a table to be transactional should fail if transactions are disabled conn.createStatement().execute("CREATE TABLE " + dataTableFullName + "(k INTEGER PRIMARY KEY, v VARCHAR)"); try { conn.createStatement().execute("ALTER TABLE " + dataTableFullName + " SET TRANSACTIONAL=true"); fail(); } catch (SQLException e) { assertEquals(SQLExceptionCode.CANNOT_ALTER_TO_BE_TXN_IF_TXNS_DISABLED.getErrorCode(), e.getErrorCode()); } } } @Test public void testMetadataForImmutableTable() throws Exception { String schemaName = "XYZ"; String baseTableName = generateUniqueName(); String viewName = generateUniqueName(); String fullTableName = schemaName + "." + baseTableName; String fullViewName = schemaName + "." + viewName; try (Connection conn = DriverManager.getConnection(getUrl())) { PhoenixConnection phxConn = conn.unwrap(PhoenixConnection.class); conn.createStatement().execute("CREATE TABLE IF NOT EXISTS " + fullTableName + " (" + " ID char(1) NOT NULL," + " COL1 integer NOT NULL," + " COL2 bigint NOT NULL," + " KV1 VARCHAR" + " CONSTRAINT NAME_PK PRIMARY KEY (ID, COL1, COL2)" + " ) " + generateDDLOptions("IMMUTABLE_ROWS = true" + (!columnEncoded ? ",IMMUTABLE_STORAGE_SCHEME="+ PTable.ImmutableStorageScheme.ONE_CELL_PER_COLUMN : ""))); PTable baseTable = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullTableName)); long initBaseTableSeqNumber = baseTable.getSequenceNumber(); // assert that the client side cache is updated. EncodedCQCounter cqCounter = baseTable.getEncodedCQCounter(); assertEquals( columnEncoded ? (Integer)(ENCODED_CQ_COUNTER_INITIAL_VALUE + 1) : null, cqCounter.getNextQualifier(QueryConstants.DEFAULT_COLUMN_FAMILY)); // assert that the server side metadata is updated correctly. assertEncodedCQCounter(DEFAULT_COLUMN_FAMILY, schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 1); assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "KV1", schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE); assertSequenceNumber(schemaName, baseTableName, initBaseTableSeqNumber); // now create a view and validate client and server side metadata String viewDDL = "CREATE VIEW " + fullViewName + " ( VIEW_COL1 INTEGER, A.VIEW_COL2 VARCHAR ) AS SELECT * FROM " + fullTableName; conn.createStatement().execute(viewDDL); baseTable = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullTableName)); PTable view = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullViewName)); // verify that the client side cache is updated. Base table's cq counters should be updated. assertEquals( columnEncoded ? (Integer)(ENCODED_CQ_COUNTER_INITIAL_VALUE + 2) : null, baseTable.getEncodedCQCounter().getNextQualifier(DEFAULT_COLUMN_FAMILY)); assertEquals( columnEncoded ? (Integer)(ENCODED_CQ_COUNTER_INITIAL_VALUE + 1) : null, baseTable.getEncodedCQCounter().getNextQualifier("A")); assertNull("A view should always have the null cq counter", view.getEncodedCQCounter().getNextQualifier(DEFAULT_COLUMN_FAMILY)); // assert that the server side metadata for the base table and the view is also updated correctly. assertEncodedCQCounter(DEFAULT_COLUMN_FAMILY, schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 2); assertEncodedCQCounter("A", schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 1); assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "VIEW_COL1", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 1); assertEncodedCQValue("A", "VIEW_COL2", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE); assertSequenceNumber(schemaName, baseTableName, initBaseTableSeqNumber + (columnEncoded ? 1 : 0)); assertSequenceNumber(schemaName, viewName, PTable.INITIAL_SEQ_NUM); } } @Test public void testMetadataForMutableTable() throws Exception { String schemaName = "XYZ"; String baseTableName = generateUniqueName(); String viewName = generateUniqueName(); String fullTableName = schemaName + "." + baseTableName; String fullViewName = schemaName + "." + viewName; try (Connection conn = DriverManager.getConnection(getUrl())) { PhoenixConnection phxConn = conn.unwrap(PhoenixConnection.class); conn.createStatement().execute("CREATE TABLE IF NOT EXISTS " + fullTableName + " (" + " ID char(1) NOT NULL," + " COL1 integer NOT NULL," + " COL2 bigint NOT NULL," + " KV1 VARCHAR" + " CONSTRAINT NAME_PK PRIMARY KEY (ID, COL1, COL2)" + " ) " + tableDDLOptions); PTable baseTable = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullTableName)); long initBaseTableSeqNumber = baseTable.getSequenceNumber(); // assert that the client side cache is updated. EncodedCQCounter cqCounter = baseTable.getEncodedCQCounter(); assertEquals( columnEncoded ? (Integer)(ENCODED_CQ_COUNTER_INITIAL_VALUE + 1) : null, cqCounter.getNextQualifier(QueryConstants.DEFAULT_COLUMN_FAMILY)); // assert that the server side metadata is updated correctly. assertEncodedCQCounter(DEFAULT_COLUMN_FAMILY, schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 1); assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "KV1", schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE); assertSequenceNumber(schemaName, baseTableName, initBaseTableSeqNumber); // now create a view and validate client and server side metadata String viewDDL = "CREATE VIEW " + fullViewName + " ( VIEW_COL1 INTEGER, A.VIEW_COL2 VARCHAR ) AS SELECT * FROM " + fullTableName; conn.createStatement().execute(viewDDL); baseTable = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullTableName)); PTable view = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullViewName)); // verify that the client side cache is updated. Base table's cq counters should be updated. assertEquals(columnEncoded ? (Integer)(ENCODED_CQ_COUNTER_INITIAL_VALUE + 3) : null, baseTable.getEncodedCQCounter().getNextQualifier(DEFAULT_COLUMN_FAMILY)); assertNull("A view should always have the null cq counter", view.getEncodedCQCounter().getNextQualifier(DEFAULT_COLUMN_FAMILY)); // assert that the server side metadata for the base table and the view is also updated correctly. assertEncodedCQCounter(DEFAULT_COLUMN_FAMILY, schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 3); assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "VIEW_COL1", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 1); assertEncodedCQValue("A", "VIEW_COL2", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 2); assertSequenceNumber(schemaName, baseTableName, initBaseTableSeqNumber + (columnEncoded ? 1 : 0)); assertSequenceNumber(schemaName, viewName, PTable.INITIAL_SEQ_NUM); } } @Test public void testAddingColumnsToTablesAndViews() throws Exception { String schemaName = generateUniqueName(); String baseTableName = generateUniqueName(); String viewName = generateUniqueName(); String fullTableName = schemaName + "." + baseTableName; String fullViewName = schemaName + "." + viewName; Properties props = new Properties(); props.put(QueryServices.IS_NAMESPACE_MAPPING_ENABLED, Boolean.toString(true)); try (Connection conn = DriverManager.getConnection(getUrl(), props)) { conn.createStatement().execute("CREATE SCHEMA " + schemaName); PhoenixConnection phxConn = conn.unwrap(PhoenixConnection.class); conn.createStatement().execute("CREATE TABLE " + fullTableName + " (" + " ID char(1) NOT NULL," + " COL1 integer NOT NULL," + " COL2 bigint NOT NULL," + " CONSTRAINT NAME_PK PRIMARY KEY (ID, COL1, COL2)" + " ) " + tableDDLOptions); PTable baseTable = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullTableName)); long initBaseTableSeqNumber = baseTable.getSequenceNumber(); // Add a column to the base table and see if the client and server metadata is updated correctly String alterDDL = "ALTER TABLE " + fullTableName + " ADD COL3 VARCHAR PRIMARY KEY, COL4 INTEGER, COL5 VARCHAR, B.COL6 DECIMAL (10, 2)"; conn.createStatement().execute(alterDDL); // assert that the client side cache is updated. baseTable = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullTableName)); EncodedCQCounter encodedCqCounter = baseTable.getEncodedCQCounter(); assertEquals( columnEncoded ?(Integer)(ENCODED_CQ_COUNTER_INITIAL_VALUE + 3) : null, encodedCqCounter.getNextQualifier(DEFAULT_COLUMN_FAMILY)); // assert that the server side metadata is updated correctly. assertEncodedCQCounter(DEFAULT_COLUMN_FAMILY, schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 3); // assert that the server side metadata for columns is updated correctly. assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "COL4", schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE); assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "COL5", schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 1); assertEncodedCQValue("B", "COL6", schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 2); long baseTableSeqNumBeforeAddingChildCols = initBaseTableSeqNumber + 1; assertSequenceNumber(schemaName, baseTableName, baseTableSeqNumBeforeAddingChildCols); // Create a view String viewDDL = "CREATE VIEW " + fullViewName + " ( VIEW_COL1 INTEGER, A.VIEW_COL2 VARCHAR ) AS SELECT * FROM " + fullTableName; conn.createStatement().execute(viewDDL); // assert that the server side metadata is updated correctly. assertEncodedCQCounter(DEFAULT_COLUMN_FAMILY, schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 5); // assert that the server side metadata for columns is updated correctly. assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "VIEW_COL1", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 3); assertEncodedCQValue("A", "VIEW_COL2", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 4); // for encoded columns creating a view that adds its own columns should increment the base table's sequence number too. assertSequenceNumber(schemaName, baseTableName, columnEncoded ? initBaseTableSeqNumber + 2 : baseTableSeqNumBeforeAddingChildCols ); // Add column to the view viewDDL = "ALTER VIEW " + fullViewName + " ADD VIEW_COL3 DECIMAL(10, 2), A.VIEW_COL4 VARCHAR, B.VIEW_COL5 INTEGER"; conn.createStatement().execute(viewDDL); // assert that the client cache for the base table is updated baseTable = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullTableName)); encodedCqCounter = baseTable.getEncodedCQCounter(); assertEquals( columnEncoded ? (Integer)(ENCODED_CQ_COUNTER_INITIAL_VALUE + 8) : null, encodedCqCounter.getNextQualifier(DEFAULT_COLUMN_FAMILY)); // assert client cache for view PTable view = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullViewName)); encodedCqCounter = view.getEncodedCQCounter(); assertNull("A view should always have the column qualifier counter as null", view.getEncodedCQCounter().getNextQualifier(DEFAULT_COLUMN_FAMILY)); // assert that the server side metadata for the base table and the view is also updated correctly. assertEncodedCQCounter(DEFAULT_COLUMN_FAMILY, schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 8); assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "VIEW_COL1", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 3); assertEncodedCQValue("A", "VIEW_COL2", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 4); assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "VIEW_COL3", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 5); assertEncodedCQValue("A", "VIEW_COL4", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 6); assertEncodedCQValue("B", "VIEW_COL5", schemaName, viewName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 7); // adding a column to the should increment the base table's sequence number too since we update the cq counters for column families. assertSequenceNumber(schemaName, baseTableName, columnEncoded ? initBaseTableSeqNumber + 3 : baseTableSeqNumBeforeAddingChildCols ); assertSequenceNumber(schemaName, viewName, PTable.INITIAL_SEQ_NUM + 1); // Add column to the base table which doesn't already exist in the view. alterDDL = "ALTER TABLE " + fullTableName + " ADD COL10 VARCHAR, A.COL11 INTEGER"; conn.createStatement().execute(alterDDL); baseTable = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullTableName)); // assert that the client cache for the base table is updated encodedCqCounter = baseTable.getEncodedCQCounter(); assertEquals( columnEncoded ? (Integer)(ENCODED_CQ_COUNTER_INITIAL_VALUE + 10) : null, encodedCqCounter.getNextQualifier(DEFAULT_COLUMN_FAMILY)); // assert client cache for view view = phxConn.getTable(new PTableKey(phxConn.getTenantId(), fullViewName)); encodedCqCounter = view.getEncodedCQCounter(); assertNull("A view should always have the column qualifier counter as null", view.getEncodedCQCounter().getNextQualifier(DEFAULT_COLUMN_FAMILY)); // assert that the server side metadata for the base table and the view is also updated correctly. assertEncodedCQCounter(DEFAULT_COLUMN_FAMILY, schemaName, baseTableName, (ENCODED_CQ_COUNTER_INITIAL_VALUE + 10)); assertEncodedCQValue(DEFAULT_COLUMN_FAMILY, "COL10", schemaName, baseTableName, (ENCODED_CQ_COUNTER_INITIAL_VALUE + 8)); assertEncodedCQValue("A", "COL11", schemaName, baseTableName, ENCODED_CQ_COUNTER_INITIAL_VALUE + 9); assertSequenceNumber(schemaName, baseTableName, columnEncoded ? initBaseTableSeqNumber + 4 : initBaseTableSeqNumber + 2 ); // view sequence number does not change as base table column changes are not propagated to views assertSequenceNumber(schemaName, viewName, PTable.INITIAL_SEQ_NUM + 1); } } private void assertEncodedCQValue(String columnFamily, String columnName, String schemaName, String tableName, int expectedValue) throws Exception { String query = "SELECT " + COLUMN_QUALIFIER + " FROM \"SYSTEM\".CATALOG WHERE " + TABLE_SCHEM + " = ? AND " + TABLE_NAME + " = ? " + " AND " + COLUMN_FAMILY + " = ?" + " AND " + COLUMN_NAME + " = ?" + " AND " + COLUMN_QUALIFIER + " IS NOT NULL"; try (Connection conn = DriverManager.getConnection(getUrl())) { PreparedStatement stmt = conn.prepareStatement(query); stmt.setString(1, schemaName); stmt.setString(2, tableName); stmt.setString(3, columnFamily); stmt.setString(4, columnName); ResultSet rs = stmt.executeQuery(); assertTrue(rs.next()); if (columnEncoded) { assertTrue(Bytes.equals(QualifierEncodingScheme.TWO_BYTE_QUALIFIERS.encode(expectedValue), rs.getBytes(1))); } else { assertTrue(Bytes.equals(columnName.getBytes(), rs.getBytes(1))); } assertFalse(rs.next()); } } private void assertEncodedCQCounter(String columnFamily, String schemaName, String tableName, int expectedValue) throws Exception { String query = "SELECT " + COLUMN_QUALIFIER_COUNTER + " FROM \"SYSTEM\".CATALOG WHERE " + TABLE_SCHEM + " = ? AND " + TABLE_NAME + " = ? " + " AND " + COLUMN_FAMILY + " = ? AND " + COLUMN_QUALIFIER_COUNTER + " IS NOT NULL"; try (Connection conn = DriverManager.getConnection(getUrl())) { PreparedStatement stmt = conn.prepareStatement(query); stmt.setString(1, schemaName); stmt.setString(2, tableName); stmt.setString(3, columnFamily); ResultSet rs = stmt.executeQuery(); if (columnEncoded) { assertTrue(rs.next()); assertEquals(expectedValue, rs.getInt(1)); assertFalse(rs.next()); } else { assertFalse(rs.next()); } } } private void assertSequenceNumber(String schemaName, String tableName, long expectedSequenceNumber) throws Exception { String query = "SELECT " + TABLE_SEQ_NUM + " FROM \"SYSTEM\".CATALOG WHERE " + TABLE_SCHEM + " = ? AND " + TABLE_NAME + " = ? AND " + TABLE_SEQ_NUM + " IS NOT NULL AND " + COLUMN_NAME + " IS NULL AND " + COLUMN_FAMILY + " IS NULL "; try (Connection conn = DriverManager.getConnection(getUrl())) { PreparedStatement stmt = conn.prepareStatement(query); stmt.setString(1, schemaName); stmt.setString(2, tableName); ResultSet rs = stmt.executeQuery(); assertTrue(rs.next()); assertEquals(expectedSequenceNumber, rs.getInt(1)); assertFalse(rs.next()); } } @Test public void testAlterTableWithIndexesExtendPk() throws Exception { Properties props = PropertiesUtil.deepCopy(TestUtil.TEST_PROPERTIES); Connection conn = DriverManager.getConnection(getUrl(), props); conn.setAutoCommit(false); String tableName = generateUniqueName(); String indexName1 = "I_" + generateUniqueName(); String indexName2 = "I_" + generateUniqueName(); try { String ddl = "CREATE TABLE " + tableName + " (ORG_ID CHAR(15) NOT NULL," + " PARTITION_KEY CHAR(3) NOT NULL, " + " ACTIVITY_DATE DATE NOT NULL, " + " FK1_ID CHAR(15) NOT NULL, " + " FK2_ID CHAR(15) NOT NULL, " + " TYPE VARCHAR NOT NULL, " + " IS_OPEN BOOLEAN " + " CONSTRAINT PKVIEW PRIMARY KEY " + "(" + "ORG_ID, PARTITION_KEY, ACTIVITY_DATE, FK1_ID, FK2_ID, TYPE" + "))"; createTestTable(getUrl(), ddl); String idx1ddl = "CREATE INDEX " + indexName1 + " ON " + tableName + " (FK1_ID, ACTIVITY_DATE DESC) INCLUDE (IS_OPEN)"; PreparedStatement stmt1 = conn.prepareStatement(idx1ddl); stmt1.execute(); String idx2ddl = "CREATE INDEX " + indexName2 + " ON " + tableName + " (FK2_ID, ACTIVITY_DATE DESC) INCLUDE (IS_OPEN)"; PreparedStatement stmt2 = conn.prepareStatement(idx2ddl); stmt2.execute(); ddl = "ALTER TABLE " + tableName + " ADD SOURCE VARCHAR(25) NULL PRIMARY KEY"; PreparedStatement stmt3 = conn.prepareStatement(ddl); stmt3.execute(); } finally { conn.close(); } } @Test public void testAddNonPKColumnWhenlastPKIsVARBINARYOrARRAY() throws Exception { String tableName1 = generateUniqueName(); String tableName2 = generateUniqueName(); Properties props = PropertiesUtil.deepCopy(TestUtil.TEST_PROPERTIES); try (Connection conn = DriverManager.getConnection(getUrl(), props); Statement stmt = conn.createStatement()) { conn.setAutoCommit(false); String ddl = "CREATE TABLE " + tableName1 + " (id VARBINARY PRIMARY KEY, col1 INTEGER)"; stmt.execute(ddl); String alterDdl = "ALTER TABLE " + tableName1 + " ADD col2 INTEGER"; stmt.execute(alterDdl); String ddl2 = "CREATE TABLE " + tableName2 + " (id INTEGER ARRAY PRIMARY KEY, col1 INTEGER)"; stmt.execute(ddl2); String alterDdl2 = "ALTER TABLE " + tableName2 + " ADD col2 INTEGER"; stmt.execute(alterDdl2); } } }