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

shahrs87 pushed a commit to branch PHOENIX-6883-feature
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/PHOENIX-6883-feature by this 
push:
     new 4f8811b456 PHOENIX-7212 : Handle inherited view indexes when 
validating LAST_DDL_TIMESTAMPS (#1823)
4f8811b456 is described below

commit 4f8811b4562fb062f1bc88416e932ea2be9743ec
Author: palash <[email protected]>
AuthorDate: Wed Feb 14 09:50:43 2024 -0800

    PHOENIX-7212 : Handle inherited view indexes when validating 
LAST_DDL_TIMESTAMPS (#1823)
---
 .../org/apache/phoenix/execute/MutationState.java  |  2 +-
 .../org/apache/phoenix/jdbc/PhoenixStatement.java  |  2 +-
 .../phoenix/util/ValidateLastDDLTimestampUtil.java | 60 ++++++++-------
 .../java/org/apache/phoenix/util/ViewUtil.java     |  3 +-
 .../phoenix/cache/ServerMetadataCacheTest.java     | 90 ++++++++++++++++++++++
 .../apache/phoenix/compile/WhereOptimizerTest.java | 43 +++++++++++
 6 files changed, 170 insertions(+), 30 deletions(-)

diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/MutationState.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/MutationState.java
index 60df3a570f..a4341b91a9 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/execute/MutationState.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/execute/MutationState.java
@@ -1219,7 +1219,7 @@ public class MutationState implements SQLCloseable {
             List<TableRef> tableRefs = new 
ArrayList<>(this.mutationsMap.keySet());
             try {
                 ValidateLastDDLTimestampUtil.validateLastDDLTimestamp(
-                        connection, tableRefs, true, true);
+                        connection, tableRefs, true);
             } catch (StaleMetadataCacheException e) {
                 GlobalClientMetrics
                         
.GLOBAL_CLIENT_STALE_METADATA_CACHE_EXCEPTION_COUNTER.increment();
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
index aec3b4d78d..fa59125e86 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixStatement.java
@@ -389,7 +389,7 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
                                 //plan.getTableRef can be null in some cases 
like EXPLAIN <query>
                                 if (shouldValidateLastDdlTimestamp && 
plan.getTableRef() != null) {
                                     
ValidateLastDDLTimestampUtil.validateLastDDLTimestamp(
-                                        connection, 
Arrays.asList(plan.getTableRef()), false, true);
+                                        connection, 
Arrays.asList(plan.getTableRef()), true);
                                 }
 
                                 if (plan.getTableRef() != null
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/util/ValidateLastDDLTimestampUtil.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/util/ValidateLastDDLTimestampUtil.java
index 01d58f2f11..7d77e6aa89 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/util/ValidateLastDDLTimestampUtil.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/util/ValidateLastDDLTimestampUtil.java
@@ -29,6 +29,7 @@ import org.apache.hadoop.hbase.util.ByteStringer;
 import org.apache.phoenix.coprocessor.generated.RegionServerEndpointProtos;
 import org.apache.phoenix.exception.StaleMetadataCacheException;
 import org.apache.phoenix.jdbc.PhoenixConnection;
+import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.query.QueryServicesOptions;
 import org.apache.phoenix.schema.PName;
@@ -74,15 +75,14 @@ public class ValidateLastDDLTimestampUtil {
      * Verifies that table metadata for given tables is up-to-date in client 
cache with server.
      * A random live region server is picked for invoking the RPC to validate 
LastDDLTimestamp.
      * Retry once if there was an error performing the RPC, otherwise throw 
the Exception.
+     *
      * @param tableRefs
-     * @param isWritePath
      * @param doRetry
      * @throws SQLException
      */
-    public static void validateLastDDLTimestamp(
-            PhoenixConnection conn, List<TableRef> tableRefs, boolean 
isWritePath, boolean doRetry)
-            throws SQLException {
-
+    public static void validateLastDDLTimestamp(PhoenixConnection conn,
+                                                List<TableRef> tableRefs,
+                                                boolean doRetry) throws 
SQLException {
         String infoString = getInfoString(conn.getTenantId(), tableRefs);
         try (Admin admin = conn.getQueryServices().getAdmin()) {
             // get all live region servers
@@ -100,7 +100,7 @@ public class ValidateLastDDLTimestampUtil {
                     service = 
RegionServerEndpointProtos.RegionServerEndpointService
                     .newBlockingStub(admin.coprocessorService(regionServer));
             RegionServerEndpointProtos.ValidateLastDDLTimestampRequest request
-                    = getValidateDDLTimestampRequest(conn, tableRefs, 
isWritePath);
+                    = getValidateDDLTimestampRequest(tableRefs);
             service.validateLastDDLTimestamp(null, request);
         } catch (Exception e) {
             SQLException parsedException = ClientUtil.parseServerException(e);
@@ -112,7 +112,7 @@ public class ValidateLastDDLTimestampUtil {
             if (doRetry) {
                 // update the list of live region servers
                 conn.getQueryServices().refreshLiveRegionServers();
-                validateLastDDLTimestamp(conn, tableRefs, isWritePath, false);
+                validateLastDDLTimestamp(conn, tableRefs, false);
                 return;
             }
             throw parsedException;
@@ -122,19 +122,16 @@ public class ValidateLastDDLTimestampUtil {
     /**
      * Build a request for the validateLastDDLTimestamp RPC for the given 
tables.
      * 1. For a view, we need to add all its ancestors to the request
-     *    in case something changed in the hierarchy.
+     * in case something changed in the hierarchy.
      * 2. For an index, we need to add its parent table to the request
-     *    in case the index was dropped.
-     * 3. On the write path, we need to add all indexes of a table/view
-     *    in case index state was changed.
-     * @param conn
+     * in case the index was dropped.
+     * 3. Add all indexes of a table/view in case index state was changed.
+     *
      * @param tableRefs
-     * @param isWritePath
      * @return ValidateLastDDLTimestampRequest for the table in tableRef
      */
     private static RegionServerEndpointProtos.ValidateLastDDLTimestampRequest
-        getValidateDDLTimestampRequest(PhoenixConnection conn, List<TableRef> 
tableRefs,
-                                        boolean isWritePath) {
+        getValidateDDLTimestampRequest(List<TableRef> tableRefs) {
 
         RegionServerEndpointProtos.ValidateLastDDLTimestampRequest.Builder 
requestBuilder
                 = 
RegionServerEndpointProtos.ValidateLastDDLTimestampRequest.newBuilder();
@@ -158,18 +155,15 @@ public class ValidateLastDDLTimestampUtil {
             PTable ptable = tableRef.getTable();
             innerBuilder = 
RegionServerEndpointProtos.LastDDLTimestampRequest.newBuilder();
             setLastDDLTimestampRequestParameters(innerBuilder, ptable.getKey(),
-                                                    
ptable.getLastDDLTimestamp());
+                    ptable.getLastDDLTimestamp());
             requestBuilder.addLastDDLTimestampRequests(innerBuilder);
 
-            //on the write path, we need to validate all indexes of a 
table/view
-            //in case index state was changed
-            if (isWritePath) {
-                for (PTable idxPTable : tableRef.getTable().getIndexes()) {
-                    innerBuilder = 
RegionServerEndpointProtos.LastDDLTimestampRequest.newBuilder();
-                    setLastDDLTimestampRequestParameters(innerBuilder, 
idxPTable.getKey(),
-                                                            
idxPTable.getLastDDLTimestamp());
-                    requestBuilder.addLastDDLTimestampRequests(innerBuilder);
-                }
+            // add all indexes of the current table
+            for (PTable idxPTable : tableRef.getTable().getIndexes()) {
+                innerBuilder = 
RegionServerEndpointProtos.LastDDLTimestampRequest.newBuilder();
+                setLastDDLTimestampRequestParameters(innerBuilder, 
idxPTable.getKey(),
+                        idxPTable.getLastDDLTimestamp());
+                requestBuilder.addLastDDLTimestampRequests(innerBuilder);
             }
         }
         return requestBuilder.build();
@@ -181,15 +175,27 @@ public class ValidateLastDDLTimestampUtil {
     private static void setLastDDLTimestampRequestParameters(
             RegionServerEndpointProtos.LastDDLTimestampRequest.Builder builder,
             PTableKey key, long lastDDLTimestamp) {
+        String tableName = key.getTableName();
+        String schemaName = key.getSchemaName();
+
+        // view(V) with Index (VIndex) -> child view (V1) -> grand child view 
(V2)
+        // inherited view index is of the form V2#V1#VIndex, it does not exist 
in syscat
+        if 
(tableName.contains(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR)) {
+            int lastIndexOf = 
tableName.lastIndexOf(QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR);
+            String indexFullName = tableName.substring(lastIndexOf + 1);
+            tableName = SchemaUtil.getTableNameFromFullName(indexFullName);
+            schemaName = SchemaUtil.getSchemaNameFromFullName(indexFullName);
+        }
+
         byte[] tenantIDBytes = key.getTenantId() == null
                 ? HConstants.EMPTY_BYTE_ARRAY
                 : key.getTenantId().getBytes();
-        byte[] schemaBytes = key.getSchemaName() == null
+        byte[] schemaBytes = schemaName == null
                 ?   HConstants.EMPTY_BYTE_ARRAY
                 : key.getSchemaName().getBytes();
         builder.setTenantId(ByteStringer.wrap(tenantIDBytes));
         builder.setSchemaName(ByteStringer.wrap(schemaBytes));
-        builder.setTableName(ByteStringer.wrap(key.getTableName().getBytes()));
+        builder.setTableName(ByteStringer.wrap(tableName.getBytes()));
         builder.setLastDDLTimestamp(lastDDLTimestamp);
     }
 }
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/util/ViewUtil.java 
b/phoenix-core-client/src/main/java/org/apache/phoenix/util/ViewUtil.java
index 505b9f3ad1..aadba4a8e6 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/ViewUtil.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/ViewUtil.java
@@ -484,7 +484,8 @@ public class ViewUtil {
                             .setTableName(modifiedIndexName)
                             .setViewStatement(viewStatement)
                             
.setUpdateCacheFrequency(view.getUpdateCacheFrequency())
-                            .setTenantId(view.getTenantId())
+                            //retain the tenantId from the index being 
inherited
+                            .setTenantId(index.getTenantId())
                             
.setPhysicalNames(Collections.singletonList(index.getPhysicalName()))
                             .build());
                 }
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/cache/ServerMetadataCacheTest.java
 
b/phoenix-core/src/test/java/org/apache/phoenix/cache/ServerMetadataCacheTest.java
index ec70879b17..48042655f3 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/cache/ServerMetadataCacheTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/cache/ServerMetadataCacheTest.java
@@ -20,12 +20,15 @@ package org.apache.phoenix.cache;
 import org.apache.hadoop.hbase.util.Bytes;
 import 
org.apache.phoenix.coprocessorclient.metrics.MetricsMetadataCachingSource;
 import 
org.apache.phoenix.coprocessorclient.metrics.MetricsPhoenixCoprocessorSourceFactory;
+import org.apache.phoenix.end2end.IndexToolIT;
 import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+import org.apache.phoenix.jdbc.PhoenixResultSet;
 import org.apache.phoenix.jdbc.PhoenixStatement;
 import org.apache.phoenix.monitoring.GlobalClientMetrics;
 import org.apache.phoenix.query.ConnectionQueryServices;
+import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.schema.ColumnNotFoundException;
 import org.apache.phoenix.schema.ConnectionProperty;
@@ -1591,8 +1594,95 @@ public class ServerMetadataCacheTest extends 
ParallelStatsDisabledIT {
         }
     }
 
+    /**
+     * Test that tenant connections are able to learn about state change of an 
inherited index
+     * on their tenant views with different names.
+     */
+    @Test
+    public void testInheritedIndexOnTenantViewsDifferentNames() throws 
Exception {
+        testInheritedIndexOnTenantViews(false);
+    }
+
+    /**
+     * Test that tenant connections are able to learn about state change of an 
inherited index
+     * on their tenant views with same names.
+     */
+    @Test
+    public void testInheritedIndexOnTenantViewsSameNames() throws Exception {
+        testInheritedIndexOnTenantViews(true);
+    }
+
+    public void testInheritedIndexOnTenantViews(boolean sameTenantViewNames) 
throws Exception {
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        String url = QueryUtil.getConnectionUrl(props, config, "client1");
+        ConnectionQueryServices cqs = driver.getConnectionQueryServices(url, 
props);
+        String baseTableName =  generateUniqueName();
+        String globalViewName = generateUniqueName();
+        String globalViewIndexName =  generateUniqueName();
+        String tenantViewName1 =  generateUniqueName();
+        String tenantViewName2 =  sameTenantViewNames ? tenantViewName1 : 
generateUniqueName();
+        try (Connection conn = cqs.connect(url, props)) {
+            // create table, view and view index
+            conn.createStatement().execute("CREATE TABLE " + baseTableName +
+                    " (TENANT_ID CHAR(9) NOT NULL, KP CHAR(3) NOT NULL, PK 
CHAR(3) NOT NULL, KV CHAR(2), KV2 CHAR(2) " +
+                    "CONSTRAINT PK PRIMARY KEY(TENANT_ID, KP, PK)) 
MULTI_TENANT=true,UPDATE_CACHE_FREQUENCY=NEVER");
+            conn.createStatement().execute("CREATE VIEW " + globalViewName +
+                    " AS SELECT * FROM " + baseTableName + " WHERE  KP = 
'001'");
+            conn.createStatement().execute("CREATE INDEX " + 
globalViewIndexName + " on " +
+                    globalViewName + " (KV) " + " INCLUDE (KV2) ASYNC");
+            String tenantId1 = "tenantId1";
+            String tenantId2 = "tenantId2";
+            Properties tenantProps1 = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+            Properties tenantProps2 = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+            tenantProps1.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, 
tenantId1);
+            tenantProps2.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, 
tenantId2);
+
+            //create tenant views and upsert one row, this updates all the 
timestamps in the client's cache
+            try (Connection tenantConn1 = cqs.connect(url, tenantProps1);
+                 Connection tenantConn2 = cqs.connect(url, tenantProps2)) {
+                tenantConn1.createStatement().execute("CREATE VIEW " + 
tenantViewName1 + " AS SELECT * FROM " + globalViewName);
+                tenantConn1.createStatement().execute("UPSERT INTO " + 
tenantViewName1 + " (PK, KV, KV2) VALUES " + "('PK1', 'KV', '01')");
+                tenantConn1.commit();
+
+                tenantConn2.createStatement().execute("CREATE VIEW " + 
tenantViewName2 + " AS SELECT * FROM " + globalViewName);
+                tenantConn2.createStatement().execute("UPSERT INTO " + 
tenantViewName2 + " (PK, KV, KV2) VALUES " + "('PK2', 'KV', '02')");
+                tenantConn2.commit();
+            }
+            // build global view index
+            IndexToolIT.runIndexTool(false, "", globalViewName,
+                    globalViewIndexName);
+
+            // query on secondary key should use inherited index for all 
tenant views.
+            try (Connection tenantConn1 = cqs.connect(url, tenantProps1);
+                 Connection tenantConn2 = cqs.connect(url, tenantProps2)) {
+
+                String query1 = "SELECT KV2 FROM  " + tenantViewName1 + " 
WHERE KV = 'KV'";
+                String query2 = "SELECT KV2 FROM  " + tenantViewName2 + " 
WHERE KV = 'KV'";
+
+                ResultSet rs = 
tenantConn1.createStatement().executeQuery(query1);
+                assertPlan((PhoenixResultSet) rs,  "",
+                        tenantViewName1 + 
QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR + globalViewIndexName);
+                assertTrue(rs.next());
+                assertEquals("01", rs.getString(1));
+
+                rs = tenantConn2.createStatement().executeQuery(query2);
+                assertPlan((PhoenixResultSet) rs,  "",
+                        tenantViewName2 + 
QueryConstants.CHILD_VIEW_INDEX_NAME_SEPARATOR + globalViewIndexName);
+                assertTrue(rs.next());
+                assertEquals("02", rs.getString(1));
+            }
+        }
+    }
+
+
 
     //Helper methods
+    public static void assertPlan(PhoenixResultSet rs, String schemaName, 
String tableName) {
+        PTable table = rs.getContext().getCurrentTable().getTable();
+        assertTrue(table.getSchemaName().getString().equals(schemaName) &&
+                table.getTableName().getString().equals(tableName));
+    }
+
     private long getLastDDLTimestamp(String tableName) throws SQLException {
         Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
         // Need to use different connection than what is used for creating 
table or indexes.
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
index 20bd097e04..99ecb0ed95 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
@@ -3458,6 +3458,49 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
         }
     }
 
+    /**
+     * Test that tenantId is present in the scan start row key when using an 
inherited index on a tenant view.
+     */
+    @Test
+    public void testScanKeyInheritedIndexTenantView() throws Exception {
+        String baseTableName =  generateUniqueName();
+        String globalViewName = generateUniqueName();
+        String globalViewIndexName =  generateUniqueName();
+        String tenantViewName =  generateUniqueName();
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            // create table, view and view index
+            conn.createStatement().execute("CREATE TABLE " + baseTableName +
+                    " (TENANT_ID CHAR(8) NOT NULL, KP CHAR(3) NOT NULL, PK 
CHAR(3) NOT NULL, KV CHAR(2), KV2 CHAR(2) " +
+                    "CONSTRAINT PK PRIMARY KEY(TENANT_ID, KP, PK)) 
MULTI_TENANT=true");
+            conn.createStatement().execute("CREATE VIEW " + globalViewName +
+                    " AS SELECT * FROM " + baseTableName + " WHERE  KP = 
'001'");
+            conn.createStatement().execute("CREATE INDEX " + 
globalViewIndexName + " on " +
+                    globalViewName + " (KV) " + " INCLUDE (KV2)");
+            //create tenant view
+            String tenantId = "tenantId";
+            Properties tenantProps = new Properties();
+            tenantProps.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, tenantId);
+            try (Connection tenantConn = DriverManager.getConnection(getUrl(), 
tenantProps)) {
+                tenantConn.createStatement().execute("CREATE VIEW " + 
tenantViewName + " AS SELECT * FROM " + globalViewName);
+                // query on secondary key
+                String query = "SELECT KV2 FROM  " + tenantViewName + " WHERE 
KV = 'KV'";
+                PhoenixConnection pconn = 
tenantConn.unwrap(PhoenixConnection.class);
+                PhoenixPreparedStatement pstmt = new 
PhoenixPreparedStatement(pconn, query);
+                QueryPlan plan = pstmt.compileQuery();
+                plan = 
tenantConn.unwrap(PhoenixConnection.class).getQueryServices().getOptimizer().optimize(pstmt,
 plan);
+                // optimized query plan should use inherited index
+                assertEquals(tenantViewName + "#" + globalViewIndexName, 
plan.getContext().getCurrentTable().getTable().getName().getString());
+                Scan scan = plan.getContext().getScan();
+                PTable viewIndexPTable = 
tenantConn.unwrap(PhoenixConnection.class).getTable(globalViewIndexName);
+                // PK of view index [_INDEX_ID, tenant_id, KV, PK]
+                byte[] startRow = 
ByteUtil.concat(PLong.INSTANCE.toBytes(viewIndexPTable.getViewIndexId()),
+                        PChar.INSTANCE.toBytes(tenantId),
+                        PChar.INSTANCE.toBytes("KV"));
+                assertArrayEquals(startRow, scan.getStartRow());
+            }
+        }
+    }
+
     private void createBaseTable(String baseTable) throws SQLException {
 
         try (Connection globalConnection = 
DriverManager.getConnection(getUrl())) {

Reply via email to