blob: 5de41bfa5f71052aefb90c2b11e4a390218ada57 [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.junit.Assert.assertArrayEquals;
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.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.phoenix.exception.SQLExceptionCode;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.schema.ColumnNotFoundException;
import org.apache.phoenix.schema.PColumn;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTableKey;
import org.apache.phoenix.schema.SequenceNotFoundException;
import org.apache.phoenix.schema.TableAlreadyExistsException;
import org.apache.phoenix.schema.types.PInteger;
import org.apache.phoenix.util.PhoenixRuntime;
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 AutoPartitionViewsIT extends ParallelStatsDisabledIT {
private String tableDDLOptions;
private boolean isMultiTenant;
private final String TENANT_SPECIFIC_URL1 = getUrl() + ';' + PhoenixRuntime.TENANT_ID_ATTRIB
+ "=tenant1";
private final String TENANT_SPECIFIC_URL2 = getUrl() + ';' + PhoenixRuntime.TENANT_ID_ATTRIB
+ "=tenant2";
@Parameters(name = "AutoPartitionViewsIT_salted={0},multi-tenant={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 } });
}
public AutoPartitionViewsIT(boolean salted, boolean isMultiTenant) {
this.isMultiTenant = isMultiTenant;
StringBuilder optionBuilder = new StringBuilder(" AUTO_PARTITION_SEQ=\"%s\"");
if (salted) optionBuilder.append(", SALTED=4 ");
if (isMultiTenant) optionBuilder.append(", MULTI_TENANT=true ");
this.tableDDLOptions = optionBuilder.toString();
}
@Test
public void testValidateAttributes() throws SQLException {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn1 =
isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1)
: DriverManager.getConnection(getUrl());
Connection viewConn2 =
isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1)
: DriverManager.getConnection(getUrl())) {
String tableName = generateUniqueName();
String autoSeqName = generateUniqueName();
try {
String ddl =
String.format(
"CREATE TABLE " + tableName + " (%s metricId VARCHAR, val1 DOUBLE, val2 DOUBLE CONSTRAINT PK PRIMARY KEY( %s metricId)) %s",
isMultiTenant ? "tenantId VARCHAR, " : "",
isMultiTenant ? "tenantId, ": "", String.format(tableDDLOptions, autoSeqName)
);
conn.createStatement().execute(ddl);
fail("Sequence value must be castable to the auto partition id column data type");
} catch (SQLException e) {
assertEquals(
SQLExceptionCode.SEQUENCE_NOT_CASTABLE_TO_AUTO_PARTITION_ID_COLUMN
.getErrorCode(),
e.getErrorCode());
}
String ddl =
String.format(
"CREATE TABLE " + tableName + " (%s metricId INTEGER NOT NULL, val1 DOUBLE, val2 DOUBLE CONSTRAINT PK PRIMARY KEY( %s metricId)) %s",
isMultiTenant ? "tenantId VARCHAR NOT NULL, " : "",
isMultiTenant ? "tenantId, ": "",
String.format(tableDDLOptions, autoSeqName));
conn.createStatement().execute(ddl);
String baseViewName = generateUniqueName();
String metricView1 = baseViewName + "_VIEW1";
String metricView2 = baseViewName + "_VIEW2";
String metricView3 = baseViewName + "_VIEW3";
String metricView4 = baseViewName + "_VIEW4";
try {
viewConn1.createStatement().execute(
"CREATE VIEW " + metricView1 + " AS SELECT * FROM " + tableName);
fail("Auto-partition sequence must be created before view is created");
} catch (SequenceNotFoundException e) {
}
conn.createStatement().execute(
"CREATE SEQUENCE " + autoSeqName + " start with " + (Integer.MAX_VALUE-2) + " cache 1");
viewConn1.createStatement().execute(
"CREATE VIEW " + metricView1 + " AS SELECT * FROM " + tableName + " WHERE val2=1.2");
// create a view without a where clause
viewConn1.createStatement().execute(
"CREATE VIEW " + metricView2 + " AS SELECT * FROM " + tableName);
// create a view with a complex where clause
viewConn1.createStatement().execute(
"CREATE VIEW " + metricView3 + " AS SELECT * FROM " + tableName + " WHERE val1=1.0 OR val2=2.0");
try {
viewConn1.createStatement().execute(
"CREATE VIEW " + metricView4 + " AS SELECT * FROM " + tableName);
fail("Creating a view with a partition id that is too large should fail");
} catch (SQLException e) {
assertEquals(SQLExceptionCode.CANNOT_COERCE_AUTO_PARTITION_ID.getErrorCode(),
e.getErrorCode());
}
if (isMultiTenant) {
// load tables into cache
viewConn1.createStatement().execute("SELECT * FROM " + metricView1);
viewConn1.createStatement().execute("SELECT * FROM " + metricView2);
viewConn1.createStatement().execute("SELECT * FROM " + metricView3);
}
PhoenixConnection pconn = viewConn1.unwrap(PhoenixConnection.class);
PTable view1 = pconn.getTable(new PTableKey(pconn.getTenantId(), metricView1));
PTable view2 = pconn.getTable(new PTableKey(pconn.getTenantId(), metricView2));
PTable view3 = pconn.getTable(new PTableKey(pconn.getTenantId(), metricView3));
// verify the view statement was set correctly
String expectedViewStatement1 =
"SELECT * FROM \"" + tableName + "\" WHERE VAL2 = 1.2 AND METRICID = "
+ (Integer.MAX_VALUE - 2);
String expectedViewStatement2 =
"SELECT * FROM \"" + tableName + "\" WHERE METRICID = " + (Integer.MAX_VALUE - 1);
String expectedViewStatement3 =
"SELECT * FROM \"" + tableName + "\" WHERE (VAL1 = 1.0 OR VAL2 = 2.0) AND METRICID = " + Integer.MAX_VALUE;
assertEquals("Unexpected view statement", expectedViewStatement1,
view1.getViewStatement());
assertEquals("Unexpected view statement", expectedViewStatement2,
view2.getViewStatement());
assertEquals("Unexpected view statement", expectedViewStatement3,
view3.getViewStatement());
// verify isViewReferenced was set correctly
int expectedParitionColIndex = isMultiTenant ? 1 : 0;
PColumn partitionCol1 = view1.getColumns().get(expectedParitionColIndex);
PColumn partitionCol2 = view2.getColumns().get(expectedParitionColIndex);
PColumn partitionCol3 = view3.getColumns().get(expectedParitionColIndex);
assertTrue("Partition column view referenced attribute should be true ",
partitionCol1.isViewReferenced());
assertTrue("Partition column view referenced attribute should be true ",
partitionCol2.isViewReferenced());
assertTrue("Partition column view referenced attribute should be true ",
partitionCol3.isViewReferenced());
// verify viewConstant was set correctly
byte[] expectedPartition1 = new byte[Bytes.SIZEOF_INT + 1];
PInteger.INSTANCE.toBytes(Integer.MAX_VALUE - 2, expectedPartition1, 0);
byte[] expectedPartition2 = new byte[Bytes.SIZEOF_INT + 1];
PInteger.INSTANCE.toBytes(Integer.MAX_VALUE - 1, expectedPartition2, 0);
byte[] expectedPartition3 = new byte[Bytes.SIZEOF_INT + 1];
PInteger.INSTANCE.toBytes(Integer.MAX_VALUE, expectedPartition3, 0);
assertArrayEquals("Unexpected Partition column view constant attribute",
expectedPartition1, partitionCol1.getViewConstant());
assertArrayEquals("Unexpected Partition column view constant attribute",
expectedPartition2, partitionCol2.getViewConstant());
assertArrayEquals("Unexpected Partition column view constant attribute",
expectedPartition3, partitionCol3.getViewConstant());
// verify that the table was created correctly on the server
viewConn2.createStatement().execute("SELECT * FROM " + metricView1);
viewConn2.createStatement().execute("SELECT * FROM " + metricView2 );
viewConn2.createStatement().execute("SELECT * FROM " + metricView3);
pconn = viewConn2.unwrap(PhoenixConnection.class);
view1 = pconn.getTable(new PTableKey(pconn.getTenantId(), metricView1));
view2 = pconn.getTable(new PTableKey(pconn.getTenantId(), metricView2));
view3 = pconn.getTable(new PTableKey(pconn.getTenantId(), metricView3));
// verify the view statement was set correctly
assertEquals("Unexpected view statement", expectedViewStatement1,
view1.getViewStatement());
assertEquals("Unexpected view statement", expectedViewStatement2,
view2.getViewStatement());
assertEquals("Unexpected view statement", expectedViewStatement3,
view3.getViewStatement());
// verify isViewReferenced was set correctly
partitionCol1 = view1.getColumns().get(expectedParitionColIndex);
partitionCol2 = view2.getColumns().get(expectedParitionColIndex);
partitionCol3 = view3.getColumns().get(expectedParitionColIndex);
assertTrue("Partition column view referenced attribute should be true ",
partitionCol1.isViewReferenced());
assertTrue("Partition column view referenced attribute should be true ",
partitionCol2.isViewReferenced());
assertTrue("Partition column view referenced attribute should be true ",
partitionCol3.isViewReferenced());
// verify viewConstant was set correctly
assertArrayEquals("Unexpected Partition column view constant attribute",
expectedPartition1, partitionCol1.getViewConstant());
assertArrayEquals("Unexpected Partition column view constant attribute",
expectedPartition2, partitionCol2.getViewConstant());
assertArrayEquals("Unexpected Partition column view constant attribute",
expectedPartition3, partitionCol3.getViewConstant());
}
}
@Test
public void testViewCreationFailure() throws SQLException {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn1 =
isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1)
: DriverManager.getConnection(getUrl());
Connection viewConn2 =
isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL2)
: DriverManager.getConnection(getUrl())) {
String tableName = generateUniqueName();
String autoSeqName = generateUniqueName();
String ddl =
String.format(
"CREATE TABLE " + tableName + " (%s metricId INTEGER NOT NULL, val1 DOUBLE, val2 DOUBLE CONSTRAINT PK PRIMARY KEY( %s metricId)) %s",
isMultiTenant ? "tenantId VARCHAR NOT NULL, " : "",
isMultiTenant ? "tenantId, ": "",
String.format(tableDDLOptions, autoSeqName));
conn.createStatement().execute(ddl);
conn.createStatement().execute("CREATE SEQUENCE " + autoSeqName + " CACHE 1");
String baseViewName = generateUniqueName();
String metricView1 = baseViewName + "_VIEW1";
String metricView2 = baseViewName + "_VIEW2";
// create a view
viewConn1.createStatement().execute(
"CREATE VIEW " + metricView1 + " AS SELECT * FROM " + tableName + " WHERE val2=1.2");
try {
// create the same view which should fail
viewConn1.createStatement()
.execute("CREATE VIEW " + metricView1 + " AS SELECT * FROM " + tableName);
fail("view should already exist");
} catch (TableAlreadyExistsException e) {
}
// create a second view (without a where clause)
viewConn2.createStatement().execute(
"CREATE VIEW " + metricView2 + " AS SELECT * FROM " + tableName);
// upsert a row into each view
viewConn1.createStatement().execute("UPSERT INTO " + metricView1 + "(val1) VALUES(1.1)");
viewConn1.commit();
viewConn2.createStatement().execute("UPSERT INTO " + metricView2 + "(val1,val2) VALUES(2.1,2.2)");
viewConn2.commit();
// query the base table
ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName);
assertTrue(rs.next());
int offset = 0;
if (isMultiTenant) {
assertEquals("tenant1", rs.getString(1));
offset = 1;
}
assertEquals(1, rs.getInt(1+offset));
assertEquals(1.1, rs.getDouble(2+offset), 1e-6);
assertEquals(1.2, rs.getDouble(3+offset), 1e-6);
assertTrue(rs.next());
// validate that the auto partition sequence was not incremented even though view creation failed
if (isMultiTenant) {
assertEquals("tenant2", rs.getString(1));
}
assertEquals(2, rs.getInt(1+offset));
assertEquals(2.1, rs.getDouble(2+offset), 1e-6);
assertEquals(2.2, rs.getDouble(3+offset), 1e-6);
assertFalse(rs.next());
// query the first view
rs = viewConn1.createStatement().executeQuery("SELECT * FROM " + metricView1);
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertEquals(1.1, rs.getDouble(2), 1e-6);
assertEquals(1.2, rs.getDouble(3), 1e-6);
assertFalse(rs.next());
// query the second view
rs = viewConn2.createStatement().executeQuery("SELECT * FROM " + metricView2);
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertEquals(2.1, rs.getDouble(2), 1e-6);
assertEquals(2.2, rs.getDouble(3), 1e-6);
assertFalse(rs.next());
}
}
@Test
public void testAddDropColumns() throws SQLException {
try (Connection conn = DriverManager.getConnection(getUrl());
Connection viewConn1 =
isMultiTenant ? DriverManager.getConnection(TENANT_SPECIFIC_URL1)
: DriverManager.getConnection(getUrl())) {
String tableName = generateUniqueName();
String autoSeqName = generateUniqueName();
String ddl =
String.format(
"CREATE TABLE " + tableName + " (%s metricId INTEGER NOT NULL, val1 DOUBLE, CONSTRAINT PK PRIMARY KEY( %s metricId)) %s",
isMultiTenant ? "tenantId VARCHAR NOT NULL, " : "",
isMultiTenant ? "tenantId, ": "",
String.format(tableDDLOptions, autoSeqName));
conn.createStatement().execute(ddl);
conn.createStatement().execute("CREATE SEQUENCE " + autoSeqName + " CACHE 1");
String metricView = generateUniqueName() + "_VIEW";
// create a view
viewConn1.createStatement().execute(
"CREATE VIEW " + metricView + " AS SELECT * FROM " + tableName);
// add a column to the base table
conn.createStatement().execute(
"ALTER TABLE " + tableName + " add val2 DOUBLE");
// add a column to the view
viewConn1.createStatement().execute(
"ALTER VIEW " + metricView + " add val3 DOUBLE");
// upsert a row into the view
viewConn1.createStatement().execute("UPSERT INTO " + metricView + "(val1,val2,val3) VALUES(1.1,1.2,1.3)");
viewConn1.commit();
// query the base table
ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM " + tableName);
assertTrue(rs.next());
int offset = 0;
if (isMultiTenant) {
assertEquals("tenant1", rs.getString(1));
offset = 1;
}
assertEquals(1, rs.getInt(1+offset));
assertEquals(1.1, rs.getDouble(2+offset), 1e-6);
assertEquals(1.2, rs.getDouble(3+offset), 1e-6);
assertFalse(rs.next());
// query the view
rs = viewConn1.createStatement().executeQuery("SELECT * FROM " + metricView);
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertEquals(1.1, rs.getDouble(2), 1e-6);
assertEquals(1.2, rs.getDouble(3), 1e-6);
assertEquals(1.3, rs.getDouble(4), 1e-6);
assertFalse(rs.next());
// drop a column from the base table
conn.createStatement().execute(
"ALTER TABLE " + tableName + " DROP COLUMN val2");
// add a column to the view
viewConn1.createStatement().execute(
"ALTER VIEW " + metricView + " DROP COLUMN val3");
// verify columns don't exist
try {
viewConn1.createStatement().executeQuery("SELECT val2 FROM " + metricView);
fail("column should have been dropped");
}
catch (ColumnNotFoundException e) {
}
try {
viewConn1.createStatement().executeQuery("SELECT val3 FROM " + metricView);
fail("column should have been dropped");
}
catch (ColumnNotFoundException e) {
}
}
}
}