This is an automated email from the ASF dual-hosted git repository.

dengzhhu653 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git


The following commit(s) were added to refs/heads/master by this push:
     new 82edd2a2ef0 HIVE-29694: Reuse DB column descriptors when adding 
partitions in HMS (#6577)
82edd2a2ef0 is described below

commit 82edd2a2ef04c7d75431f285f01c85a535ed07c5
Author: InvisibleProgrammer <[email protected]>
AuthorDate: Fri Jul 10 04:45:22 2026 +0200

    HIVE-29694: Reuse DB column descriptors when adding partitions in HMS 
(#6577)
    
    Co-authored-by: Stamatis Zampetakis <[email protected]>
    Co-authored-by: zdeng <[email protected]>
---
 .../hadoop/hive/metastore/conf/MetastoreConf.java  |   9 +-
 .../metastore/directsql/MetaStoreDirectSql.java    | 247 ++++++++++++++++-----
 .../directsql/MetastoreDirectSqlUtils.java         |   6 +
 .../metastore/metastore/impl/TableStoreImpl.java   |  80 +++++--
 .../metastore/TestHMSColumnDescriptorReuse.java    | 220 ++++++++++++++++++
 5 files changed, 486 insertions(+), 76 deletions(-)

diff --git 
a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
 
b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
index e955d1e07d6..0a571587eba 100644
--- 
a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
+++ 
b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
@@ -312,9 +312,6 @@ public enum ConfVars {
     ACID_HOUSEKEEPER_SERVICE_INTERVAL("metastore.acid.housekeeper.interval",
         "hive.metastore.acid.housekeeper.interval", 60, TimeUnit.SECONDS,
         "Time interval describing how often the acid housekeeper runs."),
-    
COMPACTION_HOUSEKEEPER_SERVICE_INTERVAL("metastore.compaction.housekeeper.interval",
-        "hive.metastore.compaction.housekeeper.interval", 300, 
TimeUnit.SECONDS,
-        "Time interval describing how often the acid compaction housekeeper 
runs."),
     ACID_TXN_CLEANER_INTERVAL("metastore.acid.txn.cleaner.interval",
         "hive.metastore.acid.txn.cleaner.interval", 10, TimeUnit.SECONDS,
         "Time interval describing how often aborted and committed txns are 
cleaned."),
@@ -432,6 +429,9 @@ public enum ConfVars {
             TimeUnit.SECONDS, "MetaStore Client socket timeout in seconds"),
     CLIENT_CONNECTION_TIMEOUT("metastore.client.connection.timeout", 
"hive.metastore.client.connection.timeout", 600,
             TimeUnit.SECONDS, "MetaStore Client connection timeout in 
seconds"),
+    
COMPACTION_HOUSEKEEPER_SERVICE_INTERVAL("metastore.compaction.housekeeper.interval",
+        "hive.metastore.compaction.housekeeper.interval", 300, 
TimeUnit.SECONDS,
+        "Time interval describing how often the acid compaction housekeeper 
runs."),
     
COMPACTOR_HISTORY_RETENTION_DID_NOT_INITIATE("metastore.compactor.history.retention.did.not.initiate",
         "hive.compactor.history.retention.did.not.initiate", 2,
         new RangeValidator(0, 100), "Determines how many compaction records in 
state " +
@@ -1373,6 +1373,9 @@ public enum ConfVars {
         "hive.metastore.partition.order.expr", "\"PART_NAME\" asc",
         "The default partition order if the metastore does not return all 
partitions. \n" +
             "It can be sorted based on any column in the PARTITIONS table 
(e.g., \"PARTITIONS\".\"CREATE_TIME\" desc, \"PARTITIONS\".\"LAST_ACCESS_TIME\" 
desc etc)"),
+    
PARTITION_REUSE_COLUMN_DESCRIPTORS("metastore.partition.reuse.column.descriptors",
+        "hive.metastore.partition.reuse.column.descriptors", false,
+        "Add partition reuse existing column descriptors to avoid metadata 
bloat on schema evolution."),
     PART_INHERIT_TBL_PROPS("metastore.partition.inherit.table.properties",
         "hive.metastore.partition.inherit.table.properties", "",
         "List of comma separated keys occurring in table properties which will 
get inherited to newly created partitions. \n" +
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetaStoreDirectSql.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetaStoreDirectSql.java
index 713f3ff4e33..7c95577e261 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetaStoreDirectSql.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetaStoreDirectSql.java
@@ -29,6 +29,8 @@
 import static org.apache.hadoop.hive.metastore.ColumnType.TIMESTAMP_TYPE_NAME;
 import static org.apache.hadoop.hive.metastore.ColumnType.TINYINT_TYPE_NAME;
 import static org.apache.hadoop.hive.metastore.ColumnType.VARCHAR_TYPE_NAME;
+import static 
org.apache.hadoop.hive.metastore.directsql.MetastoreDirectSqlUtils.extractSqlClob;
+import static 
org.apache.hadoop.hive.metastore.directsql.MetastoreDirectSqlUtils.extractSqlString;
 import static 
org.apache.hadoop.hive.metastore.directsql.MetastoreDirectSqlUtils.getFullyQualifiedName;
 import static 
org.apache.hadoop.hive.metastore.directsql.MetastoreDirectSqlUtils.makeParams;
 import static 
org.apache.hadoop.hive.metastore.directsql.MetastoreDirectSqlUtils.prepareParams;
@@ -107,6 +109,7 @@
 import org.apache.hadoop.hive.metastore.client.builder.GetPartitionsArgs;
 import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
 import org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars;
+import org.apache.hadoop.hive.metastore.model.MColumnDescriptor;
 import org.apache.hadoop.hive.metastore.model.MConstraint;
 import org.apache.hadoop.hive.metastore.model.MCreationMetadata;
 import org.apache.hadoop.hive.metastore.model.MDatabase;
@@ -432,28 +435,27 @@ public Database getDatabase(String catName, String 
dbName) throws MetaException{
           queryDbParams.getInnerQuery(), params, queryTextDbParams));
       if (!sqlResult2.isEmpty()) {
         for (Object[] line : sqlResult2) {
-          dbParams.put(MetastoreDirectSqlUtils.extractSqlString(line[0]), 
MetastoreDirectSqlUtils
-              .extractSqlString(line[1]));
+          dbParams.put(extractSqlString(line[0]), extractSqlString(line[1]));
         }
       }
       Database db = new Database();
-      db.setName(MetastoreDirectSqlUtils.extractSqlString(dbline[1]));
-      db.setLocationUri(MetastoreDirectSqlUtils.extractSqlString(dbline[2]));
-      db.setDescription(MetastoreDirectSqlUtils.extractSqlString(dbline[3]));
-      db.setOwnerName(MetastoreDirectSqlUtils.extractSqlString(dbline[4]));
-      String type = MetastoreDirectSqlUtils.extractSqlString(dbline[5]);
+      db.setName(extractSqlString(dbline[1]));
+      db.setLocationUri(extractSqlString(dbline[2]));
+      db.setDescription(extractSqlString(dbline[3]));
+      db.setOwnerName(extractSqlString(dbline[4]));
+      String type = extractSqlString(dbline[5]);
       db.setOwnerType(
           (null == type || type.trim().isEmpty()) ? null : 
PrincipalType.valueOf(type));
-      db.setCatalogName(MetastoreDirectSqlUtils.extractSqlString(dbline[6]));
+      db.setCatalogName(extractSqlString(dbline[6]));
       if (dbline[7] != null) {
         db.setCreateTime(MetastoreDirectSqlUtils.extractSqlInt(dbline[7]));
       }
-      
db.setManagedLocationUri(MetastoreDirectSqlUtils.extractSqlString(dbline[8]));
-      String dbType = MetastoreDirectSqlUtils.extractSqlString(dbline[9]);
+      db.setManagedLocationUri(extractSqlString(dbline[8]));
+      String dbType = extractSqlString(dbline[9]);
       if (dbType != null && 
dbType.equalsIgnoreCase(DatabaseType.REMOTE.name())) {
         db.setType(DatabaseType.REMOTE);
-        
db.setConnector_name(MetastoreDirectSqlUtils.extractSqlString(dbline[10]));
-        
db.setRemote_dbname(MetastoreDirectSqlUtils.extractSqlString(dbline[11]));
+        db.setConnector_name(extractSqlString(dbline[10]));
+        db.setRemote_dbname(extractSqlString(dbline[11]));
       } else {
         db.setType(DatabaseType.NATIVE);
       }
@@ -534,6 +536,132 @@ public void addPartitions(List<MPartition> parts, 
List<List<MPartitionPrivilege>
     directSqlInsertPart.addPartitions(parts, partPrivilegesList, 
partColPrivilegesList);
   }
 
+  /**
+   * Gets a suitable column descriptor for an existing table, based on the 
latest partitions
+   *
+   * @param cols column list of a partition
+   * @param tblId table id
+   * @return an existing column descriptor which columns matches with @cols.  
Null if there is no match.
+   * @throws MetaException
+   */
+  public MColumnDescriptor getColumnDescriptor(List<FieldSchema> cols, long 
tblId)
+      throws MetaException {
+    if (cols == null || cols.isEmpty()) {
+      return null;
+    }
+
+    // Please note! In case you modify any of those methods,
+    // run TestHMSColumnDescriptorReuse with all the supported databases
+    List<Long> cdCandidates = 
filterCandidatesByColumnCount(findTheLatestColumnDescriptors(tblId), 
cols.size());
+    if (cdCandidates.isEmpty()) {
+      return null;
+    }
+
+    Long matchedColumnDescriptorId = 
matchColumnDescriptorWithActualColumns(cdCandidates, cols);
+    if (matchedColumnDescriptorId != null) {
+      return pm.getObjectById(MColumnDescriptor.class, 
matchedColumnDescriptorId);
+    }
+    return null;
+  }
+
+  private List<Long> findTheLatestColumnDescriptors(long tblId) throws 
MetaException {
+    final int limit = 20;
+    String findLatestDescriptorsSql =
+        """
+          SELECT s."CD_ID"
+          FROM "PARTITIONS" p
+          JOIN "SDS" s ON s."SD_ID" = p."SD_ID"
+          WHERE p."TBL_ID" = ?
+          GROUP BY s."CD_ID"
+          ORDER BY MAX(p."PART_ID") DESC
+        """;
+
+    try (QueryWrapper query = new 
QueryWrapper(pm.newQuery("javax.jdo.query.SQL", findLatestDescriptorsSql))) {
+
+      List<Object> sqlResult = executeWithArray(
+          query.getInnerQuery(), new Object[]{tblId}, 
findLatestDescriptorsSql, limit);
+      if (sqlResult == null || sqlResult.isEmpty()) {
+        return Collections.emptyList();
+      }
+
+      List<Long> latestColumnDescriptorIds = new ArrayList<>();
+      for (Object cdId : sqlResult) {
+        
latestColumnDescriptorIds.add(MetastoreDirectSqlUtils.extractSqlLong(cdId));
+      }
+      return latestColumnDescriptorIds;
+    }
+  }
+
+  private List<Long> filterCandidatesByColumnCount(List<Long> cdCandidates, 
int size) throws MetaException {
+    if (cdCandidates == null || cdCandidates.isEmpty()) {
+      return Collections.emptyList();
+    }
+
+    String placeholders = cdCandidates.stream()
+        .map(c -> "?")
+        .collect(Collectors.joining(", "));
+    String candidatesWithProperColumnCountSql = String.format(
+        """
+          SELECT c."CD_ID"
+          FROM "COLUMNS_V2" c
+          WHERE c."CD_ID" IN (%s)
+          GROUP BY c."CD_ID"
+          HAVING COUNT(*) = ?
+        """, placeholders);
+
+    try (QueryWrapper query =
+             new QueryWrapper(pm.newQuery("javax.jdo.query.SQL", 
candidatesWithProperColumnCountSql))) {
+
+      Object[] params = new Object[cdCandidates.size() + 1];
+      for (int i = 0; i < cdCandidates.size(); i++) {
+        params[i] = cdCandidates.get(i);
+      }
+      params[cdCandidates.size()] = size;
+
+      List<Object> result = executeWithArray(query.getInnerQuery(), params, 
candidatesWithProperColumnCountSql);
+      if (result == null || result.isEmpty()) {
+        return Collections.emptyList();
+      }
+      List<Long> candidateIds = new  ArrayList<>();
+      for (Object cdId : result) {
+        candidateIds.add(MetastoreDirectSqlUtils.extractSqlLong(cdId));
+      }
+      return candidateIds;
+    }
+  }
+
+  private Long matchColumnDescriptorWithActualColumns(List<Long> cdCandidates, 
List<FieldSchema> cols)
+      throws MetaException {
+    if (cdCandidates == null || cdCandidates.isEmpty()) {
+      return null;
+    }
+
+    String findColumnSql = "SELECT \"COLUMN_NAME\", \"TYPE_NAME\", \"COMMENT\" 
FROM \"COLUMNS_V2\" "
+        + "WHERE \"CD_ID\" = ? ORDER BY \"INTEGER_IDX\"";
+    for (Long candidate: cdCandidates) {
+      try (QueryWrapper query = new 
QueryWrapper(pm.newQuery("javax.jdo.query.SQL", findColumnSql))) {
+        List<Object[]> rows = executeWithArray(query.getInnerQuery(), new 
Object[]{candidate}, findColumnSql);
+        if (rows != null && rows.size() == cols.size()) {
+          int i = 0;
+          for (; i < cols.size(); i++) {
+            Object[] row = rows.get(i);
+            FieldSchema col = new FieldSchema(
+                extractSqlString(row[0]),
+                extractSqlClob(row[1]),
+                extractSqlString(row[2]));
+            if (!cols.get(i).equals(col)) {
+              break;
+            }
+          }
+          if (i ==  cols.size()) {
+            return candidate;
+          }
+        }
+      }
+    }
+    return null;
+  }
+
   /**
    * Alter partitions in batch using direct SQL
    * @param table the target table
@@ -1685,17 +1813,17 @@ public List<HiveObjectPrivilege> 
getTableAllColumnGrants(String catName, String
 
       // If there is some result convert it into HivePrivilege bag and return.
       for (Object[] privLine : queryResult) {
-        String privAuthorizer = 
MetastoreDirectSqlUtils.extractSqlString(privLine[authorizerIndex]);
-        String principalName = 
MetastoreDirectSqlUtils.extractSqlString(privLine[principalNameIndex]);
+        String privAuthorizer = extractSqlString(privLine[authorizerIndex]);
+        String principalName = extractSqlString(privLine[principalNameIndex]);
         PrincipalType ptype = PrincipalType.valueOf(
-                
MetastoreDirectSqlUtils.extractSqlString(privLine[principalTypeIndex]));
-        String columnName = 
MetastoreDirectSqlUtils.extractSqlString(privLine[columnNameIndex]);
-        String privilege = 
MetastoreDirectSqlUtils.extractSqlString(privLine[privilegeIndex]);
+                extractSqlString(privLine[principalTypeIndex]));
+        String columnName = extractSqlString(privLine[columnNameIndex]);
+        String privilege = extractSqlString(privLine[privilegeIndex]);
         int createTime = 
MetastoreDirectSqlUtils.extractSqlInt(privLine[createTimeIndex]);
-        String grantor = 
MetastoreDirectSqlUtils.extractSqlString(privLine[grantorIndex]);
+        String grantor = extractSqlString(privLine[grantorIndex]);
         PrincipalType grantorType =
                 PrincipalType.valueOf(
-                        
MetastoreDirectSqlUtils.extractSqlString(privLine[grantorTypeIndex]));
+                        extractSqlString(privLine[grantorTypeIndex]));
         boolean grantOption = 
MetastoreDirectSqlUtils.extractSqlBoolean(privLine[grantOptionIndex]);
 
         HiveObjectRef objectRef = new HiveObjectRef(HiveObjectType.COLUMN, 
dbName, tableName, null,
@@ -1927,17 +2055,17 @@ public List<SQLForeignKey> getForeignKeys(String 
catName, String parent_db_name,
           boolean validate = (enableValidateRely & 2) != 0;
           boolean rely = (enableValidateRely & 1) != 0;
           SQLForeignKey currKey = new SQLForeignKey(
-              MetastoreDirectSqlUtils.extractSqlString(line[0]),
-              MetastoreDirectSqlUtils.extractSqlString(line[1]),
-              MetastoreDirectSqlUtils.extractSqlString(line[2]),
-              MetastoreDirectSqlUtils.extractSqlString(line[3]),
-              MetastoreDirectSqlUtils.extractSqlString(line[4]),
-              MetastoreDirectSqlUtils.extractSqlString(line[5]),
+              extractSqlString(line[0]),
+              extractSqlString(line[1]),
+              extractSqlString(line[2]),
+              extractSqlString(line[3]),
+              extractSqlString(line[4]),
+              extractSqlString(line[5]),
               MetastoreDirectSqlUtils.extractSqlInt(line[6]),
               MetastoreDirectSqlUtils.extractSqlInt(line[7]),
               MetastoreDirectSqlUtils.extractSqlInt(line[8]),
-              MetastoreDirectSqlUtils.extractSqlString(line[9]),
-              MetastoreDirectSqlUtils.extractSqlString(line[10]),
+              extractSqlString(line[9]),
+              extractSqlString(line[10]),
               enable,
               validate,
               rely
@@ -1995,14 +2123,14 @@ public List<SQLPrimaryKey> getPrimaryKeys(String 
catName, String db_name, String
           boolean validate = (enableValidateRely & 2) != 0;
           boolean rely = (enableValidateRely & 1) != 0;
           SQLPrimaryKey currKey = new SQLPrimaryKey(
-              MetastoreDirectSqlUtils.extractSqlString(line[0]),
-              MetastoreDirectSqlUtils.extractSqlString(line[1]),
-              MetastoreDirectSqlUtils.extractSqlString(line[2]),
-              MetastoreDirectSqlUtils.extractSqlInt(line[3]), 
MetastoreDirectSqlUtils.extractSqlString(line[4]),
+              extractSqlString(line[0]),
+              extractSqlString(line[1]),
+              extractSqlString(line[2]),
+              MetastoreDirectSqlUtils.extractSqlInt(line[3]), 
extractSqlString(line[4]),
               enable,
               validate,
               rely);
-          
currKey.setCatName(MetastoreDirectSqlUtils.extractSqlString(line[6]));
+          currKey.setCatName(extractSqlString(line[6]));
           ret.add(currKey);
         }
       }
@@ -2055,10 +2183,10 @@ public List<SQLUniqueConstraint> 
getUniqueConstraints(String catName, String db_
           boolean rely = (enableValidateRely & 1) != 0;
           ret.add(new SQLUniqueConstraint(
               catName,
-              MetastoreDirectSqlUtils.extractSqlString(line[0]),
-              MetastoreDirectSqlUtils.extractSqlString(line[1]),
-              MetastoreDirectSqlUtils.extractSqlString(line[2]),
-              MetastoreDirectSqlUtils.extractSqlInt(line[3]), 
MetastoreDirectSqlUtils.extractSqlString(line[4]),
+              extractSqlString(line[0]),
+              extractSqlString(line[1]),
+              extractSqlString(line[2]),
+              MetastoreDirectSqlUtils.extractSqlInt(line[3]), 
extractSqlString(line[4]),
               enable,
               validate,
               rely));
@@ -2113,10 +2241,10 @@ public List<SQLNotNullConstraint> 
getNotNullConstraints(String catName, String d
           boolean rely = (enableValidateRely & 1) != 0;
           ret.add(new SQLNotNullConstraint(
               catName,
-              MetastoreDirectSqlUtils.extractSqlString(line[0]),
-              MetastoreDirectSqlUtils.extractSqlString(line[1]),
-              MetastoreDirectSqlUtils.extractSqlString(line[2]),
-              MetastoreDirectSqlUtils.extractSqlString(line[3]),
+              extractSqlString(line[0]),
+              extractSqlString(line[1]),
+              extractSqlString(line[2]),
+              extractSqlString(line[3]),
               enable,
               validate,
               rely));
@@ -2175,11 +2303,11 @@ public List<SQLDefaultConstraint> 
getDefaultConstraints(String catName, String d
           boolean rely = (enableValidateRely & 1) != 0;
           SQLDefaultConstraint currConstraint = new SQLDefaultConstraint(
               catName,
-              MetastoreDirectSqlUtils.extractSqlString(line[0]),
-              MetastoreDirectSqlUtils.extractSqlString(line[1]),
-              MetastoreDirectSqlUtils.extractSqlString(line[2]),
-              MetastoreDirectSqlUtils.extractSqlString(line[5]),
-              MetastoreDirectSqlUtils.extractSqlString(line[3]),
+              extractSqlString(line[0]),
+              extractSqlString(line[1]),
+              extractSqlString(line[2]),
+              extractSqlString(line[5]),
+              extractSqlString(line[3]),
               enable,
               validate,
               rely);
@@ -2239,11 +2367,11 @@ public List<SQLCheckConstraint> 
getCheckConstraints(String catName, String db_na
           boolean rely = (enableValidateRely & 1) != 0;
           SQLCheckConstraint currConstraint = new SQLCheckConstraint(
               catName,
-              MetastoreDirectSqlUtils.extractSqlString(line[0]),
-              MetastoreDirectSqlUtils.extractSqlString(line[1]),
-              MetastoreDirectSqlUtils.extractSqlString(line[2]),
-              MetastoreDirectSqlUtils.extractSqlString(line[5]),
-              MetastoreDirectSqlUtils.extractSqlString(line[3]),
+              extractSqlString(line[0]),
+              extractSqlString(line[1]),
+              extractSqlString(line[2]),
+              extractSqlString(line[5]),
+              extractSqlString(line[3]),
               enable,
               validate,
               rely);
@@ -2309,7 +2437,7 @@ public List<String> dropAllPartitionsAndGetLocations(Long 
tableId, String baseLo
         
partIds.add(MetastoreDirectSqlUtils.extractSqlLong(((Object[])result)[0]));
         String partitionLocation;
         if (baseLocationToNotShow != null &&
-            (partitionLocation = 
MetastoreDirectSqlUtils.extractSqlString(((Object[])result)[1])) != null &&
+            (partitionLocation = extractSqlString(((Object[])result)[1])) != 
null &&
             !FileUtils.isSubdirectory(baseLocationToNotShow, 
partitionLocation)) {
           locations.add(partitionLocation);
         }
@@ -2639,8 +2767,8 @@ public Map<String, List<String>> 
getColAndPartNamesWithStats(
       String lastPartName = null;
       List<String> cols = null;
       for (Object[] line : sqlResult) {
-        String col = MetastoreDirectSqlUtils.extractSqlString(line[1]);
-        String part = MetastoreDirectSqlUtils.extractSqlString(line[0]);
+        String col = extractSqlString(line[1]);
+        String part = extractSqlString(line[0]);
         if (!part.equals(lastPartName)) {
           if (lastPartName != null) {
             result.put(lastPartName, cols);
@@ -2675,8 +2803,7 @@ private void getStatsTableListResult(
           .ensureList(executeWithArray(query.getInnerQuery(), 
STATS_TABLE_TYPES, queryText));
       for (Object[] line : sqlResult) {
         result.add(new org.apache.hadoop.hive.common.TableName(
-            MetastoreDirectSqlUtils.extractSqlString(line[2]), 
MetastoreDirectSqlUtils
-            .extractSqlString(line[1]), 
MetastoreDirectSqlUtils.extractSqlString(line[0])));
+            extractSqlString(line[2]), extractSqlString(line[1]), 
extractSqlString(line[0])));
       }
     }
   }
@@ -2784,11 +2911,11 @@ private List<Function> 
getFunctionsFromFunctionIds(List<Long> funcIdList, String
 
       for (Object[] function : queryResult) {
         Long funcId = 
MetastoreDirectSqlUtils.extractSqlLong(function[funcIdIndex]);
-        String funcName = 
MetastoreDirectSqlUtils.extractSqlString(function[funcNameIndex]);
-        String dbName = 
MetastoreDirectSqlUtils.extractSqlString(function[dbNameIndex]);
-        String funcClassName = 
MetastoreDirectSqlUtils.extractSqlString(function[funcClassNameIndex]);
-        String funcOwnerName = 
MetastoreDirectSqlUtils.extractSqlString(function[funcOwnerNameIndex]);
-        String funcOwnerType = 
MetastoreDirectSqlUtils.extractSqlString(function[funcOwnerTypeIndex]);
+        String funcName = extractSqlString(function[funcNameIndex]);
+        String dbName = extractSqlString(function[dbNameIndex]);
+        String funcClassName = extractSqlString(function[funcClassNameIndex]);
+        String funcOwnerName = extractSqlString(function[funcOwnerNameIndex]);
+        String funcOwnerType = extractSqlString(function[funcOwnerTypeIndex]);
         int funcCreateTime = 
MetastoreDirectSqlUtils.extractSqlInt(function[funcCreateTimeIndex]);
         int funcType = 
MetastoreDirectSqlUtils.extractSqlInt(function[funcTypeIndex]);
 
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetastoreDirectSqlUtils.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetastoreDirectSqlUtils.java
index 489a61a0859..b586f75ee6c 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetastoreDirectSqlUtils.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/directsql/MetastoreDirectSqlUtils.java
@@ -582,6 +582,12 @@ public static Boolean extractSqlBoolean(Object value) 
throws MetaException {
 
  public static String extractSqlString(Object value) {
     if (value == null) return null;
+
+    // Workaround for DataNucleus's Oracle empty-string behavior (it puts 
ASCII value 1 instead of an empty string).
+    if ("\u0001".equals(value)) {
+      return "";
+    }
+
     return value.toString();
   }
 
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/metastore/impl/TableStoreImpl.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/metastore/impl/TableStoreImpl.java
index e0e183f93f2..82900c08fa6 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/metastore/impl/TableStoreImpl.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/metastore/impl/TableStoreImpl.java
@@ -136,6 +136,7 @@
 import static 
org.apache.hadoop.hive.metastore.ObjectStore.isCurrentStatsValidForTheQuery;
 import static 
org.apache.hadoop.hive.metastore.ObjectStore.makeParameterDeclarationString;
 import static 
org.apache.hadoop.hive.metastore.ObjectStore.verifyStatsChangeCtx;
+import static 
org.apache.hadoop.hive.metastore.conf.MetastoreConf.ConfVars.PARTITION_REUSE_COLUMN_DESCRIPTORS;
 import static 
org.apache.hadoop.hive.metastore.metastore.impl.PrivilegeStoreImpl.getPrincipalTypeFromStr;
 import static 
org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.getDefaultCatalog;
 import static 
org.apache.hadoop.hive.metastore.utils.MetaStoreUtils.newMetaException;
@@ -3334,19 +3335,7 @@ private MPartition convertToMPart(Partition part, MTable 
mt)
           "Partition doesn't have a valid table or database name");
     }
 
-    // If this partition's set of columns is the same as the parent table's,
-    // use the parent table's, so we do not create a duplicate column 
descriptor,
-    // thereby saving space
-    MStorageDescriptor msd;
-    if (mt.getSd() != null && mt.getSd().getCD() != null &&
-        mt.getSd().getCD().getCols() != null &&
-        part.getSd() != null &&
-        convertToFieldSchemas(mt.getSd().getCD().getCols()).
-            equals(part.getSd().getCols())) {
-      msd = convertToMStorageDescriptor(part.getSd(), mt.getSd().getCD());
-    } else {
-      msd = convertToMStorageDescriptor(part.getSd());
-    }
+    MStorageDescriptor msd = convertToMStorageDescriptor(part.getSd(), mt);
 
     return new MPartition(Warehouse.makePartName(convertToFieldSchemas(mt
         .getPartitionKeys()), part.getValues()), mt, part.getValues(), part
@@ -3354,6 +3343,71 @@ private MPartition convertToMPart(Partition part, MTable 
mt)
         msd, part.getParameters());
   }
 
+  /**
+   * Converts a storage descriptor to a db-backed storage descriptor.  Creates 
a
+   * new db-backed column descriptor object for this SD, unless a matching one 
already
+   * exists for the given table.
+   * @param sd the storage descriptor to wrap in a db-backed object
+   * @param mt the table to search for existing column descriptors, may be null
+   * @return the storage descriptor db-backed object
+   */
+  private MStorageDescriptor convertToMStorageDescriptor(StorageDescriptor sd, 
MTable mt)
+      throws MetaException {
+    if (sd == null) {
+      return null;
+    }
+
+    MColumnDescriptor mcd = (mt != null) ? getColumnDescriptor(sd.getCols(), 
mt) : null;
+    if (mcd == null) {
+      mcd = createNewMColumnDescriptor(convertToMFieldSchemas(sd.getCols()));
+    }
+    return convertToMStorageDescriptor(sd, mcd);
+  }
+
+  private MColumnDescriptor getColumnDescriptor(List<FieldSchema> cols, MTable 
mt)
+      throws MetaException {
+    if (cols == null || cols.isEmpty()) {
+      return null;
+    }
+
+    // First check to see if partition and table's column descriptor match;
+    // that's the easy and relatively fast check since it does not require
+    // a round-trip to the database.
+    List<FieldSchema> tableSchema =
+        mt.getSd() != null &&
+        mt.getSd().getCD() != null &&
+        mt.getSd().getCD().getCols() != null ?
+            convertToFieldSchemas(mt.getSd().getCD().getCols()) : null;
+
+    if (cols.equals(tableSchema)) {
+      return mt.getSd().getCD();
+    }
+
+    if (!MetastoreConf.getBoolVar(conf, PARTITION_REUSE_COLUMN_DESCRIPTORS)) {
+      return null;
+    }
+
+    long tblId = mt.getId();
+    try {
+      return new GetHelper<TableName, MColumnDescriptor>(this, null) {
+        @Override
+        protected String describeResult() {
+          return "Matching column descriptor";
+        }
+        @Override
+        protected MColumnDescriptor getSqlResult() throws MetaException {
+          return getDirectSql().getColumnDescriptor(cols, tblId);
+        }
+        @Override
+        protected MColumnDescriptor getJdoResult() {
+          return null;
+        }
+      }.run(false);
+    } catch (NoSuchObjectException e) {
+      return null;
+    }
+  }
+
   private Partition convertToPart(String catName, String dbName, String 
tblName,
       MPartition mpart, boolean isAcidTable, GetPartitionsArgs... args)
       throws MetaException {
diff --git 
a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestHMSColumnDescriptorReuse.java
 
b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestHMSColumnDescriptorReuse.java
new file mode 100644
index 00000000000..c1b310c5798
--- /dev/null
+++ 
b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/TestHMSColumnDescriptorReuse.java
@@ -0,0 +1,220 @@
+/*
+ * 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.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.annotation.MetastoreUnitTest;
+import org.apache.hadoop.hive.metastore.api.Database;
+import org.apache.hadoop.hive.metastore.api.FieldSchema;
+import org.apache.hadoop.hive.metastore.api.InvalidObjectException;
+import org.apache.hadoop.hive.metastore.api.MetaException;
+import org.apache.hadoop.hive.metastore.api.Partition;
+import org.apache.hadoop.hive.metastore.api.SerDeInfo;
+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.conf.MetastoreConf;
+import org.apache.hadoop.hive.metastore.dbinstall.rules.DatabaseRule;
+import org.apache.hadoop.hive.metastore.dbinstall.rules.Derby;
+import org.apache.hadoop.hive.metastore.utils.TestTxnDbUtil;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.hadoop.hive.metastore.Warehouse.DEFAULT_CATALOG_NAME;
+import static org.junit.Assert.assertEquals;
+
+@Category(MetastoreUnitTest.class)
+public class TestHMSColumnDescriptorReuse {
+  private ObjectStore objectStore = null;
+  private Configuration conf;
+
+  // Modify to try out with different databases.
+  // Keep it on Derby once you commit your change. It makes the test execution 
faster.
+  private final DatabaseRule db = new Derby(true);
+
+  @Before
+  public void setUp() throws Exception {
+    db.before();
+    db.install();
+
+    conf = MetastoreConf.newMetastoreConf();
+    MetastoreConf.setVar(conf, MetastoreConf.ConfVars.CONNECT_URL_KEY, 
db.getJdbcUrl());
+    MetastoreConf.setVar(conf, MetastoreConf.ConfVars.CONNECTION_DRIVER, 
db.getJdbcDriver());
+    MetastoreConf.setVar(conf, MetastoreConf.ConfVars.CONNECTION_USER_NAME, 
db.getHiveUser());
+    MetastoreConf.setVar(conf, MetastoreConf.ConfVars.PWD, 
db.getHivePassword());
+    MetastoreConf.setBoolVar(conf, MetastoreConf.ConfVars.AUTO_CREATE_ALL, 
false);
+
+    MetastoreConf.setBoolVar(conf, MetastoreConf.ConfVars.HIVE_IN_TEST, true);
+    MetastoreConf.setBoolVar(conf, 
MetastoreConf.ConfVars.PARTITION_REUSE_COLUMN_DESCRIPTORS, true);
+
+    MetaStoreTestUtils.setConfForStandloneMode(conf);
+
+    objectStore = new ObjectStore();
+    objectStore.setConf(conf);
+    HMSHandler.createDefaultCatalog(objectStore, new Warehouse(conf));
+    Database db = new DatabaseBuilder()
+            .setName("default")
+            .setDescription("description")
+            .setLocation("locationurl")
+            .build(conf);
+    objectStore.createDatabase(db);
+  }
+
+  @After
+  public void tearDown() {
+    db.after();
+  }
+
+  @Test
+  public void testReuseAfterSimpleSchemaEvolution() throws Exception {
+    FieldSchema id = new FieldSchema("id", ColumnType.STRING_TYPE_NAME, "");
+    FieldSchema fname = new FieldSchema("fname", ColumnType.STRING_TYPE_NAME, 
"");
+    FieldSchema country = new FieldSchema("country", 
ColumnType.STRING_TYPE_NAME, "");
+
+    Table tbl1 = newTable(Arrays.asList(id, fname), 
Collections.singletonList(country));
+    objectStore.createTable(tbl1);
+    objectStore.addPartition(newPart(tbl1, "US"));
+
+    FieldSchema lname = new FieldSchema("lname", ColumnType.STRING_TYPE_NAME, 
"");
+    Table tbl2 = newTable(Arrays.asList(id, fname, lname), 
Collections.singletonList(country));
+
+    objectStore.alterTable(DEFAULT_CATALOG_NAME, tbl1.getDbName(), 
tbl1.getTableName(), tbl2, null);
+    objectStore.addPartition(newPart(tbl2, "Italy"));
+
+    objectStore.addPartition(newPart(tbl2, "Hungary"));
+    objectStore.addPartition(newPart(tbl1, "Ukraine"));
+
+    assertEquals(2, countColumnDescriptors());
+  }
+
+  @Test
+  public void testAddPartitionAlterAddPartition() throws MetaException, 
InvalidObjectException {
+    FieldSchema id = new FieldSchema("id", ColumnType.STRING_TYPE_NAME, "");
+    FieldSchema fname = new FieldSchema("fname", ColumnType.STRING_TYPE_NAME, 
"");
+    FieldSchema country = new FieldSchema("country", 
ColumnType.STRING_TYPE_NAME, "");
+
+    Table tbl1 = newTable(Arrays.asList(id, fname), 
Collections.singletonList(country));
+    objectStore.createTable(tbl1);
+    objectStore.addPartition(newPart(tbl1, "US"));
+    objectStore.addPartition(newPart(tbl1, "Greece"));
+    FieldSchema lname = new FieldSchema("lname", ColumnType.STRING_TYPE_NAME, 
"");
+
+    Table tbl2 = newTable(Arrays.asList(id, fname, lname), 
Collections.singletonList(country));
+    objectStore.alterTable(DEFAULT_CATALOG_NAME, tbl1.getDbName(), 
tbl1.getTableName(), tbl2, null);
+    objectStore.addPartition(newPart(tbl2, "Italy"));
+
+    // Mimics replication scenario where we are adding partitions to the 
"same" table but with a different schema.
+    // The tbl1 is using the old storage descriptor so "Germany" and "Belgium" 
partitions will have the old schema
+    // And will lead to "duplicate" entries in "CDS" and "COLUMNS_V2" tables.
+    objectStore.addPartition(newPart(tbl1, "Germany"));
+    objectStore.addPartition(newPart(tbl1, "Belgium"));
+
+    // On the other hand the addition of a partition to the table with the new 
schema is successfully using the
+    // existing storage/column descriptors
+    objectStore.addPartition(newPart(tbl2, "England"));
+
+    assertEquals(2, countColumnDescriptors());
+  }
+
+  @Test
+  public void testNoReusableColumnDescriptors() throws MetaException, 
InvalidObjectException {
+    FieldSchema id = new FieldSchema("id", ColumnType.STRING_TYPE_NAME, "");
+    FieldSchema fname = new FieldSchema("fname", ColumnType.STRING_TYPE_NAME, 
"");
+    FieldSchema country = new FieldSchema("country", 
ColumnType.STRING_TYPE_NAME, "");
+
+    Table tbl1 = newTable(Arrays.asList(id, fname), 
Collections.singletonList(country));
+    objectStore.createTable(tbl1);
+    objectStore.addPartition(newPart(tbl1, "US"));
+
+    FieldSchema lname = new FieldSchema("lname", ColumnType.STRING_TYPE_NAME, 
"");
+    Table tbl2 = newTable(Arrays.asList(id, fname, lname), 
Collections.singletonList(country));
+
+    objectStore.alterTable(DEFAULT_CATALOG_NAME, tbl1.getDbName(), 
tbl1.getTableName(), tbl2, null);
+    objectStore.addPartition(newPart(tbl2, "Italy"));
+
+    FieldSchema address = new FieldSchema("address", 
ColumnType.STRING_TYPE_NAME, "");
+    Table tbl3 = newTable(Arrays.asList(id, fname, lname, address), 
Collections.singletonList(country));
+    objectStore.alterTable(DEFAULT_CATALOG_NAME, tbl1.getDbName(), 
tbl1.getTableName(), tbl3, null);
+    objectStore.addPartition(newPart(tbl3, "Hungary"));
+
+    assertEquals(3, countColumnDescriptors());
+  }
+
+  private int countColumnDescriptors() {
+    try(Connection c = TestTxnDbUtil.getConnection(conf)){
+      try(ResultSet rs = c.prepareStatement("SELECT COUNT(*) FROM 
\"CDS\"").executeQuery()) {
+        rs.next();
+        return rs.getInt(1);
+      }
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static Table newTable(List<FieldSchema> columns, List<FieldSchema> 
partCols) {
+    int timeSec = (int) (System.currentTimeMillis() / 1000);
+
+    StorageDescriptor sd = new StorageDescriptor(columns,
+            "/fake/location/person",
+            "org.apache.hadoop.mapred.TextInputFormat",
+            "org.apache.hadoop.mapred.MapFileOutputFormat",
+            false,
+            0,
+            new SerDeInfo("SerDeName", "serializationLib", null),
+            null,
+            null,
+            null);
+
+    Map<String, String> tableParams = new HashMap<>();
+    tableParams.put("EXTERNAL", "false");
+
+    return
+            new Table("person", "default", "owner", timeSec, timeSec, 3, sd, 
partCols,
+                    tableParams, null, null, "MANAGED_TABLE");
+  }
+
+  private static Partition newPart(Table tbl, String value) {
+    int timeSec = (int) (System.currentTimeMillis() / 1000);
+    Map<String, String> partitionParams = new HashMap<>();
+
+    partitionParams.put("PARTITION_LEVEL_PRIVILEGE", "true");
+    StorageDescriptor psd = tbl.getSd().deepCopy();
+    psd.setLocation(psd.getLocation() + "/" + value);
+    Partition p = new Partition(
+        Collections.singletonList(value),
+        tbl.getDbName(),
+        tbl.getTableName(),
+        timeSec,
+        timeSec,
+        psd,
+        partitionParams);
+    p.setCatName(DEFAULT_CATALOG_NAME);
+
+    return p;
+  }
+}


Reply via email to