[1/2] phoenix git commit: Allow ALTER TABLE SET command to update HTableDescriptor and HColumnDescriptor properties (Samarth Jain/Alicia Ying Shu)

Fri, 09 Jan 2015 16:36:06 -0800

Repository: phoenix
Updated Branches:
  refs/heads/4.0 cae025a52 -> 7578ee92b


http://git-wip-us.apache.org/repos/asf/phoenix/blob/7578ee92/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java 
b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
index 623fc0e..8a3a2b2 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
@@ -20,6 +20,7 @@ package org.apache.phoenix.schema;
 import static com.google.common.collect.Lists.newArrayListWithExpectedSize;
 import static com.google.common.collect.Sets.newLinkedHashSet;
 import static com.google.common.collect.Sets.newLinkedHashSetWithExpectedSize;
+import static org.apache.hadoop.hbase.HColumnDescriptor.TTL;
 import static 
org.apache.phoenix.exception.SQLExceptionCode.INSUFFICIENT_MULTI_TENANT_COLUMNS;
 import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.ARRAY_SIZE;
 import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.COLUMN_COUNT;
@@ -72,16 +73,21 @@ import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
 import java.sql.Types;
+import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
+import java.util.concurrent.TimeoutException;
 
 import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.HColumnDescriptor;
@@ -133,6 +139,7 @@ import org.apache.phoenix.parse.ParseNodeFactory;
 import org.apache.phoenix.parse.PrimaryKeyConstraint;
 import org.apache.phoenix.parse.TableName;
 import org.apache.phoenix.parse.UpdateStatisticsStatement;
+import org.apache.phoenix.query.ConnectionQueryServices;
 import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.query.QueryServicesOptions;
@@ -151,11 +158,14 @@ import org.apache.phoenix.util.MetaDataUtil;
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.ReadOnlyProps;
 import org.apache.phoenix.util.SchemaUtil;
