Repository: hive Updated Branches: refs/heads/master dc8d8e134 -> e39a19801
http://git-wip-us.apache.org/repos/asf/hive/blob/e39a1980/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestGetPartitionsUsingProjectionAndFilterSpecs.java ---------------------------------------------------------------------- diff --git a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestGetPartitionsUsingProjectionAndFilterSpecs.java b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestGetPartitionsUsingProjectionAndFilterSpecs.java new file mode 100644 index 0000000..bc43f3d --- /dev/null +++ b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestGetPartitionsUsingProjectionAndFilterSpecs.java @@ -0,0 +1,904 @@ +/* + * + * 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.hadoop.hive.metastore; + +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hive.metastore.annotation.MetastoreCheckinTest; +import org.apache.hadoop.hive.metastore.api.FieldSchema; +import org.apache.hadoop.hive.metastore.api.GetPartitionsFilterSpec; +import org.apache.hadoop.hive.metastore.api.GetPartitionsProjectionSpec; +import org.apache.hadoop.hive.metastore.api.GetPartitionsRequest; +import org.apache.hadoop.hive.metastore.api.GetPartitionsResponse; +import org.apache.hadoop.hive.metastore.api.MetaException; +import org.apache.hadoop.hive.metastore.api.Partition; +import org.apache.hadoop.hive.metastore.api.PartitionFilterMode; +import org.apache.hadoop.hive.metastore.api.PartitionListComposingSpec; +import org.apache.hadoop.hive.metastore.api.PartitionSpec; +import org.apache.hadoop.hive.metastore.api.PartitionSpecWithSharedSD; +import org.apache.hadoop.hive.metastore.api.PartitionWithoutSD; +import org.apache.hadoop.hive.metastore.api.StorageDescriptor; +import org.apache.hadoop.hive.metastore.api.Table; +import org.apache.hadoop.hive.metastore.client.builder.DatabaseBuilder; +import org.apache.hadoop.hive.metastore.client.builder.PartitionBuilder; +import org.apache.hadoop.hive.metastore.client.builder.TableBuilder; +import org.apache.hadoop.hive.metastore.conf.MetastoreConf; +import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars; +import org.apache.hadoop.hive.metastore.security.HadoopThriftAuthBridge; +import org.apache.thrift.TException; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import static org.apache.hadoop.hive.metastore.ColumnType.SERIALIZATION_FORMAT; + +/** + * Tests for getPartitionsWithSpecs metastore API. This test create some partitions and makes sure + * that getPartitionsWithSpecs returns results which are comparable with the get_partitions API when + * various combinations of projection spec are set. Also checks the JDO code path in addition to + * directSQL code path + */ +@Category(MetastoreCheckinTest.class) +public class TestGetPartitionsUsingProjectionAndFilterSpecs { + private static final Logger LOG = LoggerFactory.getLogger(TestGetPartitionsUsingProjectionAndFilterSpecs.class); + protected static Configuration conf = MetastoreConf.newMetastoreConf(); + private static int port; + private static final String dbName = "test_projection_db"; + private static final String tblName = "test_projection_table"; + private List<Partition> origPartitions; + private Table tbl; + private static final String EXCLUDE_KEY_PREFIX = "exclude"; + private HiveMetaStoreClient client; + + @BeforeClass + public static void startMetaStoreServer() throws Exception { + conf.set("hive.in.test", "true"); + MetaStoreTestUtils.setConfForStandloneMode(conf); + MetastoreConf.setLongVar(conf, ConfVars.BATCH_RETRIEVE_MAX, 2); + MetastoreConf.setLongVar(conf, ConfVars.LIMIT_PARTITION_REQUEST, 100); + port = MetaStoreTestUtils.startMetaStoreWithRetry(HadoopThriftAuthBridge.getBridge(), conf); + LOG.info("Starting MetaStore Server on port " + port); + + try (HiveMetaStoreClient client = createClient()) { + new DatabaseBuilder().setName(dbName).create(client, conf); + } + } + + @AfterClass + public static void tearDown() throws Exception { + try (HiveMetaStoreClient client = createClient()) { + client.dropDatabase(dbName, true, true, true); + } + } + + @Before + public void setup() throws TException { + // This is default case with setugi off for both client and server + client = createClient(); + createTestTables(); + origPartitions = client.listPartitions(dbName, tblName, (short) -1); + tbl = client.getTable(dbName, tblName); + // set directSQL to true explicitly + client.setMetaConf(ConfVars.TRY_DIRECT_SQL.getVarname(), "true"); + client.setMetaConf(ConfVars.TRY_DIRECT_SQL_DDL.getVarname(), "true"); + } + + @After + public void cleanup() { + dropTestTables(); + client.close(); + client = null; + } + + private void dropTestTables() { + try { + client.dropTable(dbName, tblName); + } catch (TException e) { + // ignored + } + } + + private void createTestTables() throws TException { + if (client.tableExists(dbName, tblName)) { + LOG.info("Table is already existing. Dropping it and then recreating"); + client.dropTable(dbName, tblName); + } + new TableBuilder().setTableName(tblName).setDbName(dbName).setCols(Arrays + .asList(new FieldSchema("col1", "string", "c1 comment"), + new FieldSchema("col2", "int", "c2 comment"))).setPartCols(Arrays + .asList(new FieldSchema("state", "string", "state comment"), + new FieldSchema("city", "string", "city comment"))) + .setTableParams(new HashMap<String, String>(2) {{ + put("tableparam1", "tableval1"); + put("tableparam2", "tableval2"); + }}) + .setBucketCols(Collections.singletonList("col1")) + .addSortCol("col2", 1) + .addSerdeParam(SERIALIZATION_FORMAT, "1").setSerdeName(tblName) + .setSerdeLib("org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe") + .setInputFormat("org.apache.hadoop.hive.ql.io.HiveInputFormat") + .setOutputFormat("org.apache.hadoop.hive.ql.io.HiveOutputFormat") + .create(client, conf); + + Table table = client.getTable(dbName, tblName); + Assert.assertTrue("Table " + dbName + "." + tblName + " does not exist", + client.tableExists(dbName, tblName)); + + List<Partition> partitions = new ArrayList<>(); + partitions.add(createPartition(Arrays.asList("CA", "SanFrancisco"), table)); + partitions.add(createPartition(Arrays.asList("CA", "PaloAlto"), table)); + partitions.add(createPartition(Arrays.asList("WA", "Seattle"), table)); + partitions.add(createPartition(Arrays.asList("AZ", "Phoenix"), table)); + + client.add_partitions(partitions); + } + + private Partition createPartition(List<String> vals, Table table) throws MetaException { + return new PartitionBuilder() + .inTable(table) + .setValues(vals) + .addPartParam("key1", "S1") + .addPartParam("key2", "S2") + .addPartParam(EXCLUDE_KEY_PREFIX + "key1", "e1") + .addPartParam(EXCLUDE_KEY_PREFIX + "key2", "e2") + .setBucketCols(table.getSd().getBucketCols()) + .setSortCols(table.getSd().getSortCols()) + .setSerdeName(table.getSd().getSerdeInfo().getName()) + .setSerdeLib(table.getSd().getSerdeInfo().getSerializationLib()) + .setSerdeParams(table.getSd().getSerdeInfo().getParameters()) + .build(conf); + } + + private static HiveMetaStoreClient createClient() throws MetaException { + MetastoreConf.setVar(conf, ConfVars.THRIFT_URIS, "thrift://localhost:" + port); + MetastoreConf.setBoolVar(conf, ConfVars.EXECUTE_SET_UGI, false); + return new HiveMetaStoreClient(conf); + } + + @Test + public void testGetPartitions() throws TException { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + validateBasic(response); + } + + @Test + public void testPartitionProjectionEmptySpec() throws Throwable { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + + projectSpec.setFieldList(new ArrayList<>(0)); + projectSpec.setExcludeParamKeyPattern("exclude%"); + + GetPartitionsResponse response; + response = client.getPartitionsWithSpecs(request); + Assert.assertEquals(1, response.getPartitionSpec().size()); + PartitionSpec partitionSpec = response.getPartitionSpec().get(0); + PartitionSpecWithSharedSD partitionSpecWithSharedSD = partitionSpec.getSharedSDPartitionSpec(); + + StorageDescriptor sharedSD = partitionSpecWithSharedSD.getSd(); + Assert.assertNotNull(sharedSD); + // everything except location in sharedSD should be same + StorageDescriptor origSd = origPartitions.get(0).getSd().deepCopy(); + origSd.unsetLocation(); + StorageDescriptor sharedSDCopy = sharedSD.deepCopy(); + sharedSDCopy.unsetLocation(); + Assert.assertEquals(origSd, sharedSDCopy); + + List<PartitionWithoutSD> partitionWithoutSDS = partitionSpecWithSharedSD.getPartitions(); + Assert.assertNotNull(partitionWithoutSDS); + Assert.assertEquals("Unexpected number of partitions returned", + origPartitions.size(), partitionWithoutSDS.size()); + for (int i = 0; i < origPartitions.size(); i++) { + Partition origPartition = origPartitions.get(i); + PartitionWithoutSD retPartition = partitionWithoutSDS.get(i); + Assert.assertEquals(origPartition.getCreateTime(), retPartition.getCreateTime()); + Assert.assertEquals(origPartition.getLastAccessTime(), retPartition.getLastAccessTime()); + Assert.assertEquals(origPartition.getSd().getLocation(), + sharedSD.getLocation() + retPartition.getRelativePath()); + validateMap(origPartition.getParameters(), retPartition.getParameters()); + validateList(origPartition.getValues(), retPartition.getValues()); + } + } + + @Test + public void testPartitionProjectionAllSingleValuedFields() throws Throwable { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + + List<String> projectedFields = Arrays + .asList("dbName", "tableName", "createTime", "lastAccessTime", "sd.location", + "sd.inputFormat", "sd.outputFormat", "sd.compressed", "sd.numBuckets", + "sd.serdeInfo.name", "sd.serdeInfo.serializationLib"/*, "sd.serdeInfo.serdeType"*/); + //TODO directSQL does not support serdeType, serializerClass and deserializerClass in serdeInfo + projectSpec.setFieldList(projectedFields); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + Assert.assertEquals(1, response.getPartitionSpec().size()); + PartitionSpec partitionSpec = response.getPartitionSpec().get(0); + Assert.assertTrue("DbName is not set", partitionSpec.isSetDbName()); + Assert.assertTrue("tableName is not set", partitionSpec.isSetTableName()); + PartitionSpecWithSharedSD partitionSpecWithSharedSD = partitionSpec.getSharedSDPartitionSpec(); + + StorageDescriptor sharedSD = partitionSpecWithSharedSD.getSd(); + Assert.assertNotNull(sharedSD); + List<PartitionWithoutSD> partitionWithoutSDS = partitionSpecWithSharedSD.getPartitions(); + Assert.assertNotNull(partitionWithoutSDS); + Assert.assertEquals(partitionWithoutSDS.size(), origPartitions.size()); + comparePartitionForSingleValuedFields(projectedFields, sharedSD, partitionWithoutSDS, 0); + } + + @Test + public void testProjectionUsingJDO() throws Throwable { + // disable direct SQL to make sure + client.setMetaConf(ConfVars.TRY_DIRECT_SQL.getVarname(), "false"); + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + List<String> projectedFields = Collections.singletonList("sd.location"); + projectSpec.setFieldList(projectedFields); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + Assert.assertEquals(1, response.getPartitionSpec().size()); + PartitionSpec partitionSpec = response.getPartitionSpec().get(0); + Assert.assertTrue("DbName is not set", partitionSpec.isSetDbName()); + Assert.assertTrue("tableName is not set", partitionSpec.isSetTableName()); + PartitionSpecWithSharedSD partitionSpecWithSharedSD = partitionSpec.getSharedSDPartitionSpec(); + + StorageDescriptor sharedSD = partitionSpecWithSharedSD.getSd(); + Assert.assertNotNull(sharedSD); + List<PartitionWithoutSD> partitionWithoutSDS = partitionSpecWithSharedSD.getPartitions(); + Assert.assertNotNull(partitionWithoutSDS); + Assert.assertEquals(partitionWithoutSDS.size(), origPartitions.size()); + comparePartitionForSingleValuedFields(projectedFields, sharedSD, partitionWithoutSDS, 0); + + // set all the single-valued fields and try using JDO + request = getGetPartitionsRequest(); + projectSpec = request.getProjectionSpec(); + projectedFields = Arrays + .asList("dbName", "tableName", "createTime", "lastAccessTime", "sd.location", + "sd.inputFormat", "sd.outputFormat", "sd.compressed", "sd.numBuckets", + "sd.serdeInfo.name", "sd.serdeInfo.serializationLib", "sd.serdeInfo.serdeType", + "sd.serdeInfo.serializerClass", "sd.serdeInfo.deserializerClass"); + projectSpec.setFieldList(projectedFields); + + response = client.getPartitionsWithSpecs(request); + Assert.assertEquals(1, response.getPartitionSpec().size()); + partitionSpec = response.getPartitionSpec().get(0); + Assert.assertTrue("DbName is not set", partitionSpec.isSetDbName()); + Assert.assertTrue("tableName is not set", partitionSpec.isSetTableName()); + partitionSpecWithSharedSD = partitionSpec.getSharedSDPartitionSpec(); + + sharedSD = partitionSpecWithSharedSD.getSd(); + Assert.assertNotNull(sharedSD); + partitionWithoutSDS = partitionSpecWithSharedSD.getPartitions(); + Assert.assertNotNull(partitionWithoutSDS); + Assert.assertEquals(partitionWithoutSDS.size(), origPartitions.size()); + comparePartitionForSingleValuedFields(projectedFields, sharedSD, partitionWithoutSDS, 0); + } + + /** + * Confirms if the partitionWithoutSD object at partitionWithoutSDSIndex index has all the + * projected fields set to values which are same as the ones set in origPartitions + * @param projectedFields + * @param sharedSD + * @param partitionWithoutSDS + * @param partitionWithoutSDSIndex + * @throws IllegalAccessException + * @throws InvocationTargetException + * @throws NoSuchMethodException + */ + private void comparePartitionForSingleValuedFields(List<String> projectedFields, + StorageDescriptor sharedSD, List<PartitionWithoutSD> partitionWithoutSDS, int partitionWithoutSDSIndex) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + for (Partition origPart : origPartitions) { + for (String projectField : projectedFields) { + // dbname, tableName and catName is not stored in partition + if (projectField.equals("dbName") || projectField.equals("tableName") || projectField + .equals("catName")) + continue; + if (projectField.startsWith("sd")) { + String sdPropertyName = projectField.substring(projectField.indexOf("sd.") + 3); + if (sdPropertyName.equals("location")) { + // in case of location sharedSD has the base location and partition has relative location + Assert.assertEquals("Location does not match", origPart.getSd().getLocation(), + sharedSD.getLocation() + partitionWithoutSDS.get(partitionWithoutSDSIndex).getRelativePath()); + } else { + Assert.assertEquals(PropertyUtils.getNestedProperty(origPart, projectField), + PropertyUtils.getNestedProperty(sharedSD, sdPropertyName)); + } + } else { + Assert.assertEquals(PropertyUtils.getNestedProperty(origPart, projectField), + PropertyUtils.getNestedProperty(partitionWithoutSDS.get(partitionWithoutSDSIndex), projectField)); + } + } + partitionWithoutSDSIndex++; + } + } + + @Test + public void testPartitionProjectionAllMultiValuedFields() throws Throwable { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + List<String> projectedFields = Arrays + .asList("values", "parameters", "sd.cols", "sd.bucketCols", "sd.sortCols", "sd.parameters", + "sd.skewedInfo", "sd.serdeInfo.parameters"); + projectSpec.setFieldList(projectedFields); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + + Assert.assertEquals(1, response.getPartitionSpec().size()); + PartitionSpec partitionSpec = response.getPartitionSpec().get(0); + PartitionSpecWithSharedSD partitionSpecWithSharedSD = partitionSpec.getSharedSDPartitionSpec(); + Assert.assertEquals(origPartitions.size(), partitionSpecWithSharedSD.getPartitions().size()); + StorageDescriptor sharedSD = partitionSpecWithSharedSD.getSd(); + for (int i = 0; i < origPartitions.size(); i++) { + Partition origPartition = origPartitions.get(i); + PartitionWithoutSD retPartition = partitionSpecWithSharedSD.getPartitions().get(i); + for (String projectedField : projectedFields) { + switch (projectedField) { + case "values": + validateList(origPartition.getValues(), retPartition.getValues()); + break; + case "parameters": + validateMap(origPartition.getParameters(), retPartition.getParameters()); + break; + case "sd.cols": + validateList(origPartition.getSd().getCols(), sharedSD.getCols()); + break; + case "sd.bucketCols": + validateList(origPartition.getSd().getBucketCols(), sharedSD.getBucketCols()); + break; + case "sd.sortCols": + validateList(origPartition.getSd().getSortCols(), sharedSD.getSortCols()); + break; + case "sd.parameters": + validateMap(origPartition.getSd().getParameters(), sharedSD.getParameters()); + break; + case "sd.skewedInfo": + if (!origPartition.getSd().getSkewedInfo().getSkewedColNames().isEmpty()) { + validateList(origPartition.getSd().getSkewedInfo().getSkewedColNames(), + sharedSD.getSkewedInfo().getSkewedColNames()); + } + if (!origPartition.getSd().getSkewedInfo().getSkewedColValues().isEmpty()) { + for (int i1 = 0; + i1 < origPartition.getSd().getSkewedInfo().getSkewedColValuesSize(); i1++) { + validateList(origPartition.getSd().getSkewedInfo().getSkewedColValues().get(i1), + sharedSD.getSkewedInfo().getSkewedColValues().get(i1)); + } + } + if (!origPartition.getSd().getSkewedInfo().getSkewedColValueLocationMaps().isEmpty()) { + validateMap(origPartition.getSd().getSkewedInfo().getSkewedColValueLocationMaps(), + sharedSD.getSkewedInfo().getSkewedColValueLocationMaps()); + } + break; + case "sd.serdeInfo.parameters": + validateMap(origPartition.getSd().getSerdeInfo().getParameters(), + sharedSD.getSerdeInfo().getParameters()); + break; + default: + throw new IllegalArgumentException("Invalid field " + projectedField); + } + } + } + } + + @Test + public void testPartitionProjectionIncludeParameters() throws Throwable { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec + .setFieldList(Arrays.asList("dbName", "tableName", "catName", "parameters", "values")); + projectSpec.setIncludeParamKeyPattern(EXCLUDE_KEY_PREFIX + "%"); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + Assert.assertNotNull("All the partitions should be returned in sharedSD spec", + partitionSpecWithSharedSD); + PartitionListComposingSpec partitionListComposingSpec = + response.getPartitionSpec().get(0).getPartitionList(); + Assert.assertNull("Partition list composing spec should be null since all the " + + "partitions are expected to be in sharedSD spec", partitionListComposingSpec); + for (PartitionWithoutSD retPartion : partitionSpecWithSharedSD.getPartitions()) { + Assert.assertTrue("included parameter key is not found in the response", + retPartion.getParameters().containsKey(EXCLUDE_KEY_PREFIX + "key1")); + Assert.assertTrue("included parameter key is not found in the response", + retPartion.getParameters().containsKey(EXCLUDE_KEY_PREFIX + "key2")); + Assert.assertEquals("Additional parameters returned other than inclusion keys", + 2, retPartion.getParameters().size()); + } + } + + @Test + public void testPartitionProjectionIncludeExcludeParameters() throws Throwable { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec + .setFieldList(Arrays.asList("dbName", "tableName", "catName", "parameters", "values")); + // test parameter key inclusion using setIncludeParamKeyPattern + projectSpec.setIncludeParamKeyPattern(EXCLUDE_KEY_PREFIX + "%"); + projectSpec.setExcludeParamKeyPattern("%key1%"); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + Assert.assertNotNull("All the partitions should be returned in sharedSD spec", + partitionSpecWithSharedSD); + PartitionListComposingSpec partitionListComposingSpec = + response.getPartitionSpec().get(0).getPartitionList(); + Assert.assertNull("Partition list composing spec should be null since all the " + + "partitions are expected to be in sharedSD spec", partitionListComposingSpec); + for (PartitionWithoutSD retPartion : partitionSpecWithSharedSD.getPartitions()) { + Assert.assertFalse("excluded parameter key is found in the response", + retPartion.getParameters().containsKey(EXCLUDE_KEY_PREFIX + "key1")); + Assert.assertTrue("included parameter key is not found in the response", + retPartion.getParameters().containsKey(EXCLUDE_KEY_PREFIX + "key2")); + Assert.assertEquals("Additional parameters returned other than inclusion keys", + 1, retPartion.getParameters().size()); + } + } + + @Test + public void testPartitionProjectionExcludeParameters() throws Throwable { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec + .setFieldList(Arrays.asList("dbName", "tableName", "catName", "parameters", "values")); + projectSpec.setExcludeParamKeyPattern(EXCLUDE_KEY_PREFIX + "%"); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + Assert.assertNotNull("All the partitions should be returned in sharedSD spec", + partitionSpecWithSharedSD); + PartitionListComposingSpec partitionListComposingSpec = + response.getPartitionSpec().get(0).getPartitionList(); + Assert.assertNull("Partition list composing spec should be null", partitionListComposingSpec); + for (PartitionWithoutSD retPartion : partitionSpecWithSharedSD.getPartitions()) { + Assert.assertFalse("excluded parameter key is found in the response", + retPartion.getParameters().containsKey(EXCLUDE_KEY_PREFIX + "key1")); + Assert.assertFalse("excluded parameter key is found in the response", + retPartion.getParameters().containsKey(EXCLUDE_KEY_PREFIX + "key2")); + } + } + + @Test + public void testNestedMultiValuedFieldProjection() throws TException { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec.setFieldList(Arrays.asList("sd.cols.name", "sd.cols.type")); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + StorageDescriptor sharedSD = partitionSpecWithSharedSD.getSd(); + Assert.assertNotNull("sd.cols were requested but was not returned", sharedSD.getCols()); + for (FieldSchema col : sharedSD.getCols()) { + Assert.assertTrue("sd.cols.name was requested but was not returned", col.isSetName()); + Assert.assertTrue("sd.cols.type was requested but was not returned", col.isSetType()); + Assert.assertFalse("sd.cols.comment was not requested but was returned", col.isSetComment()); + } + } + + @Test + public void testParameterExpansion() throws TException { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec.setFieldList(Arrays.asList("sd.cols", "sd.serdeInfo")); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + StorageDescriptor sharedSD = partitionSpecWithSharedSD.getSd(); + Assert.assertNotNull("sd.cols were requested but was not returned", sharedSD.getCols()); + Assert.assertEquals("Returned serdeInfo does not match with original serdeInfo", + origPartitions.get(0).getSd().getCols(), sharedSD.getCols()); + + Assert + .assertNotNull("sd.serdeInfo were requested but was not returned", sharedSD.getSerdeInfo()); + Assert.assertEquals("Returned serdeInfo does not match with original serdeInfo", + origPartitions.get(0).getSd().getSerdeInfo(), sharedSD.getSerdeInfo()); + } + + @Test + public void testNonStandardPartitions() throws TException { + String testTblName = "test_non_standard"; + new TableBuilder() + .setTableName(testTblName) + .setDbName(dbName) + .addCol("ns_c1", "string", "comment 1") + .addCol("ns_c2", "int", "comment 2") + .addPartCol("part", "string") + .addPartCol("city", "string") + .addBucketCol("ns_c1") + .addSortCol("ns_c2", 1) + .addTableParam("tblparamKey", "Partitions of this table are not located within table directory") + .create(client, conf); + + Table table = client.getTable(dbName, testTblName); + Assert.assertNotNull("Unable to create a test table ", table); + + List<Partition> partitions = new ArrayList<>(); + partitions.add(createPartition(Arrays.asList("p1", "SanFrancisco"), table)); + partitions.add(createPartition(Arrays.asList("p1", "PaloAlto"), table)); + partitions.add(createPartition(Arrays.asList("p2", "Seattle"), table)); + partitions.add(createPartition(Arrays.asList("p2", "Phoenix"), table)); + + client.add_partitions(partitions); + // change locations of two of the partitions outside table directory + List<Partition> testPartitions = client.listPartitions(dbName, testTblName, (short) -1); + Assert.assertEquals(4, testPartitions.size()); + Partition p1 = testPartitions.get(2); + p1.getSd().setLocation("/tmp/some_other_location/part=p2/city=Seattle"); + Partition p2 = testPartitions.get(3); + p2.getSd().setLocation("/tmp/some_other_location/part=p2/city=Phoenix"); + client.alter_partitions(dbName, testTblName, Arrays.asList(p1, p2)); + + GetPartitionsRequest request = getGetPartitionsRequest(); + request.getProjectionSpec().setFieldList(Arrays.asList("values", "sd")); + request.setDbName(dbName); + request.setTblName(testTblName); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + Assert.assertNotNull("Response should have returned partition specs", + response.getPartitionSpec()); + Assert + .assertEquals("We should have two partition specs", 2, response.getPartitionSpec().size()); + Assert.assertNotNull("One SharedSD spec is expected", + response.getPartitionSpec().get(0).getSharedSDPartitionSpec()); + Assert.assertNotNull("One composing spec is expected", + response.getPartitionSpec().get(1).getPartitionList()); + + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + Assert.assertNotNull("sd was requested but not returned", partitionSpecWithSharedSD.getSd()); + Assert.assertEquals("shared SD should have table location", table.getSd().getLocation(), + partitionSpecWithSharedSD.getSd().getLocation()); + List<List<String>> expectedVals = new ArrayList<>(2); + expectedVals.add(Arrays.asList("p1", "PaloAlto")); + expectedVals.add(Arrays.asList("p1", "SanFrancisco")); + + for (int i=0; i<partitionSpecWithSharedSD.getPartitions().size(); i++) { + PartitionWithoutSD retPartition = partitionSpecWithSharedSD.getPartitions().get(i); + Assert.assertEquals(2, retPartition.getValuesSize()); + validateList(expectedVals.get(i), retPartition.getValues()); + Assert.assertNull("parameters were not requested so should have been null", + retPartition.getParameters()); + } + + PartitionListComposingSpec composingSpec = + response.getPartitionSpec().get(1).getPartitionList(); + Assert.assertNotNull("composing spec should have returned 2 partitions", + composingSpec.getPartitions()); + Assert.assertEquals("composing spec should have returned 2 partitions", 2, + composingSpec.getPartitionsSize()); + + expectedVals.clear(); + expectedVals.add(Arrays.asList("p2", "Phoenix")); + expectedVals.add(Arrays.asList("p2", "Seattle")); + for (int i=0; i<composingSpec.getPartitions().size(); i++) { + Partition partition = composingSpec.getPartitions().get(i); + Assert.assertEquals(2, partition.getValuesSize()); + validateList(expectedVals.get(i), partition.getValues()); + Assert.assertNull("parameters were not requested so should have been null", + partition.getParameters()); + } + } + + @Test + public void testGetPartitionsWithFilterExpr() throws TException { + runGetPartitionsUsingExpr(); + } + + @Test + public void testGetPartitionsUsingNames() throws Exception { + runGetPartitionsUsingNames(); + } + + @Test + public void testGetPartitionsUsingValues() throws Exception { + runGetPartitionsUsingVals(); + } + + @Test + public void testGetPartitionsUsingExprWithJDO() throws Exception { + // disable direct SQL to make sure + client.setMetaConf(ConfVars.TRY_DIRECT_SQL.getVarname(), "false"); + runGetPartitionsUsingExpr(); + } + + @Test + public void testGetPartitionsUsingValuesWithJDO() throws Exception { + // disable direct SQL to make sure + client.setMetaConf(ConfVars.TRY_DIRECT_SQL.getVarname(), "false"); + runGetPartitionsUsingVals(); + } + + @Test + public void testGetPartitionsUsingNamesWithJDO() throws Exception { + // disable direct SQL to make sure + client.setMetaConf(ConfVars.TRY_DIRECT_SQL.getVarname(), "false"); + runGetPartitionsUsingNames(); + } + + @Test(expected = MetaException.class) + public void testInvalidFilterByNames() throws Exception { + runWithInvalidFilterByNames(); + } + + @Test(expected = MetaException.class) + public void testInvalidFilterByNamesWithJDO() throws Exception { + // disable direct SQL to make sure + client.setMetaConf(ConfVars.TRY_DIRECT_SQL.getVarname(), "false"); + runWithInvalidFilterByNames(); + } + + @Test(expected = MetaException.class) + public void testInvalidProjectFieldNames() throws TException { + runWithInvalidFieldNames(Arrays.asList("values", "invalid.field.name")); + } + + @Test(expected = MetaException.class) + public void testInvalidProjectFieldNames2() throws TException { + runWithInvalidFieldNames(Arrays.asList("")); + } + + @Test(expected = MetaException.class) + public void testInvalidProjectFieldNamesWithJDO() throws TException { + // disable direct SQL to make sure + client.setMetaConf(ConfVars.TRY_DIRECT_SQL.getVarname(), "false"); + runWithInvalidFieldNames(Arrays.asList("values", "invalid.field.name")); + } + + @Test(expected = MetaException.class) + public void testInvalidProjectFieldNames2WithJDO() throws TException { + // disable direct SQL to make sure + client.setMetaConf(ConfVars.TRY_DIRECT_SQL.getVarname(), "false"); + runWithInvalidFieldNames(Arrays.asList("")); + } + + private void runWithInvalidFilterByNames() throws TException { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec.setFieldList(Arrays.asList("sd.location")); + request.getFilterSpec().setFilterMode(PartitionFilterMode.BY_NAMES); + // filter mode is set but not filters are provided + client.getPartitionsWithSpecs(request); + } + + private void runWithInvalidFieldNames(List<String> values) throws TException { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec.setFieldList(values); + client.getPartitionsWithSpecs(request); + } + + private void runGetPartitionsUsingExpr() throws TException { + // test simple case first + getPartitionsWithExpr(Arrays.asList("state=\"CA\""), 2); + // Logical AND in filter + getPartitionsWithExpr(Arrays.asList("state=\"CA\" AND city=\"PaloAlto\""), 1); + // empty result set + getPartitionsWithExpr(Arrays.asList("state=\"CA\" AND city=\"Seattle\""), 0); + + // NOT operator + getPartitionsWithExpr(Arrays.asList("state=\"CA\" AND city !=\"PaloAlto\""), 1); + // nested expr + getPartitionsWithExpr(Arrays + .asList("(state=\"CA\" AND city !=\"PaloAlto\") OR (state=\"WA\" AND city = \"Seattle\")"), + 2); + + // multiple filters + getPartitionsWithExpr(Arrays.asList("state=\"CA\"", "city=\"PaloAlto\""), 1); + getPartitionsWithExpr( + Arrays.asList("state=\"CA\" OR state=\"WA\"", "city=\"PaloAlto\" OR city=\"Seattle\""), 2); + // test empty result + getPartitionsWithExpr(Arrays.asList("state=\"AZ\"", "city=\"Tucson\""), 0); + } + + private void getPartitionsWithExpr(List<String> filters, int expectedPartition) throws TException { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec.setFieldList(Arrays.asList("sd.location")); + request.getFilterSpec().setFilterMode(PartitionFilterMode.BY_EXPR); + request.getFilterSpec().setFilters(filters); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + Assert.assertNotNull(response); + if (expectedPartition > 0) { + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + Assert.assertNotNull(partitionSpecWithSharedSD); + Assert.assertEquals("Invalid number of partitions returned", expectedPartition, + partitionSpecWithSharedSD.getPartitionsSize()); + } else { + Assert.assertTrue( + "Partition spec should have been empty since filter doesn't match with any partitions", + response.getPartitionSpec().isEmpty()); + } + } + + private void getPartitionsWithVals(List<String> filters, int expectedPartitions) + throws TException { + // get partitions from "trusted" API + List<Partition> partitions = client.listPartitions(dbName, tblName, filters, (short) -1); + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec.setFieldList(Arrays.asList("sd.location")); + request.getFilterSpec().setFilterMode(PartitionFilterMode.BY_VALUES); + request.getFilterSpec().setFilters(filters); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + Assert.assertNotNull(response); + if (expectedPartitions > 0) { + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + Assert.assertNotNull(partitionSpecWithSharedSD); + Assert.assertEquals("Invalid number of partitions returned", expectedPartitions, + partitionSpecWithSharedSD.getPartitionsSize()); + verifyLocations(partitions, partitionSpecWithSharedSD.getSd(), + partitionSpecWithSharedSD.getPartitions()); + } else { + Assert.assertTrue( + "Partition spec should have been empty since filter doesn't match with any partitions", + response.getPartitionSpec().isEmpty()); + } + } + + private void runGetPartitionsUsingVals() throws TException { + // top level val set + getPartitionsWithVals(Arrays.asList("CA"), 2); + // exactly one partition + getPartitionsWithVals(Arrays.asList("CA", "PaloAlto"), 1); + // non-existing partition should return zero partitions + getPartitionsWithVals(Arrays.asList("CA", "CityDoesNotExist"), 0); + } + + private void getPartitionsWithNames(List<String> names, int expectedPartitionCount) throws TException { + GetPartitionsRequest request = getGetPartitionsRequest(); + GetPartitionsProjectionSpec projectSpec = request.getProjectionSpec(); + projectSpec.setFieldList(Arrays.asList("sd.location")); + request.getFilterSpec().setFilterMode(PartitionFilterMode.BY_NAMES); + request.getFilterSpec().setFilters(names); + + GetPartitionsResponse response = client.getPartitionsWithSpecs(request); + Assert.assertNotNull(response); + if (expectedPartitionCount > 0) { + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + Assert.assertNotNull(partitionSpecWithSharedSD); + Assert.assertEquals("Invalid number of partitions returned", expectedPartitionCount, + partitionSpecWithSharedSD.getPartitionsSize()); + List<Partition> origPartitions = client.getPartitionsByNames(dbName, tblName, names); + verifyLocations(origPartitions, partitionSpecWithSharedSD.getSd(), partitionSpecWithSharedSD.getPartitions()); + } else { + Assert.assertTrue( + "Partition spec should have been empty since filter doesn't match with any partitions", + response.getPartitionSpec().isEmpty()); + } + } + + private void runGetPartitionsUsingNames() throws TException { + List<String> names = client.listPartitionNames(dbName, tblName, (short) -1); + // remove one to make sure that the test is really looking at 3 names + names.remove(names.size() - 1); + getPartitionsWithNames(names, 3); + // filter mode is set. Empty filter names. So no partitions should be returned + getPartitionsWithNames(Arrays.asList(""), 0); + // invalid name + getPartitionsWithNames(Arrays.asList("invalidPartitionName"), 0); + } + + private void validateBasic(GetPartitionsResponse response) throws TException { + Assert.assertNotNull("Response is null", response); + Assert.assertNotNull("Returned partition spec is null", response.getPartitionSpec()); + Assert.assertEquals(1, response.getPartitionSpecSize()); + PartitionSpecWithSharedSD partitionSpecWithSharedSD = + response.getPartitionSpec().get(0).getSharedSDPartitionSpec(); + Assert.assertNotNull(partitionSpecWithSharedSD.getSd()); + StorageDescriptor sharedSD = partitionSpecWithSharedSD.getSd(); + Assert.assertEquals("Root location should be set to table location", tbl.getSd().getLocation(), + sharedSD.getLocation()); + + List<PartitionWithoutSD> partitionWithoutSDS = partitionSpecWithSharedSD.getPartitions(); + Assert.assertEquals(origPartitions.size(), partitionWithoutSDS.size()); + for (int i = 0; i < origPartitions.size(); i++) { + Partition origPartition = origPartitions.get(i); + PartitionWithoutSD returnedPartitionWithoutSD = partitionWithoutSDS.get(i); + Assert.assertEquals(String.format("Location returned for Partition %d is not correct", i), + origPartition.getSd().getLocation(), + sharedSD.getLocation() + returnedPartitionWithoutSD.getRelativePath()); + } + } + + private GetPartitionsRequest getGetPartitionsRequest() { + GetPartitionsRequest request = new GetPartitionsRequest(); + request.setProjectionSpec(new GetPartitionsProjectionSpec()); + request.setFilterSpec(new GetPartitionsFilterSpec()); + request.setTblName(tblName); + request.setDbName(dbName); + return request; + } + + private void verifyLocations(List<Partition> origPartitions, StorageDescriptor sharedSD, + List<PartitionWithoutSD> partitionWithoutSDS) { + int i=0; + for (Partition origPart : origPartitions) { + // in case of location sharedSD has the base location and partition has relative location + Assert.assertEquals("Location does not match", origPart.getSd().getLocation(), + sharedSD.getLocation() + partitionWithoutSDS.get(i).getRelativePath()); + Assert.assertNull("values were not requested but are still set", + partitionWithoutSDS.get(i).getValues()); + Assert.assertNull("Parameters were not requested but are still set", + partitionWithoutSDS.get(i).getParameters()); + i++; + } + } + + private <K, V> void validateMap(Map<K, V> aMap, Map<K, V> bMap) { + if ((aMap == null || aMap.isEmpty()) && (bMap == null || bMap.isEmpty())) { + return; + } + // Equality is verified here because metastore updates stats automatically + // and adds them in the returned partition. So the returned partition will + // have parameters + some more parameters for the basic stats + Assert.assertTrue(bMap.size() >= aMap.size()); + for (Entry<K, V> entries : aMap.entrySet()) { + Assert.assertTrue("Expected " + entries.getKey() + " is missing from the map", + bMap.containsKey(entries.getKey())); + Assert.assertEquals("Expected value to be " + aMap.get(entries.getKey()) + " found" + bMap + .get(entries.getKey()), aMap.get(entries.getKey()), bMap.get(entries.getKey())); + } + } + + private <T> void validateList(List<T> aList, List<T> bList) { + if ((aList == null || aList.isEmpty()) && (bList == null || bList.isEmpty())) { + return; + } + Assert.assertEquals(aList.size(), bList.size()); + Iterator<T> origValuesIt = aList.iterator(); + Iterator<T> retValuesIt = bList.iterator(); + while (origValuesIt.hasNext()) { + Assert.assertTrue(retValuesIt.hasNext()); + Assert.assertEquals(origValuesIt.next(), retValuesIt.next()); + } + } +}