From 96a48a0a3a2927501d437dc77dcfa8012442dc8a Mon Sep 17 00:00:00 2001 From: Ujjawal Kumar <> Date: Wed, 2 Sep 2026 10:25:30 +0530 Subject: [PATCH] PHOENIX-6868 - Inheritable table level properties from data table to indexes --- .../query/ConnectionQueryServicesImpl.java | 40 ++- .../apache/phoenix/query/QueryServices.java | 5 + .../apache/phoenix/schema/MetaDataClient.java | 8 + .../org/apache/phoenix/util/MetaDataUtil.java | 32 ++ .../end2end/InheritableTablePropertiesIT.java | 337 ++++++++++++++++++ 5 files changed, 415 insertions(+), 7 deletions(-) create mode 100644 phoenix-core/src/it/java/org/apache/phoenix/end2end/InheritableTablePropertiesIT.java diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java index f14ae7aa711..b067d466a27 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java @@ -1349,6 +1349,14 @@ private TableDescriptorBuilder generateTableDescriptor(byte[] physicalTableName, if (baseTableMaxLookbackVal != null) { tableProps.put(PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY, baseTableMaxLookbackVal); } + // PHOENIX-6868: Inherit configured custom table descriptor properties from base table + for (String inheritableProp : + MetaDataUtil.getInheritableTableDescriptorProperties(this.config)) { + String val = baseTableDesc.getValue(inheritableProp); + if (val != null) { + tableProps.put(inheritableProp, val); + } + } dataTableColDescForIndexTablePropSyncing = baseTableDesc.getColumnFamily(defaultFamilyBytes); // It's possible that the table has specific column families and none of them are declared // to be the DEFAULT_COLUMN_FAMILY, so we choose the first column family for syncing @@ -3358,6 +3366,13 @@ private Map separateAndValidateProperties(PTab } newMaxLookback = (Integer) propValue; } + // PHOENIX-6868: Disallow setting inheritable properties directly on index + if (table.getType() == PTableType.INDEX + && MetaDataUtil.isInheritableTableDescriptorProperty(this.config, propName)) { + throw new SQLExceptionInfo.Builder( + SQLExceptionCode.CANNOT_SET_OR_ALTER_PROPERTY_FOR_INDEX) + .setMessage("Property: " + propName).build().buildException(); + } tableProps.put(propName, propValue); } else { if (TableProperty.isPhoenixTableProperty(propName)) { @@ -3691,8 +3706,10 @@ private Map separateAndValidateProperties(PTab // Copy properties that need to be synced from the default column family of the base table to // the column families of each of its indexes (including indexes on this base table's views) // and store those table descriptor mappings as well + Map syncedTableDescProps = getNewSyncedPropsMapForTableDescriptor( + newMaxLookback, tableProps); setSyncedPropertiesForTableIndexes(table, tableAndIndexDescriptorMappings, - applyPropsToAllIndexColFams, getNewSyncedPropsMapForTableDescriptor(newMaxLookback)); + applyPropsToAllIndexColFams, syncedTableDescProps); return tableAndIndexDescriptorMappings; } @@ -3797,13 +3814,21 @@ private Map getNewSyncedPropsMap(Integer newTTL, return newSyncedProps; } - private Map getNewSyncedPropsMapForTableDescriptor(Integer newMaxLookback) { - if (newMaxLookback == null) { - return null; - } - Map newSyncedProps = new HashMap<>(1); + private Map getNewSyncedPropsMapForTableDescriptor(Integer newMaxLookback, + Map tableProps) { + Map newSyncedProps = new HashMap<>(); setPropIfNotNull(newSyncedProps, PHOENIX_MAX_LOOKBACK_AGE_CONF_KEY, newMaxLookback); - return newSyncedProps; + // PHOENIX-6868: Include any inheritable table descriptor properties that are being modified + List inheritableProps = + MetaDataUtil.getInheritableTableDescriptorProperties(this.config); + if (tableProps != null) { + for (String prop : inheritableProps) { + if (tableProps.containsKey(prop)) { + newSyncedProps.put(prop, tableProps.get(prop)); + } + } + } + return newSyncedProps.isEmpty() ? null : newSyncedProps; } /** @@ -3877,6 +3902,7 @@ private void setSyncedPropertiesForTableIndexes(PTable table, this.getTableDescriptor(Bytes.toBytes(viewIndexName)); TableDescriptorBuilder newViewIndexDescriptorBuilder = TableDescriptorBuilder.newBuilder(origViewIndexTableDescriptor); + modifyTableDescriptor(newViewIndexDescriptorBuilder, applyPropsToAllIndexesTd); for (ColumnFamilyDescriptor cfd : origViewIndexTableDescriptor.getColumnFamilies()) { ColumnFamilyDescriptorBuilder newCfd = ColumnFamilyDescriptorBuilder.newBuilder(cfd); modifyColumnFamilyDescriptor(newCfd, applyPropsToAllIndexesDefaultCF); diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServices.java b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServices.java index 085ac34a64b..1640d7906db 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServices.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServices.java @@ -218,6 +218,11 @@ public interface QueryServices extends SQLCloseable { public static final String ALLOW_LOCAL_INDEX_ATTRIB = "phoenix.index.allowLocalIndex"; + // See PHOENIX-6868: Comma-separated list of custom HBase table descriptor property keys that + // should be automatically inherited by index tables from their data table. + public static final String INDEX_INHERITABLE_TABLE_DESCRIPTOR_PROPERTIES = + "phoenix.index.inheritableTableDescriptorProperties"; + // Timeout config for PhoenixSyncTableTool String SYNC_TABLE_QUERY_TIMEOUT_ATTRIB = "phoenix.sync.table.query.timeout"; String SYNC_TABLE_RPC_TIMEOUT_ATTRIB = "phoenix.sync.table.rpc.timeout"; diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java index 22833e24945..8af7c11a24f 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/MetaDataClient.java @@ -1212,6 +1212,14 @@ private void populatePropertyMaps(ListMultimap> sta SQLExceptionCode.CANNOT_SET_OR_ALTER_UPDATE_CACHE_FREQ_FOR_INDEX).build() .buildException(); } + // PHOENIX-6868: Disallow setting inheritable table descriptor properties on indexes + if (tableType == PTableType.INDEX && !isCDCIndex + && MetaDataUtil.isInheritableTableDescriptorProperty( + connection.getQueryServices().getConfiguration(), prop.getFirst())) { + throw new SQLExceptionInfo.Builder( + SQLExceptionCode.CANNOT_SET_OR_ALTER_PROPERTY_FOR_INDEX) + .setMessage("Property: " + prop.getFirst()).build().buildException(); + } tableProps.put(prop.getFirst(), prop.getSecond()); } else { // HColumnDescriptor property commonFamilyProps.put(prop.getFirst(), prop.getSecond()); diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/util/MetaDataUtil.java b/phoenix-core-client/src/main/java/org/apache/phoenix/util/MetaDataUtil.java index 9249a8ad68f..00acdedc579 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/MetaDataUtil.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/MetaDataUtil.java @@ -59,6 +59,7 @@ import org.apache.phoenix.jdbc.PhoenixConnection; import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData; import org.apache.phoenix.query.QueryConstants; +import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.schema.ColumnFamilyNotFoundException; import org.apache.phoenix.schema.ColumnNotFoundException; import org.apache.phoenix.schema.ConditionalTTLExpression; @@ -1021,6 +1022,37 @@ public static boolean propertyNotAllowedToBeOutOfSync(String colFamProp) { return SYNCED_DATA_TABLE_AND_INDEX_COL_FAM_PROPERTIES.contains(colFamProp); } + /** + * Get the list of inheritable table descriptor properties from configuration. + * See PHOENIX-6868. + * @param conf the configuration + * @return list of property names that should be inherited by indexes, or empty list if none + */ + public static List getInheritableTableDescriptorProperties(Configuration conf) { + String propValue = + conf.get(QueryServices.INDEX_INHERITABLE_TABLE_DESCRIPTOR_PROPERTIES); + if (propValue == null || propValue.trim().isEmpty()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (String prop : propValue.split(",")) { + String trimmed = prop.trim(); + if (!trimmed.isEmpty()) { + result.add(trimmed); + } + } + return result; + } + + /** + * Check if a table descriptor property is configured as inheritable from data tables to indexes. + * See PHOENIX-6868. + */ + public static boolean isInheritableTableDescriptorProperty(Configuration conf, + String propName) { + return getInheritableTableDescriptorProperties(conf).contains(propName); + } + public static Map getSyncedProps(ColumnFamilyDescriptor defaultCFDesc) { Map syncedProps = new HashMap<>(); if (defaultCFDesc != null) { diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/InheritableTablePropertiesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/InheritableTablePropertiesIT.java new file mode 100644 index 00000000000..96779c5a363 --- /dev/null +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/InheritableTablePropertiesIT.java @@ -0,0 +1,337 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.phoenix.end2end; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Map; +import java.util.Properties; + +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.TableDescriptor; +import org.apache.phoenix.exception.SQLExceptionCode; +import org.apache.phoenix.jdbc.PhoenixConnection; +import org.apache.phoenix.query.QueryServices; +import org.apache.phoenix.util.MetaDataUtil; +import org.apache.phoenix.util.ReadOnlyProps; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.phoenix.thirdparty.com.google.common.collect.Maps; + +/** + * Integration tests for PHOENIX-6868: Inheritable table-level (TableDescriptor) properties from + * data tables to their indexes. + */ +@Category(NeedsOwnMiniClusterTest.class) +public class InheritableTablePropertiesIT extends ParallelStatsDisabledIT { + + private static final String CUSTOM_PROP_1 = "custom.prop.one"; + private static final String CUSTOM_PROP_2 = "custom.prop.two"; + private static final String INITIAL_PROP_1_VALUE = "value1"; + private static final String INITIAL_PROP_2_VALUE = "value2"; + private static final String MODIFIED_PROP_1_VALUE = "modified_value1"; + private static final String MODIFIED_PROP_2_VALUE = "modified_value2"; + + @BeforeClass + public static synchronized void doSetup() throws Exception { + Map props = Maps.newHashMapWithExpectedSize(2); + props.put(QueryServices.USE_STATS_FOR_PARALLELIZATION, Boolean.toString(false)); + props.put(QueryServices.INDEX_INHERITABLE_TABLE_DESCRIPTOR_PROPERTIES, + CUSTOM_PROP_1 + "," + CUSTOM_PROP_2); + setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator())); + } + + // Test that custom table descriptor properties on data table are inherited by a global index + @Test + public void testGlobalIndexInheritsTableDescriptorProps() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String indexName = generateUniqueName(); + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)" + + " \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'," + + " \"" + CUSTOM_PROP_2 + "\"='" + INITIAL_PROP_2_VALUE + "'"); + conn.createStatement() + .execute("CREATE INDEX " + indexName + " ON " + tableName + "(name)"); + + verifyTableDescriptorProperty(conn, indexName, CUSTOM_PROP_1, INITIAL_PROP_1_VALUE); + verifyTableDescriptorProperty(conn, indexName, CUSTOM_PROP_2, INITIAL_PROP_2_VALUE); + } + } + + // Test that custom table descriptor properties on data table are inherited by a local index + // (local indexes live on the same physical table, so the properties are implicitly shared) + @Test + public void testLocalIndexInheritsTableDescriptorProps() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String localIndexName = generateUniqueName(); + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)" + + " \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'," + + " \"" + CUSTOM_PROP_2 + "\"='" + INITIAL_PROP_2_VALUE + "'"); + conn.createStatement() + .execute("CREATE LOCAL INDEX " + localIndexName + " ON " + tableName + "(name)"); + + // Local index is on the same physical table, so properties are inherently shared + verifyTableDescriptorProperty(conn, tableName, CUSTOM_PROP_1, INITIAL_PROP_1_VALUE); + verifyTableDescriptorProperty(conn, tableName, CUSTOM_PROP_2, INITIAL_PROP_2_VALUE); + } + } + + // Test that custom table descriptor properties on data table are inherited by a view index + @Test + public void testViewIndexInheritsTableDescriptorProps() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String viewName = generateUniqueName(); + String viewIndexName = generateUniqueName(); + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)" + + " \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'," + + " \"" + CUSTOM_PROP_2 + "\"='" + INITIAL_PROP_2_VALUE + "'"); + conn.createStatement().execute("CREATE VIEW " + viewName + + " AS SELECT * FROM " + tableName + " WHERE id > 1"); + conn.createStatement() + .execute("CREATE INDEX " + viewIndexName + " ON " + viewName + "(name)"); + + String physicalViewIndexName = + MetaDataUtil.getViewIndexPhysicalName(tableName); + verifyTableDescriptorProperty(conn, physicalViewIndexName, CUSTOM_PROP_1, + INITIAL_PROP_1_VALUE); + verifyTableDescriptorProperty(conn, physicalViewIndexName, CUSTOM_PROP_2, + INITIAL_PROP_2_VALUE); + } + } + + // Test that altering custom table descriptor properties on data table propagates to global index + @Test + public void testAlterTablePropagatesInheritablePropsToGlobalIndex() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String indexName = generateUniqueName(); + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)" + + " \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'," + + " \"" + CUSTOM_PROP_2 + "\"='" + INITIAL_PROP_2_VALUE + "'"); + conn.createStatement() + .execute("CREATE INDEX " + indexName + " ON " + tableName + "(name)"); + + // Alter the base table's custom property + conn.createStatement().execute("ALTER TABLE " + tableName + + " SET \"" + CUSTOM_PROP_1 + "\"='" + MODIFIED_PROP_1_VALUE + "'"); + + verifyTableDescriptorProperty(conn, tableName, CUSTOM_PROP_1, MODIFIED_PROP_1_VALUE); + verifyTableDescriptorProperty(conn, indexName, CUSTOM_PROP_1, MODIFIED_PROP_1_VALUE); + // CUSTOM_PROP_2 should remain unchanged + verifyTableDescriptorProperty(conn, indexName, CUSTOM_PROP_2, INITIAL_PROP_2_VALUE); + } + } + + // Test that altering custom table descriptor properties on data table propagates to view index + @Test + public void testAlterTablePropagatesInheritablePropsToViewIndex() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String viewName = generateUniqueName(); + String viewIndexName = generateUniqueName(); + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)" + + " \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'," + + " \"" + CUSTOM_PROP_2 + "\"='" + INITIAL_PROP_2_VALUE + "'"); + conn.createStatement().execute("CREATE VIEW " + viewName + + " AS SELECT * FROM " + tableName + " WHERE id > 1"); + conn.createStatement() + .execute("CREATE INDEX " + viewIndexName + " ON " + viewName + "(name)"); + + conn.createStatement().execute("ALTER TABLE " + tableName + + " SET \"" + CUSTOM_PROP_1 + "\"='" + MODIFIED_PROP_1_VALUE + "'," + + " \"" + CUSTOM_PROP_2 + "\"='" + MODIFIED_PROP_2_VALUE + "'"); + + String physicalViewIndexName = + MetaDataUtil.getViewIndexPhysicalName(tableName); + verifyTableDescriptorProperty(conn, physicalViewIndexName, CUSTOM_PROP_1, + MODIFIED_PROP_1_VALUE); + verifyTableDescriptorProperty(conn, physicalViewIndexName, CUSTOM_PROP_2, + MODIFIED_PROP_2_VALUE); + } + } + + // Test that altering custom table descriptor properties on data table propagates to multiple + // indexes simultaneously + @Test + public void testAlterTablePropagatesInheritablePropsToMultipleIndexes() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String globalIndex1 = generateUniqueName(); + String globalIndex2 = generateUniqueName(); + String viewName = generateUniqueName(); + String viewIndexName = generateUniqueName(); + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)" + + " \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'"); + conn.createStatement() + .execute("CREATE INDEX " + globalIndex1 + " ON " + tableName + "(name)"); + conn.createStatement() + .execute("CREATE INDEX " + globalIndex2 + " ON " + tableName + "(flag)"); + conn.createStatement().execute("CREATE VIEW " + viewName + + " AS SELECT * FROM " + tableName + " WHERE id > 1"); + conn.createStatement() + .execute("CREATE INDEX " + viewIndexName + " ON " + viewName + "(name)"); + + conn.createStatement().execute("ALTER TABLE " + tableName + + " SET \"" + CUSTOM_PROP_1 + "\"='" + MODIFIED_PROP_1_VALUE + "'"); + + verifyTableDescriptorProperty(conn, tableName, CUSTOM_PROP_1, MODIFIED_PROP_1_VALUE); + verifyTableDescriptorProperty(conn, globalIndex1, CUSTOM_PROP_1, MODIFIED_PROP_1_VALUE); + verifyTableDescriptorProperty(conn, globalIndex2, CUSTOM_PROP_1, MODIFIED_PROP_1_VALUE); + String physicalViewIndexName = + MetaDataUtil.getViewIndexPhysicalName(tableName); + verifyTableDescriptorProperty(conn, physicalViewIndexName, CUSTOM_PROP_1, + MODIFIED_PROP_1_VALUE); + } + } + + // Test that setting an inheritable property directly on a global index is disallowed + @Test + public void testDisallowSettingInheritablePropOnGlobalIndex() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String indexName = generateUniqueName(); + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)"); + conn.createStatement() + .execute("CREATE INDEX " + indexName + " ON " + tableName + "(name)"); + + try { + conn.createStatement().execute("ALTER TABLE " + indexName + + " SET \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'"); + fail("Should fail when setting an inheritable property directly on an index table"); + } catch (SQLException e) { + assertEquals( + SQLExceptionCode.CANNOT_SET_OR_ALTER_PROPERTY_FOR_INDEX.getErrorCode(), + e.getErrorCode()); + } + } + } + + // Test that specifying an inheritable property during CREATE INDEX is disallowed + @Test + public void testDisallowInheritablePropDuringCreateIndex() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String indexName = generateUniqueName(); + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)"); + + try { + conn.createStatement().execute("CREATE INDEX " + indexName + " ON " + tableName + + "(name) \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'"); + fail("Should fail when specifying an inheritable property during CREATE INDEX"); + } catch (SQLException e) { + assertEquals( + SQLExceptionCode.CANNOT_SET_OR_ALTER_PROPERTY_FOR_INDEX.getErrorCode(), + e.getErrorCode()); + } + } + } + + // Test that non-inheritable custom properties are not propagated to indexes + @Test + public void testNonInheritablePropsNotPropagated() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String indexName = generateUniqueName(); + String nonInheritableProp = "non.inheritable.prop"; + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)" + + " \"" + nonInheritableProp + "\"='somevalue'"); + conn.createStatement() + .execute("CREATE INDEX " + indexName + " ON " + tableName + "(name)"); + + // The non-inheritable property should NOT be on the index table + verifyTableDescriptorProperty(conn, indexName, nonInheritableProp, null); + } + } + + // Test that indexes created after ALTER TABLE get the latest property values + @Test + public void testNewIndexGetsLatestInheritableProps() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String indexBefore = generateUniqueName(); + String indexAfter = generateUniqueName(); + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)" + + " \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'"); + conn.createStatement() + .execute("CREATE INDEX " + indexBefore + " ON " + tableName + "(name)"); + + // Alter the base table + conn.createStatement().execute("ALTER TABLE " + tableName + + " SET \"" + CUSTOM_PROP_1 + "\"='" + MODIFIED_PROP_1_VALUE + "'"); + + // Create a new index after alteration + conn.createStatement() + .execute("CREATE INDEX " + indexAfter + " ON " + tableName + "(flag)"); + + verifyTableDescriptorProperty(conn, indexBefore, CUSTOM_PROP_1, MODIFIED_PROP_1_VALUE); + verifyTableDescriptorProperty(conn, indexAfter, CUSTOM_PROP_1, MODIFIED_PROP_1_VALUE); + } + } + + // Test when no inheritable properties are configured, custom properties should not propagate + // (this test uses the default configured properties from doSetup, so we check that only + // CUSTOM_PROP_1 and CUSTOM_PROP_2 propagate and nothing else) + @Test + public void testOnlyConfiguredPropsAreInherited() throws Exception { + try (Connection conn = DriverManager.getConnection(getUrl(), new Properties())) { + String tableName = generateUniqueName(); + String indexName = generateUniqueName(); + String extraProp = "some.other.custom.prop"; + conn.createStatement().execute("CREATE TABLE " + tableName + + " (id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(10), flag BOOLEAN)" + + " \"" + CUSTOM_PROP_1 + "\"='" + INITIAL_PROP_1_VALUE + "'," + + " \"" + extraProp + "\"='extraval'"); + conn.createStatement() + .execute("CREATE INDEX " + indexName + " ON " + tableName + "(name)"); + + // Configured inheritable prop should propagate + verifyTableDescriptorProperty(conn, indexName, CUSTOM_PROP_1, INITIAL_PROP_1_VALUE); + // Non-configured prop should not propagate + verifyTableDescriptorProperty(conn, indexName, extraProp, null); + } + } + + private void verifyTableDescriptorProperty(Connection conn, String tableName, + String propertyName, String expectedValue) throws Exception { + try (Admin admin = conn.unwrap(PhoenixConnection.class).getQueryServices().getAdmin()) { + TableDescriptor td = admin.getDescriptor(TableName.valueOf(tableName)); + String actualValue = td.getValue(propertyName); + assertEquals("Mismatch for property " + propertyName + " on table " + tableName, + expectedValue, actualValue); + } + } +}