+import org.apache.phoenix.util.ServerUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterators;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.primitives.Ints;
@@ -285,6 +295,9 @@ public class MetaDataClient {
         ") VALUES (?, ?, ?, ?, ?, ?)";
     
     private final PhoenixConnection connection;
+    
+    private static final HColumnDescriptor defaultColDescriptor = new 
HColumnDescriptor(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES);
+    
 
     public MetaDataClient(PhoenixConnection connection) {
         this.connection = connection;
@@ -1455,6 +1468,11 @@ public class MetaDataClient {
                     Map<String,Object> combinedFamilyProps = 
Maps.newHashMapWithExpectedSize(props.size() + commonFamilyProps.size());
                     combinedFamilyProps.putAll(commonFamilyProps);
                     for (Pair<String,Object> prop : props) {
+                        // Don't allow specifying column families for TTL. TTL 
can only apply for the all the column families of the table 
+                        // i.e. it can't be column family specific.
+                        if 
(!familyName.equals(QueryConstants.ALL_FAMILY_PROPERTIES_KEY) && 
prop.getFirst().equals(TTL)) {
+                            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.COLUMN_FAMILY_NOT_ALLOWED_FOR_TTL).build().buildException();
 
+                        }
                         combinedFamilyProps.put(prop.getFirst(), 
prop.getSecond());
                     }
                     familyPropList.add(new 
Pair<byte[],Map<String,Object>>(familyName.getBytes(),combinedFamilyProps));
@@ -1954,33 +1972,100 @@ public class MetaDataClient {
             TableName tableNameNode = statement.getTable().getName();
             String schemaName = tableNameNode.getSchemaName();
             String tableName = tableNameNode.getTableName();
-            // Outside of retry loop, as we're removing the property and 
wouldn't find it the second time
-            Boolean isImmutableRowsProp = 
(Boolean)statement.getProps().remove(PTable.IS_IMMUTABLE_ROWS_PROP_NAME);
-            Boolean multiTenantProp = 
(Boolean)statement.getProps().remove(PhoenixDatabaseMetaData.MULTI_TENANT);
-            Boolean disableWALProp = 
(Boolean)statement.getProps().remove(DISABLE_WAL);
             
+            Boolean isImmutableRowsProp = null;
+            Boolean multiTenantProp = null;
+            Boolean disableWALProp = null;
+            
+            // flatten, separate and validate properties
+            ListMultimap<String,Pair<String,Object>> stmtPropsMap = 
statement.getProps();
+            Map<String, Map<String, Object>> stmtFamiliesPropsMap = new 
HashMap<>(stmtPropsMap.size());
+            Map<String, Object> tableProps = new 
HashMap<>(stmtPropsMap.size());
+            PTable table = FromCompiler.getResolver(statement, 
connection).getTables().get(0).getTable();
+            Map<String,Object> commonFamilyProps = new HashMap<>();
+            List<ColumnDef> columnDefs = statement.getColumnDefs();
+            if (columnDefs == null) {                    
+                columnDefs = Lists.newArrayListWithExpectedSize(1);
+            }
+            for (String family : stmtPropsMap.keySet()) {
+                List<Pair<String, Object>> propsList = 
stmtPropsMap.get(family);
+                Map<String, Object> colFamilyPropsMap = new HashMap<String, 
Object>(propsList.size());
+                for (Pair<String, Object> prop : propsList) {
+                    String propName = prop.getFirst();
+                    if ((isHTableProperty(propName) ||  
TableProperty.isPhoenixTableProperty(propName)) && columnDefs.size() > 0) {
+                       // setting HTable and PhoenixTable properties while 
adding a column is not allowed.
+                       throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_SET_TABLE_PROPERTY_ADD_COLUMN)
+                       .setMessage("Property: " + propName).build()
+                       .buildException();
+                    }
+                    if (isHTableProperty(propName)) {
+                        // Can't have a column family name for a property 
that's an HTableProperty
+                        if 
(!family.equals(QueryConstants.ALL_FAMILY_PROPERTIES_KEY)) {
+                            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.COLUMN_FAMILY_NOT_ALLOWED_TABLE_PROPERTY)
+                            .setMessage("Column Family: " + family + ", 
Property: " + propName).build()
+                            .buildException(); 
+                        }
+                        tableProps.put(propName, prop.getSecond());
+                    } else {
+                        if (TableProperty.isPhoenixTableProperty(propName)) {
+                               TableProperty.valueOf(propName).validate(true, 
!family.equals(QueryConstants.ALL_FAMILY_PROPERTIES_KEY), table.getType());
+                            if (propName.equals(TTL)) {
+                                // Even though TTL is really a HColumnProperty 
we treat it specially.
+                                // We enforce that all column families have 
the same TTL.
+                               commonFamilyProps.put(propName, 
prop.getSecond());
+                            } else if 
(propName.equals(PTable.IS_IMMUTABLE_ROWS_PROP_NAME)) {
+                                isImmutableRowsProp = 
(Boolean)prop.getSecond();
+                            } else if 
(propName.equals(PhoenixDatabaseMetaData.MULTI_TENANT)) {
+                                multiTenantProp = (Boolean)prop.getSecond();
+                            } else if (propName.equals(DISABLE_WAL)) {
+                                disableWALProp = (Boolean)prop.getSecond();
+                            } 
+                        } else {
+                            if (isHColumnProperty(propName)) {
+                                if 
(family.equals(QueryConstants.ALL_FAMILY_PROPERTIES_KEY)) {
+                                    commonFamilyProps.put(propName, 
prop.getSecond());
+                                } else {
+                                    colFamilyPropsMap.put(propName, 
prop.getSecond());
+                                }
+                            } else {
+                                // invalid property - neither of HTableProp, 
HColumnProp or PhoenixTableProp
+                                // FIXME: This isn't getting triggered as 
currently a property gets evaluated 
+                                // as HTableProp if its neither HColumnProp or 
PhoenixTableProp.
+                                throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.SET_UNSUPPORTED_PROP_ON_ALTER_TABLE)
+                                .setMessage("Column Family: " + family + ", 
Property: " + propName).build()
+                                .buildException();
+                            }
+                        }
+                    }
+                }
+                if (!colFamilyPropsMap.isEmpty()) {
+                    stmtFamiliesPropsMap.put(family, colFamilyPropsMap);
+                }
+            }
+            
+                        
             boolean retried = false;
             while (true) {
-                List<Mutation> tableMetaData = 
Lists.newArrayListWithExpectedSize(2);
                 ColumnResolver resolver = FromCompiler.getResolver(statement, 
connection);
-                PTable table = resolver.getTables().get(0).getTable();
+                table = resolver.getTables().get(0).getTable();
+                List<Mutation> tableMetaData = 
Lists.newArrayListWithExpectedSize(2);
                 if (logger.isDebugEnabled()) {
                     logger.debug(LogUtil.addCustomAnnotations("Resolved table 
to " + table.getName().getString() + " with seqNum " + 
table.getSequenceNumber() + " at timestamp " + table.getTimeStamp() + " with " 
+ table.getColumns().size() + " columns: " + table.getColumns(), connection));
                 }
-                
+
                 int position = table.getColumns().size();
-                
+
                 List<PColumn> currentPKs = table.getPKColumns();
                 PColumn lastPK = currentPKs.get(currentPKs.size()-1);
                 // Disallow adding columns if the last column is VARBIANRY.
                 if (lastPK.getDataType() == PVarbinary.INSTANCE || 
lastPK.getDataType().isArrayType()) {
                     throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.VARBINARY_LAST_PK)
-                        
.setColumnName(lastPK.getName().getString()).build().buildException();
+                    
.setColumnName(lastPK.getName().getString()).build().buildException();
                 }
                 // Disallow adding columns if last column is fixed width and 
nullable.
                 if (lastPK.isNullable() && 
lastPK.getDataType().isFixedWidth()) {
                     throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.NULLABLE_FIXED_WIDTH_LAST_PK)
-                        
.setColumnName(lastPK.getName().getString()).build().buildException();
+                    
.setColumnName(lastPK.getName().getString()).build().buildException();
                 }
 
                 Boolean isImmutableRows = null;
@@ -2001,36 +2086,14 @@ public class MetaDataClient {
                         disableWAL = disableWALProp;
                     }
                 }
