blob: 5460e086bc3745ac02ba3531ffe5b1a00e171647 [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.util;
import static org.junit.Assert.assertArrayEquals;
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.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.phoenix.compile.QueryPlan;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
import org.apache.phoenix.jdbc.PhoenixStatement;
import org.apache.phoenix.query.BaseConnectionlessQueryTest;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTable.QualifierEncodingScheme;
import org.apache.phoenix.schema.TableNotFoundException;
import org.apache.phoenix.schema.types.PDataType;
import org.junit.Test;
import org.apache.phoenix.thirdparty.com.google.common.collect.ImmutableList;
public class PhoenixRuntimeTest extends BaseConnectionlessQueryTest {
@Test
public void testParseArguments_MinimalCase() {
PhoenixRuntime.ExecutionCommand execCmd = PhoenixRuntime.ExecutionCommand.parseArgs(
new String[] { "localhost", "test.csv" });
assertEquals(
"localhost",
execCmd.getConnectionString());
assertEquals(
ImmutableList.of("test.csv"),
execCmd.getInputFiles());
assertEquals(',', execCmd.getFieldDelimiter());
assertEquals('"', execCmd.getQuoteCharacter());
assertNull(execCmd.getEscapeCharacter());
assertNull(execCmd.getTableName());
assertNull(execCmd.getColumns());
assertFalse(execCmd.isStrict());
assertEquals(
CSVCommonsLoader.DEFAULT_ARRAY_ELEMENT_SEPARATOR,
execCmd.getArrayElementSeparator());
}
@Test
public void testParseArguments_FullOption() {
PhoenixRuntime.ExecutionCommand execCmd = PhoenixRuntime.ExecutionCommand.parseArgs(
new String[] { "-t", "mytable", "myzkhost:2181", "--strict", "file1.sql",
"test.csv", "file2.sql", "--header", "one, two,three", "-a", "!", "-d",
":", "-q", "3", "-e", "4" });
assertEquals("myzkhost:2181", execCmd.getConnectionString());
assertEquals(ImmutableList.of("file1.sql", "test.csv", "file2.sql"),
execCmd.getInputFiles());
assertEquals(':', execCmd.getFieldDelimiter());
assertEquals('3', execCmd.getQuoteCharacter());
assertEquals(Character.valueOf('4'), execCmd.getEscapeCharacter());
assertEquals("mytable", execCmd.getTableName());
assertEquals(ImmutableList.of("one", "two", "three"), execCmd.getColumns());
assertTrue(execCmd.isStrict());
assertEquals("!", execCmd.getArrayElementSeparator());
}
@Test
public void testGetPkColsEncodeDecode() throws Exception {
Connection conn = DriverManager.getConnection(getUrl());
String ddl = "CREATE TABLE t (\n" +
"TENANT_ID VARCHAR NOT NULL,\n" +
"PARENT_ID CHAR(15) NOT NULL,\n" +
"CREATED_DATE DATE NOT NULL,\n" +
"ENTITY_HISTORY_ID CHAR(15) NOT NULL,\n" +
"DATA_TYPE VARCHAR,\n" +
"OLDVAL_STRING VARCHAR,\n" +
"NEWVAL_STRING VARCHAR\n" +
"CONSTRAINT PK PRIMARY KEY(TENANT_ID, PARENT_ID, CREATED_DATE DESC, ENTITY_HISTORY_ID))"
+ " MULTI_TENANT = true, IMMUTABLE_ROWS = true";
conn.createStatement().execute(ddl);
String indexDDL = "CREATE INDEX i ON t (CREATED_DATE, PARENT_ID) INCLUDE (DATA_TYPE, OLDVAL_STRING, NEWVAL_STRING)";
conn.createStatement().execute(indexDDL);
String tenantId = "111111111111111";
String parentId = "222222222222222";
Date createdDate = new Date(System.currentTimeMillis());
String ehId = "333333333333333";
Object[] values = new Object[] {tenantId, createdDate, parentId, ehId};
QueryPlan plan = conn.createStatement().unwrap(PhoenixStatement.class).optimizeQuery("SELECT PARENT_ID FROM T WHERE CREATED_DATE > CURRENT_DATE()-1 AND TENANT_ID = '111111111111111'");
List<Pair<String,String>> pkColumns = PhoenixRuntime.getPkColsForSql(conn, plan);
String fullTableName = plan.getTableRef().getTable().getName().getString();
assertEquals("I", fullTableName);
byte[] encodedValues = PhoenixRuntime.encodeColumnValues(conn, fullTableName, values, pkColumns);
Object[] decodedValues = PhoenixRuntime.decodeColumnValues(conn, fullTableName, encodedValues, pkColumns);
assertArrayEquals(values, decodedValues);
plan = conn.createStatement().unwrap(PhoenixStatement.class).optimizeQuery("SELECT /*+ NO_INDEX */ ENTITY_HISTORY_ID FROM T");
pkColumns = PhoenixRuntime.getPkColsForSql(conn, plan);
values = new Object[] {tenantId, parentId, createdDate, ehId};
fullTableName = plan.getTableRef().getTable().getName().getString();
assertEquals("T", fullTableName);
encodedValues = PhoenixRuntime.encodeColumnValues(conn, fullTableName, values, pkColumns);
decodedValues = PhoenixRuntime.decodeColumnValues(conn, fullTableName, encodedValues, pkColumns);
assertArrayEquals(values, decodedValues);
}
@Test
public void testGetPkColsDataTypes() throws Exception {
Connection conn = DriverManager.getConnection(getUrl(), new Properties());
int i = 0;
PDataType[] pTypes = PDataType.values();
int size = pTypes.length;
StringBuilder sb = null;
try {
for (i = 0 ; i < size; i++) {
PDataType pType = pTypes[i];
String sqlTypeName = pType.getSqlTypeName();
if (sqlTypeName.equalsIgnoreCase("VARBINARY ARRAY")) {
// we don't support VARBINARY ARRAYS yet
// JIRA - https://issues.apache.org/jira/browse/PHOENIX-1329
continue;
}
// Condition to check if a type can be used as a primary key.
if (!pType.canBePrimaryKey()) {
continue;
}
if (pType.isArrayType() && PDataType.arrayBaseType(pType).isFixedWidth() && PDataType.arrayBaseType(pType).getByteSize() == null) {
// Need to treat array type whose base type is of fixed width whose byte size is not known as a special case.
// Cannot just use the sql type name returned by PDataType.getSqlTypeName().
String baseTypeName = PDataType.arrayBaseType(pType).getSqlTypeName();
sqlTypeName = baseTypeName + "(15)" + " " + PDataType.ARRAY_TYPE_SUFFIX;
} else if (pType.isFixedWidth() && pType.getByteSize() == null) {
sqlTypeName = sqlTypeName + "(15)";
}
String columnName = "col" + i;
String tableName = "t" + i;
sb = new StringBuilder(100);
// create a table by using the type name as returned by PDataType
sb.append("CREATE TABLE " + tableName + " (");
sb.append(columnName + " " + sqlTypeName + " NOT NULL PRIMARY KEY, V1 VARCHAR)");
conn.createStatement().execute(sb.toString());
// generate the optimized query plan by going through the pk of the table.
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnName + " = ?");
Integer maxLength = pType.isFixedWidth() && pType.getByteSize() == null ? 15 : null;
stmt.setObject(1, pType.getSampleValue(maxLength));
QueryPlan plan = PhoenixRuntime.getOptimizedQueryPlan(stmt);
// now go through the utility method, get column name and type name and
// try creating another table with the returned info. Use the query plan generated above.
// If table can be created with the returned sql type name, then great!
// It would mean "Roundtrip" of column data type name works.
List<Pair<String, String>> pkCols = new ArrayList<Pair<String, String>>();
List<String> dataTypes = new ArrayList<String>();
PhoenixRuntime.getPkColsDataTypesForSql(pkCols, dataTypes, plan, conn, true);
tableName = "newt" + i;
columnName = "newCol" + i;
String roundTripSqlTypeName = dataTypes.get(0);
// create a table by using the type name as returned by the utility method
sb = new StringBuilder(100);
sb.append("CREATE TABLE " + tableName + " (");
sb.append(columnName + " " + roundTripSqlTypeName + " NOT NULL PRIMARY KEY)");
conn.createStatement().execute(sb.toString());
}
} catch (Exception e) {
fail("Failed sql: " + sb.toString() + ExceptionUtils.getStackTrace(e));
}
}
@Test
public void testGetTenantIdExpression() throws Exception {
Connection conn = DriverManager.getConnection(getUrl());
Expression e1 = PhoenixRuntime.getTenantIdExpression(conn, PhoenixDatabaseMetaData.SYSTEM_STATS_NAME);
assertNull(e1);
Expression e2 = PhoenixRuntime.getTenantIdExpression(conn, PhoenixDatabaseMetaData.SYSTEM_CATALOG_NAME);
assertNotNull(e2);
Expression e3 = PhoenixRuntime.getTenantIdExpression(conn, PhoenixDatabaseMetaData.SYSTEM_SEQUENCE_NAME);
assertNotNull(e3);
conn.createStatement().execute("CREATE TABLE FOO (k VARCHAR PRIMARY KEY)");
Expression e4 = PhoenixRuntime.getTenantIdExpression(conn, "FOO");
assertNull(e4);
conn.createStatement().execute("CREATE TABLE A.BAR (k1 VARCHAR NOT NULL, k2 VARCHAR, CONSTRAINT PK PRIMARY KEY(K1,K2)) MULTI_TENANT=true");
Expression e5 = PhoenixRuntime.getTenantIdExpression(conn, "A.BAR");
assertNotNull(e5);
conn.createStatement().execute("CREATE INDEX I1 ON A.BAR (K2)");
Expression e5A = PhoenixRuntime.getTenantIdExpression(conn, "A.I1");
assertNotNull(e5A);
conn.createStatement().execute("CREATE TABLE BAS (k1 VARCHAR NOT NULL, k2 VARCHAR, CONSTRAINT PK PRIMARY KEY(K1,K2)) MULTI_TENANT=true, SALT_BUCKETS=3");
Expression e6 = PhoenixRuntime.getTenantIdExpression(conn, "BAS");
assertNotNull(e6);
conn.createStatement().execute("CREATE INDEX I2 ON BAS (K2)");
Expression e6A = PhoenixRuntime.getTenantIdExpression(conn, "I2");
assertNotNull(e6A);
try {
PhoenixRuntime.getTenantIdExpression(conn, "NOT.ATABLE");
fail();
} catch (TableNotFoundException e) {
// Expected
}
Properties props = PropertiesUtil.deepCopy(TestUtil.TEST_PROPERTIES);
props.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, "t1");
Connection tsconn = DriverManager.getConnection(getUrl(), props);
tsconn.createStatement().execute("CREATE VIEW V(V1 VARCHAR) AS SELECT * FROM BAS");
Expression e7 = PhoenixRuntime.getTenantIdExpression(tsconn, "V");
assertNotNull(e7);
tsconn.createStatement().execute("CREATE LOCAL INDEX I3 ON V (V1)");
try {
PhoenixRuntime.getTenantIdExpression(tsconn, "I3");
fail();
} catch (SQLFeatureNotSupportedException e) {
// Expected
}
}
@Test
public void testTableNameWithoutSchema() throws Exception {
String tableName = "tableName";
String tableNameNormalized = tableName.toUpperCase();
getTableTester(tableNameNormalized, tableName);
}
@Test
public void testTableNameWithSchema() throws Exception {
String tableName = "tableName";
String schemaName = "schemaName";
String fullName = schemaName + "." + tableName;
String fullNameNormalized = fullName.toUpperCase();
getTableTester(fullNameNormalized, fullName);
}
@Test
public void testCaseSensitiveTableNameWithoutSchema() throws Exception {
String caseSensitiveTableName = "tableName";
getTableTester(caseSensitiveTableName, quoteString(caseSensitiveTableName));
}
@Test
public void testCaseSensitiveTableNameWithSchema() throws Exception {
String caseSensitiveTableName = "tableName";
String schemaName = "schemaName";
String fullNameNormalized = schemaName.toUpperCase() + "." + caseSensitiveTableName;
String fullNameQuoted = schemaName + "." + quoteString(caseSensitiveTableName);
getTableTester(fullNameNormalized, fullNameQuoted);
}
@Test
public void testCaseSensitiveTableNameWithCaseSensitiveSchema() throws Exception {
String caseSensitiveTableName = "tableName";
String caseSensitiveSchemaName = "schemaName";
String fullName = caseSensitiveSchemaName + "." + caseSensitiveTableName;
String fullNameQuoted = quoteString(caseSensitiveSchemaName) + "." + quoteString(caseSensitiveTableName);
getTableTester(fullName, fullNameQuoted);
}
@Test
public void testCaseSensitiveTableNameWithCaseSensitiveSchemaWithPeriod() throws Exception {
String caseSensitiveTableName = "tableName";
String caseSensitiveSchemaName = "schema.Name";
String fullName = caseSensitiveSchemaName + "." + caseSensitiveTableName;
String fullNameQuoted = quoteString(caseSensitiveSchemaName) + "." + quoteString(caseSensitiveTableName);
getTableTester(fullName, fullNameQuoted);
}
private void getTableTester(String normalizedName, String sqlStatementName) throws SQLException {
Connection conn = DriverManager.getConnection(getUrl());
try {
conn.createStatement().execute("CREATE TABLE " + sqlStatementName + " (k VARCHAR PRIMARY KEY)");
PTable aTable = PhoenixRuntime.getTable(conn, normalizedName);
assertNotNull(aTable);
} finally {
if (null != conn) {
conn.createStatement().execute("DROP TABLE IF EXISTS " + sqlStatementName);
}
}
}
private String quoteString(String string) {
return "\"" + string + "\"";
}
@Test
public void testGetWallClockTimeFromCellTimeStamp() {
long ts = System.currentTimeMillis();
assertEquals(ts, PhoenixRuntime.getWallClockTimeFromCellTimeStamp(ts));
long nanoTs = TransactionUtil.convertToNanoseconds(ts);
assertEquals(ts, PhoenixRuntime.getWallClockTimeFromCellTimeStamp(nanoTs));
long skewedTs = ts + QueryConstants.MILLIS_IN_DAY; // skew of a day
// Even with a day of skew, we won't consider the ts a nanos timestamp
assertEquals(skewedTs, PhoenixRuntime.getWallClockTimeFromCellTimeStamp(skewedTs));
}
}