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

vjasani pushed a commit to branch 5.2
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/5.2 by this push:
     new 0d9a13812a PHOENIX-7625 Adding query plan information to connection 
Activity Logger (#2253)
0d9a13812a is described below

commit 0d9a13812afa175226dcb8115a1ba154733c4a8a
Author: vikas meka <vm...@salesforce.com>
AuthorDate: Thu Jul 31 14:13:49 2025 -0700

    PHOENIX-7625 Adding query plan information to connection Activity Logger 
(#2253)
---
 .../org/apache/phoenix/jdbc/PhoenixStatement.java  | 48 +++++++++++++++++++++
 .../org/apache/phoenix/log/ActivityLogInfo.java    |  3 +-
 .../phoenix/log/ConnectionActivityLogger.java      |  9 ++++
 .../org/apache/phoenix/query/QueryServices.java    |  2 +
 .../apache/phoenix/query/QueryServicesOptions.java |  4 ++
 .../java/org/apache/phoenix/util/QueryUtil.java    |  2 +
 .../org/apache/phoenix/end2end/ConnectionIT.java   | 34 +++++++++++++++
 .../phoenix/jdbc/LoggingConnectionLimiterIT.java   | 50 ++++++++++++++++++++++
 .../jdbc/LoggingSingleConnectionLimiterIT.java     |  2 +
 9 files changed, 153 insertions(+), 1 deletion(-)

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 2ea156e180..768f9c62f2 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
@@ -42,6 +42,7 @@ import static 
org.apache.phoenix.monitoring.MetricType.UPSERT_FAILED_SQL_COUNTER
 import static org.apache.phoenix.monitoring.MetricType.UPSERT_SQL_COUNTER;
 import static org.apache.phoenix.monitoring.MetricType.UPSERT_SQL_QUERY_TIME;
 import static 
org.apache.phoenix.monitoring.MetricType.UPSERT_SUCCESS_SQL_COUNTER;
+import static 
org.apache.phoenix.query.QueryServices.CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED;
 
 import java.io.File;
 import java.io.IOException;
@@ -64,6 +65,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 
 import org.apache.hadoop.conf.Configuration;
@@ -71,6 +74,7 @@ import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.CellComparator;
+import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.client.Consistency;
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.util.Pair;
@@ -87,6 +91,7 @@ import org.apache.phoenix.compile.DeclareCursorCompiler;
 import org.apache.phoenix.compile.DeleteCompiler;
 import org.apache.phoenix.compile.DropSequenceCompiler;
 import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
 import org.apache.phoenix.compile.ExpressionProjector;
 import org.apache.phoenix.compile.GroupByCompiler.GroupBy;
 import org.apache.phoenix.compile.ListJarsQueryPlan;
@@ -288,12 +293,16 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
     private int maxRows;
     private int fetchSize = -1;
     private int queryTimeoutMillis;
+    private boolean explainPlanLoggingEnabled;
     // Caching per Statement
     protected final Calendar localCalendar = Calendar.getInstance();
 
     public PhoenixStatement(PhoenixConnection connection) {
         this.connection = connection;
         this.queryTimeoutMillis = getDefaultQueryTimeoutMillis();
+        this.explainPlanLoggingEnabled = 
connection.getQueryServices().getProps()
+                .getBoolean(CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED,
+                        
QueryServicesOptions.DEFAULT_CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED);
     }
 
     /**
@@ -388,6 +397,9 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
                                     LOGGER.debug(LogUtil.addCustomAnnotations(
                                             "Explain plan: " + explainPlan, 
connection));
                                 }
+                                if (explainPlanLoggingEnabled) {
+                                    updateExplainPlanInformation(plan);
+                                }
                                 StatementContext context = plan.getContext();
                                 context.setQueryLogger(queryLogger);
                                 if (queryLogger.isDebugEnabled()) {
@@ -2605,4 +2617,40 @@ public class PhoenixStatement implements 
PhoenixMonitoredStatement, SQLCloseable
         return localCalendar;
     }
 
+    private void updateExplainPlanInformation(QueryPlan plan) throws 
SQLException {
+        if (
+                plan == null || !getConnection().getActivityLogger()
+                        
.isLevelEnabled(ActivityLogInfo.EXPLAIN_PLAN.getLogLevel())
+        ) {
+            return;
+        }
+
+        ExplainPlan explainPlan = plan.getExplainPlan();
+        ExplainPlanAttributes explainPlanAttributes = 
explainPlan.getPlanStepsAsAttributes();
+
+        List<HRegionLocation> location = 
explainPlanAttributes.getRegionLocations();
+        String regionInfo = getRegionInfo(location);
+
+        String sb = Stream.of(explainPlanAttributes.getExplainScanType(), 
regionInfo)
+                .collect(Collectors.joining(","));
+        updateActivityOnConnection(ActivityLogInfo.EXPLAIN_PLAN, sb);
+    }
+
+    private String getRegionInfo(List<HRegionLocation> location) {
+        if (location == null || location.isEmpty()) {
+            return "";
+        }
+
+        String regions =
+                location.stream().map(regionLocation -> 
regionLocation.getRegion().getEncodedName())
+                        .collect(Collectors.joining(","));
+
+        String hostnames = location.stream()
+                .map(HRegionLocation::getHostname)
+                .collect(Collectors.joining(","));
+
+        return QueryUtil.REGIONS + "={" + regions + "},"
+                + QueryUtil.HOSTNAMES + "={" + hostnames + "}";
+    }
+
 }
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/log/ActivityLogInfo.java 
b/phoenix-core-client/src/main/java/org/apache/phoenix/log/ActivityLogInfo.java
index 3baf44754d..3fa9b2fb8a 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/log/ActivityLogInfo.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/log/ActivityLogInfo.java
@@ -34,7 +34,8 @@ public enum ActivityLogInfo {
     REQUEST_ID("r", LogLevel.INFO,PVarchar.INSTANCE),
     TABLE_NAME("n", LogLevel.INFO,PVarchar.INSTANCE),
     OP_NAME("o", LogLevel.INFO,PVarchar.INSTANCE),
-    OP_STMTS("#", LogLevel.INFO, PInteger.INSTANCE);
+    OP_STMTS("#", LogLevel.INFO, PInteger.INSTANCE),
+    EXPLAIN_PLAN("ep", LogLevel.INFO, PVarchar.INSTANCE);
 
     public final String shortName;
     public final LogLevel logLevel;
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/log/ConnectionActivityLogger.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/log/ConnectionActivityLogger.java
index ee2a2a8af4..00e89eed26 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/log/ConnectionActivityLogger.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/log/ConnectionActivityLogger.java
@@ -147,4 +147,13 @@ public class ConnectionActivityLogger {
         return this.logLevel != null && logLevel != LogLevel.OFF ? 
logLevel.ordinal() <= this.logLevel.ordinal()
                 : false;
     }
+
+    /**
+     * Get the Explain plan information.
+     */
+    public String getExplainPlanInfo() {
+        return isLevelEnabled(ActivityLogInfo.EXPLAIN_PLAN.getLogLevel())
+                ? activityList.get(ActivityLogInfo.EXPLAIN_PLAN.ordinal())
+                : null;
+    }
 }
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 ea95e05f2b..3df7893f06 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
@@ -304,6 +304,8 @@ public interface QueryServices extends SQLCloseable {
             "phoenix.connection.activity.logging.enabled";
     public static final String CONNECTION_ACTIVITY_LOGGING_INTERVAL =
             "phoenix.connection.activity.logging.interval";
+    String CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED =
+            "phoenix.connection.activity.logging.explain.plan.enabled";
     public static final String DEFAULT_COLUMN_ENCODED_BYTES_ATRRIB  = 
"phoenix.default.column.encoded.bytes.attrib";
     public static final String DEFAULT_IMMUTABLE_STORAGE_SCHEME_ATTRIB  = 
"phoenix.default.immutable.storage.scheme";
     public static final String 
DEFAULT_MULTITENANT_IMMUTABLE_STORAGE_SCHEME_ATTRIB  = 
"phoenix.default.multitenant.immutable.storage.scheme";
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
 
b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
index 5c9210ef20..4c01c85d31 100644
--- 
a/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
+++ 
b/phoenix-core-client/src/main/java/org/apache/phoenix/query/QueryServicesOptions.java
@@ -29,6 +29,7 @@ import static 
org.apache.phoenix.query.QueryServices.CLIENT_SPOOL_THRESHOLD_BYTE
 import static 
org.apache.phoenix.query.QueryServices.COLLECT_REQUEST_LEVEL_METRICS;
 import static org.apache.phoenix.query.QueryServices.COMMIT_STATS_ASYNC;
 import static 
org.apache.phoenix.query.QueryServices.CONNECTION_ACTIVITY_LOGGING_ENABLED;
+import static 
org.apache.phoenix.query.QueryServices.CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED;
 import static 
org.apache.phoenix.query.QueryServices.CONNECTION_ACTIVITY_LOGGING_INTERVAL;
 import static 
org.apache.phoenix.query.QueryServices.CONNECTION_QUERY_SERVICE_HISTOGRAM_SIZE_RANGES;
 import static 
org.apache.phoenix.query.QueryServices.CONNECTION_QUERY_SERVICE_METRICS_ENABLED;
@@ -368,6 +369,7 @@ public class QueryServicesOptions {
     public static final int 
DEFAULT_INTERNAL_CONNECTION_MAX_ALLOWED_CONNECTIONS = 0;
 
     public static final boolean DEFAULT_CONNECTION_ACTIVITY_LOGGING_ENABLED = 
false;
+    public static final boolean 
DEFAULT_CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED = false;
     public static final int 
DEFAULT_CONNECTION_ACTIVITY_LOGGING_INTERVAL_IN_MINS = 15;
     public static final boolean DEFAULT_STATS_COLLECTION_ENABLED = true;
     public static final boolean DEFAULT_USE_STATS_FOR_PARALLELIZATION = true;
@@ -546,6 +548,8 @@ public class QueryServicesOptions {
                 DEFAULT_SERVER_MERGE_FOR_UNCOVERED_INDEX)
             .setIfUnset(MAX_IN_LIST_SKIP_SCAN_SIZE, 
DEFAULT_MAX_IN_LIST_SKIP_SCAN_SIZE)
             .setIfUnset(CONNECTION_ACTIVITY_LOGGING_ENABLED, 
DEFAULT_CONNECTION_ACTIVITY_LOGGING_ENABLED)
+            .setIfUnset(CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED,
+                        DEFAULT_CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED)
             .setIfUnset(CONNECTION_ACTIVITY_LOGGING_INTERVAL, 
DEFAULT_CONNECTION_ACTIVITY_LOGGING_INTERVAL_IN_MINS)
             .setIfUnset(CQSI_THREAD_POOL_ENABLED, 
DEFAULT_CQSI_THREAD_POOL_ENABLED)
             .setIfUnset(CQSI_THREAD_POOL_KEEP_ALIVE_SECONDS,
diff --git 
a/phoenix-core-client/src/main/java/org/apache/phoenix/util/QueryUtil.java 
b/phoenix-core-client/src/main/java/org/apache/phoenix/util/QueryUtil.java
index 52e4f6aa0e..af8bf049b9 100644
--- a/phoenix-core-client/src/main/java/org/apache/phoenix/util/QueryUtil.java
+++ b/phoenix-core-client/src/main/java/org/apache/phoenix/util/QueryUtil.java
@@ -136,6 +136,8 @@ public final class QueryUtil {
     public static final int DATA_TYPE_NAME_POSITION = 6;
 
     public static final String IS_SERVER_CONNECTION = "IS_SERVER_CONNECTION";
+    public static final String REGIONS = "regions";
+    public static final String HOSTNAMES = "hostnames";
     private static final String SELECT = "SELECT";
     private static final String FROM = "FROM";
     private static final String WHERE = "WHERE";
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConnectionIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConnectionIT.java
index 87cf8649e3..451afc09df 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConnectionIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ConnectionIT.java
@@ -20,6 +20,7 @@ package org.apache.phoenix.end2end;
 
 import static org.apache.phoenix.query.BaseTest.setUpConfigForMiniCluster;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assume.assumeTrue;
 
 import java.sql.Connection;
@@ -159,4 +160,37 @@ public class ConnectionIT {
         }
 
     }
+
+    @Test
+    public void testQueryPlanIsNullByDefault() throws SQLException {
+        // Test that query plan is null by default even after data operations
+        String tableName = "TEST_QUERY_PLAN_" + tableCounter++;
+
+        try (PhoenixConnection conn = (PhoenixConnection) 
DriverManager.getConnection("jdbc:phoenix")) {
+            // Initially, query plan should be null (disabled by default)
+            String initialQueryPlan = 
conn.getActivityLogger().getExplainPlanInfo();
+            assertNull("Query plan should be null by default (disabled)", 
initialQueryPlan);
+
+            // Create table and load data
+            try (Statement stmt = conn.createStatement()) {
+                stmt.execute("CREATE TABLE " + tableName + " (id INTEGER 
PRIMARY KEY, name VARCHAR)");
+                stmt.execute("UPSERT INTO " + tableName + " VALUES (1, 
'test1')");
+                stmt.execute("UPSERT INTO " + tableName + " VALUES (2, 
'test2')");
+                conn.commit();
+            }
+
+            // Execute a query - query plan should still be null since it's 
disabled by default
+            try (Statement stmt = conn.createStatement()) {
+                ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName);
+                while (rs.next()) {
+                    // Consume the result set
+                }
+
+                // Query plan should still be null since it's disabled by 
default
+                String queryPlan = 
conn.getActivityLogger().getExplainPlanInfo();
+                assertNull("Query plan should be null after query execution 
(disabled by default)",
+                        queryPlan);
+            }
+        }
+    }
 }
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingConnectionLimiterIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingConnectionLimiterIT.java
index 3a313f4973..9b02e17bc1 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingConnectionLimiterIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingConnectionLimiterIT.java
@@ -31,6 +31,7 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.PreparedStatement;
+import java.sql.Statement;
 import java.sql.Timestamp;
 import java.util.HashSet;
 import java.util.ArrayList;
@@ -199,6 +200,55 @@ public abstract class LoggingConnectionLimiterIT extends 
BaseTest {
 
     }
 
+    @Test
+    public void testQueryExplainPlan() throws Exception {
+
+        String query = "SELECT * FROM " + tableName; // FULL SCAN
+
+        if (getConnection() instanceof PhoenixConnection) {
+            try (PhoenixConnection pconn = 
getConnection().unwrap(PhoenixConnection.class);
+                 Statement stmt = pconn.createStatement()) {
+                ResultSet rs = stmt.executeQuery(query);
+                while (rs.next()) {
+                    // do nothing
+                }
+                boolean queryPlanFound = false;
+                String queryPlan = 
pconn.getActivityLogger().getExplainPlanInfo();
+                if (
+                        queryPlan != null && queryPlan.contains("FULL SCAN") 
&& queryPlan.contains("regions=")
+                                && queryPlan.contains("hostnames=")
+                ) {
+                    queryPlanFound = true;
+
+                    // Extract regions and hostnames efficiently
+                    String regions = extractBetweenBraces(queryPlan, 
"regions={");
+                    String hostnames = extractBetweenBraces(queryPlan, 
"hostnames={");
+
+                    assertFalse("Regions should not be empty", 
regions.trim().isEmpty());
+                    assertFalse("Hostnames should not be empty", 
hostnames.trim().isEmpty());
+
+                }
+                assertTrue("Query plan should contain FULL SCAN, regions, and 
hostnames", queryPlanFound);
+            }
+            try (PhoenixConnection pconn = 
getConnection().unwrap(PhoenixConnection.class);) {
+                loadData(pconn, "PhoenixTest", "1", 10, 2);
+                String queryPlan = 
pconn.getActivityLogger().getExplainPlanInfo();
+                assertTrue("Query plan should be empty for non-query 
operations", queryPlan.isEmpty());
+            }
+        } else {
+            // for HA case, ignoring as parallelPhoenixConnection object 
doesn't have activity Logger
+            // object defined.
+            assertTrue(getConnection() instanceof ParallelPhoenixConnection);
+        }
+
+    }
+
+    private String extractBetweenBraces(String text, String marker) {
+        int start = text.indexOf(marker) + marker.length();
+        int end = text.indexOf("}", start);
+        return text.substring(start, end);
+    }
+
     protected abstract ConnectionLimiter getConnectionLimiter() throws 
Exception ;
 
     protected int runSampleActivity(ActivityType activityType, int clientPool, 
int clientQueue, int numRows, int connNotClosed) throws Exception {
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingSingleConnectionLimiterIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingSingleConnectionLimiterIT.java
index fb0ad2871d..0d20b20a4d 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingSingleConnectionLimiterIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/LoggingSingleConnectionLimiterIT.java
@@ -64,6 +64,7 @@ public class LoggingSingleConnectionLimiterIT extends 
LoggingConnectionLimiterIT
                 
conf.set(QueryServices.INTERNAL_CONNECTION_MAX_ALLOWED_CONNECTIONS, 
String.valueOf(20));
                 conf.set(PhoenixHAExecutorServiceProvider.HA_MAX_POOL_SIZE, 
String.valueOf(5));
                 conf.set(PhoenixHAExecutorServiceProvider.HA_MAX_QUEUE_SIZE, 
String.valueOf(30));
+                
conf.set(QueryServices.CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED, 
String.valueOf(true));
                 return conf;
             }
 
@@ -74,6 +75,7 @@ public class LoggingSingleConnectionLimiterIT extends 
LoggingConnectionLimiterIT
                 
conf.set(QueryServices.INTERNAL_CONNECTION_MAX_ALLOWED_CONNECTIONS, 
String.valueOf(20));
                 conf.set(PhoenixHAExecutorServiceProvider.HA_MAX_POOL_SIZE, 
String.valueOf(5));
                 conf.set(PhoenixHAExecutorServiceProvider.HA_MAX_QUEUE_SIZE, 
String.valueOf(30));
+                
conf.set(QueryServices.CONNECTION_EXPLAIN_PLAN_LOGGING_ENABLED, 
String.valueOf(true));
                 Configuration copy = new Configuration(conf);
                 copy.addResource(confToClone);
                 return copy;

Reply via email to