-                
-                if 
(statement.getProps().get(PhoenixDatabaseMetaData.SALT_BUCKETS) != null) {
-                    throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.SALT_ONLY_ON_CREATE_TABLE)
-                    
.setTableName(table.getName().getString()).build().buildException();
-                }
-                if 
(statement.getProps().get(PhoenixDatabaseMetaData.DEFAULT_COLUMN_FAMILY_NAME) 
!= null) {
-                    throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.DEFAULT_COLUMN_FAMILY_ONLY_ON_CREATE_TABLE)
-                    
.setTableName(table.getName().getString()).build().buildException();
-                }
-                
+
                 boolean isAddingPKColumn = false;
                 PreparedStatement colUpsert = 
connection.prepareStatement(INSERT_COLUMN);
-                
-                List<ColumnDef> columnDefs = statement.getColumnDefs();
-                if (columnDefs == null) {                    
-                    columnDefs = Lists.newArrayListWithExpectedSize(1);
-                }
 
-                List<Pair<byte[],Map<String,Object>>> families = 
Lists.newArrayList();                
                 List<PColumn> columns = 
Lists.newArrayListWithExpectedSize(columnDefs.size());
-
-                if ( columnDefs.size() > 0 ) {
-                    // Disallow setting these props for now on an add column, 
as that
-                    // would a bit of a strange thing to do. We can revisit 
later if
-                    // need be. We'd need to do the error checking we do in 
the else
-                    // if we want to support this.
-                    if (isImmutableRowsProp != null || multiTenantProp != 
null) {
-                        throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.SET_UNSUPPORTED_PROP_ON_ALTER_TABLE)
-                        
.setTableName(table.getName().getString()).build().buildException();
-                    }
+                Set<String> colFamiliesForPColumnsToBeAdded = new 
LinkedHashSet<>();
+                HashSet<String> existingColumnFamilies = 
existingColumnFamilies(table);
+                if (columnDefs.size() > 0 ) {
                     short nextKeySeq = SchemaUtil.getMaxKeySeq(table);
                     for( ColumnDef colDef : columnDefs) {
                         if (colDef != null && !colDef.isNull()) {
@@ -2054,10 +2117,11 @@ public class MetaDataClient {
                             pkName = table.getPKName() == null ? null : 
table.getPKName().getString();
                             keySeq = ++nextKeySeq;
                         } else {
-                            families.add(new 
Pair<byte[],Map<String,Object>>(column.getFamilyName().getBytes(),statement.getProps()));
+                            
colFamiliesForPColumnsToBeAdded.add(column.getFamilyName().getString());
                         }
                         addColumnMutation(schemaName, tableName, column, 
colUpsert, null, pkName, keySeq, table.getBucketNum() != null);
                     }
+                    
                     // Add any new PK columns to end of index PK
                     if (isAddingPKColumn) {
                         for (PTable index : table.getIndexes()) {
@@ -2078,12 +2142,6 @@ public class MetaDataClient {
                     
tableMetaData.addAll(connection.getMutationState().toMutations().next().getSecond());
                     connection.rollback();
                 } else {
-                    // Only support setting IMMUTABLE_ROWS=true and 
DISABLE_WAL=true on ALTER TABLE SET command
-                    // TODO: support setting HBase table properties too
-                    if (!statement.getProps().isEmpty()) {
-                        throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.SET_UNSUPPORTED_PROP_ON_ALTER_TABLE)
-                        
.setTableName(table.getName().getString()).build().buildException();
-                    }
                     // Check that HBase configured properly for mutable 
secondary indexing
                     // if we're changing from an immutable table to a mutable 
table and we
                     // have existing indexes.
@@ -2100,7 +2158,7 @@ public class MetaDataClient {
                         throwIfInsufficientColumns(schemaName, tableName, 
table.getPKColumns(), table.getBucketNum()!=null, multiTenant);
                     }
                 }
-                
+
                 if (isAddingPKColumn && !table.getIndexes().isEmpty()) {
                     for (PTable index : table.getIndexes()) {
                         incrementTableSeqNum(index, index.getType(), 1);
@@ -2109,29 +2167,101 @@ public class MetaDataClient {
                     connection.rollback();
                 }
                 long seqNum = incrementTableSeqNum(table, 
statement.getTableType(), 1, isImmutableRows, disableWAL, multiTenant);
-                
+
                 
tableMetaData.addAll(connection.getMutationState().toMutations().next().getSecond());
                 connection.rollback();
                 // Force the table header row to be first
                 Collections.reverse(tableMetaData);
                 
-                Pair<byte[],Map<String,Object>> family = families.size() > 0 ? 
families.get(0) : null;
-                
+                byte[] family = colFamiliesForPColumnsToBeAdded.size() > 0 ? 
colFamiliesForPColumnsToBeAdded.iterator().next().getBytes() : null;
+
                 // Figure out if the empty column family is changing as a 
result of adding the new column
                 byte[] emptyCF = null;
                 byte[] projectCF = null;
                 if (table.getType() != PTableType.VIEW && family != null) {
                     if (table.getColumnFamilies().isEmpty()) {
-                        emptyCF = family.getFirst();
+                        emptyCF = family;
                     } else {
                         try {
-                            table.getColumnFamily(family.getFirst());
+                            table.getColumnFamily(family);
                         } catch (ColumnFamilyNotFoundException e) {
-                            projectCF = family.getFirst();
+                            projectCF = family;
                             emptyCF = SchemaUtil.getEmptyColumnFamily(table);
                         }
                     }
                 }
+                
+                Map<String, Map<String, Object>> allFamiliesProps = new 
HashMap<>(existingColumnFamilies.size() + stmtFamiliesPropsMap.size());
+                commonFamilyProps = 
Collections.unmodifiableMap(commonFamilyProps);
+                if (columnDefs.size() == 0) {
+                       // Add the common family props to all existing column 
families
+                       for (String existingColFamily : existingColumnFamilies) 
{
+                               Map<String, Object> m = new HashMap<String, 
Object>(commonFamilyProps.size());
+                               m.putAll(commonFamilyProps);
+                               allFamiliesProps.put(existingColFamily, m);
+                       }
+                } else {
+                       // Add the common family props to the column families 
of the columns being added
+                       for (String colFamily : 
colFamiliesForPColumnsToBeAdded) {
+                               Map<String, Object> m = new HashMap<String, 
Object>(commonFamilyProps.size());
+                               m.putAll(commonFamilyProps);
+                               allFamiliesProps.put(colFamily, m);
+                       }
+                }
+                
+                if (table.getColumnFamilies().isEmpty() && columnDefs.size() 
== 0 && !commonFamilyProps.isEmpty()) {
+                       
allFamiliesProps.put(Bytes.toString(table.getDefaultFamilyName() == null ? 
QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES : 
table.getDefaultFamilyName().getBytes() ), commonFamilyProps);
+                }
+                
+                // Now go through the column family properties specified in 
the statement
+                // and merge them with the common family properties.
+                for (String f : stmtFamiliesPropsMap.keySet()) {
+                    if (allFamiliesProps.get(f) != null) {
+                        Map<String, Object> props = allFamiliesProps.get(f);
+                        Map<String, Object> stmtProps = 
stmtFamiliesPropsMap.get(f);
+                        props.putAll(stmtProps);
+                    } else {
+                       if (columnDefs.size() == 0) {
+                               throw new ColumnFamilyNotFoundException(f);
+                       } else {
+                               if (!existingColumnFamilies.contains(f)) {
+                                       throw new 
ColumnFamilyNotFoundException(f);
+                               } else {
+                                       // If the family already exists then an 
attempt was made to 
+                                       // add property for a column family 
that doesn't have a column
+                                       // being added in the statement. 
+                                       throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_SET_PROPERTY_FOR_COLUMN_NOT_ADDED).build()
+                                .buildException();
+                               }
+                       }
+                    }
+                }
+                
+                // Views are not allowed to have any of these properties.
+                if (table.getType() == PTableType.VIEW && 
(!stmtFamiliesPropsMap.isEmpty() || !commonFamilyProps.isEmpty() || 
!tableProps.isEmpty())) {
+                    throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.VIEW_WITH_PROPERTIES).build()
+                    .buildException();
+                }
+                
+                HTableDescriptor newTableDesc = null;
+                if (!tableProps.isEmpty()) {
+                       newTableDesc = modifyTableProps(tableName, table, 
tableProps);  
+                }
+                
+                if (columnDefs.size() > 0) {
+                       // Make sure that all the CFs of the table have the 
same TTL as the empty CF. 
+                       setTTLToEmptyCFTTL(allFamiliesProps, table, 
newTableDesc);
+                }
+                
+                List<Pair<byte[], Map<String, Object>>> families = new 
ArrayList<>(allFamiliesProps.size());
+                for (Entry<String, Map<String, Object>> entry : 
allFamiliesProps.entrySet()) {
+                    byte[] cf = entry.getKey().getBytes();
+                    Map<String, Object> props = entry.getValue();
+                    if (!props.isEmpty()) {
+                        families.add(new Pair<>(cf, props));
+                    }
+                }
+                
                 MetaDataMutationResult result = 
connection.getQueryServices().addColumn(tableMetaData, families, table);
                 try {
                     MutationCode code = processMutationResult(schemaName, 
tableName, result);
@@ -2142,6 +2272,7 @@ public class MetaDataClient {
                         }
                         return new MutationState(0,connection);
                     }
+
                     // Only update client side cache if we aren't adding a PK 
column to a table with indexes.
                     // We could update the cache manually then too, it'd just 
be a pain.
                     if (!isAddingPKColumn || table.getIndexes().isEmpty()) {
@@ -2194,8 +2325,49 @@ public class MetaDataClient {
             connection.setAutoCommit(wasAutoCommit);
         }
     }
+    
+   private void setTTLToEmptyCFTTL(Map<String, Map<String, Object>> 
familyProps, PTable table, 
+                  HTableDescriptor newTableDesc) throws SQLException {
+          if (!familyProps.isEmpty()) {
+                  int emptyCFTTL = 
getTTLForEmptyCf(SchemaUtil.getEmptyColumnFamily(table), 
table.getPhysicalName().getBytes(), newTableDesc);
+                  for (String family : familyProps.keySet()) {
+                          Map<String, Object> props = familyProps.get(family) 
!= null ? familyProps.get(family) : new HashMap<String, Object>();
+                          props.put(TTL, emptyCFTTL);
+                  }
+          }
+   }
 
-
+    private HTableDescriptor modifyTableProps(String tableName, PTable table, 
Map<String, Object> tableProps) throws SQLException {
+       byte[] tableNameBytes = Bytes.toBytes(tableName);
+       ConnectionQueryServices services = connection.getQueryServices();
+       HTableDescriptor existingTableDescriptor = 
services.getTableDescriptor(tableNameBytes);
+       HTableDescriptor newTableDescriptor = new 
HTableDescriptor(existingTableDescriptor);
+       // add all the table properties to the existing table descriptor
+       for (Entry<String, Object> entry : tableProps.entrySet()) {
+               newTableDescriptor.setValue(entry.getKey(), entry.getValue() != 
null ? entry.getValue().toString() : null);
+       }
+       SQLException sqlE = null;
+       try {
+               services.modifyTable(tableNameBytes, newTableDescriptor);
+               return newTableDescriptor;
+       } catch (IOException e) {
+               sqlE = ServerUtil.parseServerException(e);
+               return null;
+       } catch (InterruptedException e) {
+               // restore the interrupt status
+               Thread.currentThread().interrupt();
+               sqlE = new 
SQLExceptionInfo.Builder(SQLExceptionCode.INTERRUPTED_EXCEPTION).setRootCause(e).build().buildException();
+               return null;
+       } catch (TimeoutException e) {
+               sqlE = new 
SQLExceptionInfo.Builder(SQLExceptionCode.OPERATION_TIMED_OUT).setRootCause(e.getCause()
 != null ? e.getCause() : e).build().buildException();
+               return null;
+       } finally {
+               if (sqlE != null) {
+                       throw sqlE;
+               }
+       } 
+    }
+    
     private String dropColumnMutations(PTable table, List<PColumn> 
columnsToDrop, List<Mutation> tableMetaData) throws SQLException {
         String tenantId = connection.getTenantId() == null ? "" : 
connection.getTenantId().getString();
         String schemaName = table.getSchemaName().getString();
@@ -2557,4 +2729,35 @@ public class MetaDataClient {
         }
         return table.getTableStats();
     }
+    
+    private boolean isHColumnProperty(String propName) {
+       return defaultColDescriptor.getValue(propName) != null;
+    }
+
+    private boolean isHTableProperty(String propName) {
+       return !isHColumnProperty(propName) && 
!TableProperty.isPhoenixTableProperty(propName);
+    } 
+
+    private HashSet<String> existingColumnFamilies(PTable table) {
+       List<PColumnFamily> cfs = table.getColumnFamilies();
+       HashSet<String> cfNames = new HashSet<>(cfs.size());
+       for (PColumnFamily cf : table.getColumnFamilies()) {
+               cfNames.add(cf.getName().getString());
+       }
+       return cfNames;
+    }
+
+    private int getTTLForEmptyCf(byte[] emptyCf, byte[] tableNameBytes, 
HTableDescriptor tableDescriptor) throws SQLException {
+       if (tableDescriptor == null) {
+               tableDescriptor = 
connection.getQueryServices().getTableDescriptor(tableNameBytes);
+       }
+       HColumnDescriptor[] colDescriptors = 
tableDescriptor.getColumnFamilies();
+       for (HColumnDescriptor colDesc : colDescriptors) {
+               if (Bytes.equals(emptyCf, colDesc.getName())) {
+                       return colDesc.getTimeToLive();
+               }
+       }
+       throw new IllegalArgumentException("No match for empty column family 
found");
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/7578ee92/phoenix-core/src/main/java/org/apache/phoenix/schema/TableProperty.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/schema/TableProperty.java 
b/phoenix-core/src/main/java/org/apache/phoenix/schema/TableProperty.java
new file mode 100644
index 0000000..605851e
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/TableProperty.java
@@ -0,0 +1,111 @@
+/*
+ * 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.schema;
+
+import static 
org.apache.phoenix.exception.SQLExceptionCode.CANNOT_ALTER_PROPERTY;
+import static 
org.apache.phoenix.exception.SQLExceptionCode.COLUMN_FAMILY_NOT_ALLOWED_FOR_TTL;
+import static 
org.apache.phoenix.exception.SQLExceptionCode.COLUMN_FAMILY_NOT_ALLOWED_TABLE_PROPERTY;
+import static 
org.apache.phoenix.exception.SQLExceptionCode.DEFAULT_COLUMN_FAMILY_ONLY_ON_CREATE_TABLE;
+import static 
org.apache.phoenix.exception.SQLExceptionCode.SALT_ONLY_ON_CREATE_TABLE;
+import static 
org.apache.phoenix.exception.SQLExceptionCode.VIEW_WITH_PROPERTIES;
+import static 
org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.DEFAULT_COLUMN_FAMILY_NAME;
+
+import java.sql.SQLException;
+
+import org.apache.hadoop.hbase.HColumnDescriptor;
+import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.exception.SQLExceptionInfo;
+import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+
+public enum TableProperty {
+       
+       IMMUTABLE_ROWS(PhoenixDatabaseMetaData.IMMUTABLE_ROWS, true, true),
+
+       MULTI_TENANT(PhoenixDatabaseMetaData.MULTI_TENANT, true, false),
+
+       DISABLE_WAL(PhoenixDatabaseMetaData.DISABLE_WAL, true, false),
+
+       SALT_BUCKETS(PhoenixDatabaseMetaData.SALT_BUCKETS, 
COLUMN_FAMILY_NOT_ALLOWED_TABLE_PROPERTY, false, SALT_ONLY_ON_CREATE_TABLE, 
false),
+
+       DEFAULT_COLUMN_FAMILY(DEFAULT_COLUMN_FAMILY_NAME, 
COLUMN_FAMILY_NOT_ALLOWED_TABLE_PROPERTY, false, 
DEFAULT_COLUMN_FAMILY_ONLY_ON_CREATE_TABLE, false),
+
+       TTL(HColumnDescriptor.TTL, COLUMN_FAMILY_NOT_ALLOWED_FOR_TTL, true, 
CANNOT_ALTER_PROPERTY, false);
+
+       private final String propertyName;
+       private final SQLExceptionCode colFamSpecifiedException;
+       private final boolean isMutable; // whether or not a property can be 
changed through statements like ALTER TABLE.
+       private final SQLExceptionCode mutatingImmutablePropException;
+       private final boolean isValidOnView;
+
+       private TableProperty(String propertyName, boolean isMutable, boolean 
isValidOnView) {
+               this(propertyName, COLUMN_FAMILY_NOT_ALLOWED_TABLE_PROPERTY, 
isMutable, CANNOT_ALTER_PROPERTY, isValidOnView);
+       }
+
+       private TableProperty(String propertyName, SQLExceptionCode 
colFamilySpecifiedException, boolean isMutable, boolean isValidOnView) {
+               this(propertyName, colFamilySpecifiedException, isMutable, 
CANNOT_ALTER_PROPERTY, isValidOnView);
+       }
+
+       private TableProperty(String propertyName, boolean isMutable, boolean 
isValidOnView, SQLExceptionCode isMutatingException) {
+               this(propertyName, COLUMN_FAMILY_NOT_ALLOWED_TABLE_PROPERTY, 
isMutable, isMutatingException, isValidOnView);
+       }
+
+       private TableProperty(String propertyName, SQLExceptionCode 
colFamSpecifiedException, boolean isMutable, SQLExceptionCode 
mutatingException, boolean isValidOnView) {
+               this.propertyName = propertyName;
+               this.colFamSpecifiedException = colFamSpecifiedException;
+               this.isMutable = isMutable;
+               this.mutatingImmutablePropException = mutatingException;
+               this.isValidOnView = isValidOnView;
+       }
+
+       public static boolean isPhoenixTableProperty(String property) {
+               try {
+                       TableProperty.valueOf(property);         
+               } catch (IllegalArgumentException e) {
+                       return false;
+               }
+               return true;
+       }
+
+       // isQualified is true if column family name is specified in property 
name
+       public void validate(boolean isMutating, boolean isQualified, 
PTableType tableType) throws SQLException {
+               checkForColumnFamily(isQualified);
+               checkForMutability(isMutating);
+               checkIfApplicableForView(tableType);
+       }
+
+       private void checkForColumnFamily(boolean isQualified) throws 
SQLException {
+               if (isQualified) {
+                       throw new 
SQLExceptionInfo.Builder(colFamSpecifiedException).setMessage(". Property: " + 
propertyName).build().buildException();
+               }
+       }
+
+       private void checkForMutability(boolean isMutating) throws SQLException 
{
+               if (isMutating && !isMutable) {
+                       throw new 
SQLExceptionInfo.Builder(mutatingImmutablePropException).setMessage(". 
Property: " + propertyName).build().buildException();
+               }
+       }
+
+       private void checkIfApplicableForView(PTableType tableType)
+                       throws SQLException {
+               if (tableType == PTableType.VIEW && !isValidOnView) { 
+                       throw new SQLExceptionInfo.Builder(
+                                       
VIEW_WITH_PROPERTIES).setMessage("Property: " + 
propertyName).build().buildException(); 
+               }
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/7578ee92/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
index d0baaee..3b93954 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
@@ -945,15 +945,21 @@ public class QueryCompilerTest extends 
BaseConnectionlessQueryTest {
     @Test
     public void testSetSaltBucketOnAlterTable() throws Exception {
         long ts = nextTimestamp();
-        String query = "ALTER TABLE atable ADD xyz INTEGER SALT_BUCKETS=4";
         String url = getUrl() + ";" + PhoenixRuntime.CURRENT_SCN_ATTRIB + "=" 
+ (ts + 5); // Run query at timestamp 5
         Connection conn = DriverManager.getConnection(url);
         try {
-            PreparedStatement statement = conn.prepareStatement(query);
+            PreparedStatement statement = conn.prepareStatement("ALTER TABLE 
atable ADD xyz INTEGER SALT_BUCKETS=4");
+            statement.execute();
+            fail();
+        } catch (SQLException e) { // expected
+            
assertEquals(SQLExceptionCode.CANNOT_SET_TABLE_PROPERTY_ADD_COLUMN.getErrorCode(),
 e.getErrorCode());
+        }
+        try {
+            PreparedStatement statement = conn.prepareStatement("ALTER TABLE 
atable SET SALT_BUCKETS=4");
             statement.execute();
             fail();
         } catch (SQLException e) { // expected
-            assertTrue(e.getErrorCode() == 
SQLExceptionCode.SALT_ONLY_ON_CREATE_TABLE.getErrorCode());
+            
assertEquals(SQLExceptionCode.SALT_ONLY_ON_CREATE_TABLE.getErrorCode(), 
e.getErrorCode());
         }
     }
 

Reply via email to